设计模式--代理设计模式

news2024/9/22 4:13:08

在这里插入图片描述

🎉🎉🎉写在前面:
博主主页:🌹🌹🌹戳一戳,欢迎大佬指点!
目标梦想:进大厂,立志成为一个牛掰的Java程序猿,虽然现在还是一个小菜鸟嘿嘿
-----------------------------谢谢你这么帅气美丽还给我点赞!比个心-----------------------------

在这里插入图片描述


代理设计模式

  • 一,初识代理模式
  • 二,静态代理模式
    • 2.1,静态代理使用场景
      • 2.1.1,缓存代理
      • 2.1.2,安全代理
      • 2.1.3,虚拟代理
    • 2.2,静态代理总结
  • 三,动态代理模式
    • 3.1,JDK动态代理
      • 3.1.1,实例实现
      • 3.1.2,过程解析
    • 3.2,CGLIB动态代理
      • 3.2.1,实例实现
      • 3.2.2,过程解析
    • 3.3,JDK动态代理 vs CGLIB


一,初识代理模式

什么是代理设计模式?

代理设计模式是一种结构型设计模式,它为我们的目标对象提供一个代理,以控制对象的访问。这个代理的过程中,我们可以通过代理对象来做到增强目标对象功能的目的。

举一个例子来理解,目标对象就是明星本人,而代理对象是此人的经纪人,当商家找这位明星做代言的时候,商家不会直接去和明星本人谈,而是通过经纪人去交涉,确定一些列的合作细节,比如代言非,合同等一切琐碎的细节问题,最终出席代言的还是明星本人。这个过程中你,经纪人就作为代理对象,控制了目标对象的访问,并增强了一些功能。

代理设计模式可以用于实现懒加载,安全访问控制,日志记录等功能。代理模式可以分为静态代理与动态代理。静态代理指的是代理类在编译时就已经确定,而动态代理指的是运行时动态的生成代理类,下面会详细的介绍这两种代理


二,静态代理模式

2.1,静态代理使用场景

2.1.1,缓存代理

缓存代理是一种特殊类型的代理模式,它可以为耗时的操作或者重复的请求提供缓存功能,从而提高程序的执行效率。缓存代理通常会在内部维护一个缓存数据结构,例如HashMap或者LinkedHashMap,用来存储已经处理过的请求以及结果。

缓存代理应用实例:

假设现在存在一个数据查询接口,它从数据库或者其他数据源中检索数据。在没有缓存代理的情况下,每次检索数据都需查询数据库,这样查询效率会比较低,并且也会占用更多的资源。此时可以通过缓存代理,将查询过的数据保存在内存中,从而避免重复查询数据库


定义一个查询接口:

//定义查询数据库的接口
public interface DataQuery {
    String qurey(String queryKey);
}

定义具体数据查询类:

/*
定义一个组件类 这个类是用来具体查询数据库的 会实现查询接口
* */
public class DataQueryUtil implements DataQuery{
    @Override
    public String qurey(String queryKey) {
        return "hello world";//这个按照具体要求会去查询数据库
    }
}

定义数据查询代理类:

import java.util.HashMap;
import java.util.Map;
/*
* 定义一个代理类 主要用来实现缓存代理的实现
* */
public class DataQueryProxy implements DataQuery{
    private final DataQueryUtil dataQueryUtil;//定义目标对象的引用
    private final Map<String,String> cache;

    public DataQueryProxy(DataQueryUtil dataQueryUtil){
        this.dataQueryUtil = dataQueryUtil;//在构造代理对象的时候初始化目标对象
        this.cache = new HashMap<>();//构建缓存
    }

    @Override
    public String qurey(String queryKey) {
        //在这里实现对于数据库查询操作的增强
        //首先查询缓存
        String res = cache.get(queryKey);
        if(res == null){
            //如果缓存查询结果为空 就说明当前并无改查询的缓存记录 需要去查询数据库
            res = dataQueryUtil.qurey(queryKey);
            //将数据库的查询结果加入缓存
            cache.put(queryKey,res);
            System.out.println("result from database~");
        }else{
            //如果不是空就说明是在缓存中查询到了 直接返回结果
            System.out.println("result from cache");
        }
        return res;
    }
}

定义主函数类,进行测试:

public class Main {
    public static void main(String[] args) {
        //通过代理类去进行数据查询
        DataQueryUtil dataQueryUtil = new DataQueryUtil();
        DataQueryProxy dataQueryProxy = new DataQueryProxy(dataQueryUtil);
        String queryKey = "key";
        //此时是第一次查询 应该是会去查询数据库
        String res = dataQueryProxy.qurey(queryKey);
        System.out.println("这是第一次查询的结果:" + res);
        //此时进行第二次查询 应该是走的缓存
        res = dataQueryProxy.qurey(queryKey);
        System.out.println("这是第二次查询的结果:" + res);
    }
}

最终测试结果输出:

在这里插入图片描述

最终结果与我们预期的一样,最开始应该是查询的数据库,第二次查询就应该走缓存了。这里的查询代理类就实现了我们的功能增强,也就是缓存代理


2.1.2,安全代理

安全代理主要是为了通过代理对象,来实现对于目标对象的访问的控制,通过安全代理,可以实现权限验证等安全相关功能。


比如现在需要去查询一个隐私数据,在查询之前可以通过代理对象来进行权限验证:

定义一个查询接口:

package securityProxy;
//定义一个私密查询接口
public interface PersonalQuery {
    String queryData(String key,String username);
}

定义一个查询实体类:

package securityProxy;

//实际来查询数据的类
public class InfoQuery implements PersonalQuery{
    @Override
    public String queryData(String key,String username) {
        return "hello world";
    }
}

定义一个代理类:

package securityProxy;

import java.util.ArrayList;
import java.util.List;

public class InfoQueryProxy implements PersonalQuery{
    private final InfoQuery infoQuery;
    private final List<String> userAuthorizationList ;//用户授权列表

    public InfoQueryProxy(InfoQuery infoQuery) {
        this.infoQuery = infoQuery;
        userAuthorizationList = new ArrayList<>();
        userAuthorizationList.add("zhangsan");//人为的添加授权列表
    }

    @Override
    public String queryData(String key,String username) {
        //具体的代理逻辑
        //首先验证用户是否具有权限
        String ret = "";
        if(userAuthorizationList.contains(username)){
            //如果说包含该用户名 那么就具有权限去进行查询操作
            ret = infoQuery.queryData(key,username);
            System.out.println("具有权限进行查询,结果如下:" + ret);
        }else{
            //否则就是不具有查询权限
            System.out.println("不具有权限进行查询");
        }
        return ret;
    }
}

主函数进行验证:

import cacheProxy.DataQueryProxy;
import cacheProxy.DataQueryUtil;
import securityProxy.InfoQuery;
import securityProxy.InfoQueryProxy;

public class Main {
    public static void main(String[] args) {
        InfoQuery infoQuery = new InfoQuery();
        InfoQueryProxy infoQueryProxy = new InfoQueryProxy(infoQuery);
        String queryKey = "key";
        String res1 = infoQueryProxy.queryData(queryKey,"zhangsan");
        System.out.println(res1);

        String res2 = infoQueryProxy.queryData(queryKey,"lisi");
        System.out.println(res2);
    }
}

最终测试结果:
在这里插入图片描述

可以看到,当我们的用户名是lisi的时候,就查询失败,因为lisi不在我们的授权列表中


2.1.3,虚拟代理

虚拟代理主要用于实现懒加载,也就是延迟创建耗时或者资源密集型对象。虚拟代理在初始访问时才会创建对象,之后则可以直接使用该对象,也就是做到懒加载我们的密集型资源。


假设现在我们有一个图片资源,从网络加载图片,但是当前图片资源很大,所以我们希望当需要进行展示的时候才会去加载图片资源。


定义一个显示图片接口:

package ImageProxy;

public interface Image {
    void display();
}

定义加载图片显示的实体类:

package ImageProxy;

public class LargeImage implements Image{
    public LargeImage(String url) {
        System.out.println("开始加载图片资源");
    }
    @Override
    public void display() {
        System.out.println("进行图片资源展示");
    }
}

定义显示图片的代理类:

package ImageProxy;

public class LargeImageProxy implements Image{
    private String url;
    private LargeImage largeImage;
    public LargeImageProxy(String url) {
        this.url = url;
    }

    @Override
    public void display() {
        if(largeImage == null){
            largeImage = new LargeImage(url);//创建具体的实体类,让其去加载
        }
        largeImage.display();
    }
}

主函数:

import ImageProxy.LargeImage;
import ImageProxy.LargeImageProxy;
import cacheProxy.DataQueryProxy;
import cacheProxy.DataQueryUtil;
import securityProxy.InfoQuery;
import securityProxy.InfoQueryProxy;

public class Main {
    public static void main(String[] args) {
        LargeImageProxy largeImageProxy = new LargeImageProxy("xxxxx");
        largeImageProxy.display();
    }
}

测试结果:
在这里插入图片描述

此时,只有当我们通过代理类调用display()方法时,才回去真正的创建LargeImage的实体类,进行图片的加载展示。


2.2,静态代理总结

总体而言,静态代理的核心就是定义一个公共的接口,然后我们的被代理类与代理类都需要实现该接口,重写对应的方法,然后在代理类中的方法中实现对被代理类的方法的增强。(面向接口编程,让两个对象之间关联起来)

当然,这是通过接口的形式实现静态代理,其实通过继承也是可以的,只不过就是代理类继承被代理类重写方法,然后去实现对于被代理类方法的增强,相对来说耦合度会更高,不够灵活。两种方式的关联方式不同,一个是通过接口,一个是通过继承的关系。


三,动态代理模式

Java中动态代理的实现主要有两种:一种是基于JDK的动态代理,另一种是基于CGLIB的动态代理。

动态代理相对于静态代理,动态代理是在运行的时候动态的去生成代理类,不需要人为的定义实现代理类,会更加方便灵活。但是因为动态代理的底层实现其实是基于反射机制的,所以在性能上会更差,开销会大一些。


3.1,JDK动态代理

对于JDK动态代理来说,我们重点关注两个点,一个是 java.lang.reflect.Proxy 类,一个是 InvocationHandler 接口

3.1.1,实例实现

还是以上面静态代理中的代理缓存的例子来说,我们现在将其改为JDK动态代理实现,如下:


定义一个查询接口:

package cacheProxy;

//定义查询数据库的接口
public interface DataQuery {
    String qurey(String queryKey);
}

定义实际的数据查询类:

package cacheProxy;
/*
定义一个组件类 这个类是用来具体查询数据库的 会实现查询接口
* */
public class DataQueryUtil implements DataQuery{
    @Override
    public String qurey(String queryKey) {
        return "hello world";//这个按照具体要求会去查询数据库
    }
}

定义拦截处理类,实现InvocationHandler接口:

package cacheProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

//定义具体的拦截实现规则 也就是拦截到方法后 具体要做些什么操作
public class MyInvocationHandler implements InvocationHandler {
    private final DataQueryUtil dataQueryUtil;//定义目标对象的引用
    private final Map<String,String> cache;
    public MyInvocationHandler(DataQueryUtil dataQueryUtil) {
        this.dataQueryUtil = dataQueryUtil;
        this.cache = new HashMap<>(255);//构建缓存
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String ret = null;
        if(method.getName().equals("qurey")){
            //如果说调用的方法是query 则开始走校验逻辑 查询缓存或者是数据库
            ret = cache.get(args[0].toString());//获取到方法的参数 从缓存中进行查询
            if(ret == null){
                //说明缓存中没有 需要查询数据库 就是调用目标对象的方法
                ret = (String) method.invoke(dataQueryUtil,args);
                System.out.println("data from database~");
                cache.put(args[0].toString(),ret);
            }else{
                System.out.println("data from cache~");
            }
        }
        return ret;
    }
}

主函数:

import cacheProxy.DataQuery;
import cacheProxy.DataQueryUtil;
import cacheProxy.MyInvocationHandler;
import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        DataQueryUtil dataQueryUtil = new DataQueryUtil();
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(dataQueryUtil);

        DataQuery dataQuery = (DataQuery) Proxy.newProxyInstance(dataQueryUtil.getClass().getClassLoader(),dataQueryUtil.getClass().getInterfaces(),myInvocationHandler);
        String ret1 = dataQuery.qurey("key");
        System.out.println(ret1);

        String ret2 = dataQuery.qurey("key");
        System.out.println(ret2);
    }
}

测试运行结果:
在这里插入图片描述


3.1.2,过程解析

可以看到,此时我们使用JDK动态代理的时候,是不需要我们自己去定义代理类的,只需要指定好相关的参数即可:
在这里插入图片描述


可能看到这里,小伙伴们还是会不太明白到底是怎么实现代理的,几个方法的参数到底是什么意思,现在我会给大家慢慢解答~
首先,关注点在 java.lang.Proxy的newProxyInstance(ClassLoader loader , Class<?>[] interfaces , InvocationHandler h)上,如下:

  • ClassLoader loader

类加载器,一般指定为目标对象的类加载器。因为代理类是在运行时动态生成的,所以说在编译时期是没有对应的字节码文件的,所以这里就需要我们指定好类加载器,确保我们动态生成的代理类可以被正确的加载和使用。我们的代理类是在Application ClassLoader下被加载的

  • Class<?>[] interfaces

目标对象的接口列表,也就是指定好我们的代理类最终需要实现的接口,当然实现接口后重写的对应方法时不需要具体的实现,因为此处的方法相当于只是起了一个标记作用,最终调用处理还是在invoke()方法中。在JDK动态代理中,也是通过面向接口的形式关联代理类与被代理类

  • InvocationHandler h

这个参数是最重要的,因为具体的代理增强逻辑都是在这个里面实现的,我们需要传入一个实现了InvocationHandler接口的类的实例,并且该实例中重写写invoke()方法。它的工作逻辑就是当我们在调用代理类中对应的方法的时候,JVM会将请求拦截并转发到invoke()方法中做统一的调度实现,在invoke()方法实现对于目标对象的增强,因为最终还是会在此invoke()方法中利用method.invoke()方法调用目标对象的方法,但是在前后可以实现例如日志管理,权限校验等功能的增强


另外,既然说到了InvocationHandler的invoke()方法,那么也是需要具体解释一下这个方法的~

此方法也含有三个参数,invoke( Object proxy , final Method method , Object[] args)

  • Object proxy

proxy是代理对象,也就是动态生成的代理类的实例对象。可以通过proxy访问代理对象本身,可以在代理对象上执行一些特定的操作,如获取代理对象的信息等

  • final Method method

Method 对象是一个用来表示 Java 语言中的一个方法的类。它提供了获取方法信息(如方法名、返回值、参数类型等)和调用方法的能力。当我们使用反射来调用某个方法时,首先需要获取该方法对应的 Method 对象。可以通过 Class 对象的 getMethod() 或 getDeclaredMethod() 方法来获取一个 Method 对象。这里的method对象主要是用来调用我们的目标对象的方法的,通过method.invoke()方法


invoke(Object obj, Object… args),该方法也是两个参数,obj是我们的目标对象的实例,因为最终执行体还是我们的目标对象的方法,args也就是之前说的参数列表,用于给方法调用提供参数

  • Object[] args

参数列表,是一个Object类型数组,其中包含着我们目标对象方法调用所需要的参数


3.2,CGLIB动态代理

CGLIB是通过继承的方式来做到对于被代理类的增强,它会在运行时动态的构造出一个子类来扩展目标对象的功能。这里我们主要关注两个点,一个是Enhancer类,一个是MethodInterceptor接口


3.2.1,实例实现

还是是缓存代理的例子来进行实现,此时通过CGLIB实现代理,如下:


引入CGLIB的依赖:

因为CGLIB它相当于是一个第三方的库,所以是需要我们手动去引入一下依赖才能进行使用的

<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.12</version>
    </dependency>
</dependencies>

定义查询接口:

package cacheProxy;

//定义查询数据库的接口
public interface DataQuery {
    String qurey(String queryKey);
}

定义查询实体类:

package cacheProxy;

/*
定义一个组件类 这个类是用来具体查询数据库的 会实现查询接口
* */
public class DataQueryUtil implements DataQuery{
    @Override
    public String qurey(String queryKey) {
        return "hello world";//这个按照具体要求会去查询数据库
    }
}

定义MyInterceptor接口实现类:

package cacheProxy;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class MyMethodInterceptor implements MethodInterceptor {
    private final Map<String,String> cache;

    public MyMethodInterceptor() {
        this.cache = new HashMap<>(255);
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        String ret = null;
        if(method.getName().equals("qurey")){
            //如果说调用的方法是query 则开始走校验逻辑 查询缓存或者是数据库
            ret = cache.get(objects[0].toString());//获取到方法的参数 从缓存中进行查询
            if(ret == null){
                //说明缓存中没有 需要查询数据库 就是调用目标对象的方法
                ret = (String) methodProxy.invokeSuper(o,objects);
                System.out.println("data from database~");
                cache.put(objects[0].toString(),ret);
            }else{
                System.out.println("data from cache~");
            }
        }
        return ret;
    }
}

定义主函数:

import cacheProxy.*;
import net.sf.cglib.proxy.Enhancer;
public class Main {
    public static void main(String[] args) {
        MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor();

        Enhancer enhancer = new Enhancer();//定义Enhancer实例
        enhancer.setClassLoader(DataQueryUtil.class.getClassLoader());//设置类加载器
        enhancer.setSuperclass(DataQueryUtil.class);//设置被代理类
        enhancer.setCallback(myMethodInterceptor);//设置方法拦截器 也即是实现MethodInterceptor接口的类的实例
        DataQueryUtil dataQueryUtil1 = (DataQueryUtil) enhancer.create();//创建代理类 代理类是目标类的子类
        String ret1 = dataQueryUtil1.qurey("key");
        System.out.println(ret1);

        String ret2 = dataQueryUtil1.qurey("key");
        System.out.println(ret2);
    }
}

测试运行结果:
在这里插入图片描述


3.2.2,过程解析

其实相对于JDK动态代理而言,CGLIB的实现过程其实差异并不是很大,只是这里是通过继承来是实现的增强代理,MethodInterceptor接口就相当于之前的InvocationHandler接口,interceptor()就相当于invoke()。


首先,定义拦截处理规则,具体是冲喜intercceptor方法,具体参数如 intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)

  • Object o

o就是我们的目标对象,即被代理对象,后面具体调用的还是该对象的对应方法

  • Method method

method对象的作用和前面JDK动态代理的作用一样,主要是用来获取方法相关的信息

  • Object[] objects

调用方法所需要的参数,这是一个参数数组

  • MethodProxy methodProxy

这是CGLIB提供的一个专门用来调用被代理类原始方法的类,我们可以通过它提供的invokeSuper()方法来调用我们目标对象的方法,该方法有两个参数,一个是目标对象的实例,也就是 o ,然后再就是对应的参数 objects


然后通过Enhancer来生成代理类,步骤如下:

  1. 定义Enhancer实例

    Enhancer enhancer = new Enhancer();//定义Enhancer实例
    
  2. 定义MethodInterceptor实例

    MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor();
    
  3. 设置类加载器

    enhancer.setClassLoader(DataQueryUtil.class.getClassLoader());//设置类加载器
    
  4. 设置父类,也就是被代理类

    enhancer.setSuperclass(DataQueryUtil.class);//设置被代理类
    
  5. 设置方法拦截器

    enhancer.setCallback(myMethodInterceptor);//设置方法拦截器 也即是实现MethodInterceptor接口的类的实例
    
  6. 创建代理类

    DataQueryUtil dataQueryUtil1 = (DataQueryUtil) enhancer.create();//创建代理类 代理类是目标类的子类
    
  7. 调用方法

    dataQueryUtil1.qurey("key")
    

3.3,JDK动态代理 vs CGLIB

  • 应用场景不同

JDK动态代理是通过面向接口编程的方式来实现代理的,所以这就要求被代理类至少是实现了一个接口的。但是CGLIB是可以适用于实现接口的,或者是没有实现接口的类都可以进行代理,应用面更广一些

  • 代理方式不同

JDK是通过反射来动态的生成代理类,代理类与被代理类之间的关联方式是通过实现相同的接口。CGLIB的代理类也是反射生成的,只不过被代理类与代理类之间是是一个父子类的关系,也就是通过继承的手法使得代理类与被代理类关联起来


既然CGLIB是继承的手法来实现代理,所以是不可以对private,final,static修饰的方法进行代理的

  • 效率不同

在大部分情况下,JDK动态代理的效率是会更高的,CGLIB生成的代理类更重量,并且随着JDK的发展,效率是会越来越好的。总体而言,如果被代理类存在接口,那么就使用JDK动态代理,不存在接口就使用CGLIB代理,这也是Spring AOP采用的方式


好久没更新博客了,还是老样子,有错误大家多多指正,后面会持续的进行更新了,主要是框架,设计模式,算法相关的了,大家一起加油,秋招冲冲冲!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/733091.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++之std::function和lambda表达式回调函数(一百五十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

新版本FasterTransformer的FUSED_MHA

关于 UNFUSED_PADDED_MHA VS FUSED_MHA FUSED_MHA用了另一种kernel的执行方法(和添加链接描述相同,将在下一个section说明)UNFUSED_PADDED 的 KERNELS执行代码在 src/fastertransformer/kernels/unfused_attention_kernels.cu enum class AttentionType {UNFUSED_MHA,UNFUSED…

C#,中国福利彩票《刮刮乐》的数学算法(01)——幸运123

彩票名称&#xff1a;幸运123面值&#xff1a;20元/张最高奖&#xff1a;100万&#xff08;人民币&#xff09;全套款式&#xff1a;2款玩法介绍&#xff1a; 一份好运&#xff0c;二倍快乐&#xff0c;三重惊喜。福彩刮刮乐新游戏“幸运123”&#xff0c;红色的票面上点缀着礼…

基于simulink使用同步图像跟踪白板上的标记(附源码)

一、前言 此示例演示如何使用Simulink基于图像跟踪白板上的标记。 二、模型 示例模型包含模型引用层次结构。每个模型都有助于图像处理算法。 ex_tracking_marker- 跟踪输入视频中的标记的顶级模型。此模型使用视频查看器块呈现输出视频&#xff0c;并将输出帧记录在工作区变…

Unity桌面弹球小游戏Finger Soccer Game Kit 1.1

按住鼠标左键发射打球 还可以开启双人模式来玩 地址&#xff1a;https://download.csdn.net/download/Highning0007/88020441

《分布式中间件技术实战:Java版》学习笔记(三):Redis实现点赞、取消赞功能

用户在发布内容&#xff08;包括博客、想法、日记等等&#xff09;时&#xff0c;后台数据入库后&#xff0c;要往Redis的有序集合添加一条分数为0的记录。这个有序集合是用来对内容点赞量做排序的。同时&#xff0c;可以记录用户操作日志。 Override public String insertArt…

C++常用库函数 4.数学函数

函数名&#xff1a;abs 函数原型&#xff1a;int abs(int n) ; 参数 in 需要求绝对值的整数。 所需头文件&#xff1a;<cstdlib>或<cmath> 功能和返回值&#xff1a;返回 n 的绝对值&#xff1b;没有错误返回函数名&#xff1a;acos 函数原型&#xff1a;doubl…

Cocos Creator 打包 Android 原生,如何配置构建环境?关键就一点

前段时间&#xff0c;有好几位老铁留言 Cocos Creator 打包 Android 原生出现问题&#xff1a;一种是构建失败&#xff0c;一种是运行起来报错&#xff01; 其实&#xff0c;我也是有好长一段时间没有碰过 Android 原生了&#xff0c;而且我这台电脑&#xff0c;环境都没有配置…

idea创建web项目没有jsp选项,不识别jsp,没有tomcat选项

如果你的idea的web项目中没有jsp选项 同时也不识别jsp 那么建议你检查一下你的idea是否为社区版 如果是社区版那么没有jsp的问题无法解决&#xff0c;这只是无法识别&#xff0c;但是语句对的可以正常运行 解决这个问题建议换个idea 至于tomcat&#xff0c; 在plugins中搜s…

axios的学习

axios是基于promise对ajax的一种封装 //将省份信息打印到网页上 <p class"my-p"></p> <script src"https://unpkg.com/axios/dist/axios.min.js"></script> <script>axios({url:http://hmajax.itheima.net/api/province}).…

【PCIE】hot-reset和link disable

Hot reset 规则 如果上游伪端口&#xff08;Pseudo Port&#xff09;的任何一个通道连续接收到两个带有热复位位设置为1b、禁用链路位和回环位设置为0b的TS1有序集合&#xff0c;并且两个伪端口上的任何一个通道&#xff08;接收到TS1有序集合&#xff09;要么收到EIOS&#xf…

java方法的覆盖(Overriding )和隐藏(Hiding)

Java方法的覆盖&#xff08;Overriding &#xff09;针对的是实例方法&#xff08;即非静态方法&#xff09;&#xff0c;而方法的隐藏&#xff08;Hiding&#xff09;针对的是类方法&#xff08;即静态方法&#xff09;。 方法的覆盖和隐藏指的是子类对从父类继承的方法进行重…

基于simulink基于颜色分割方法跟踪人员的面部和手部(附源码)

一、前言 此示例演示如何使用基于颜色的分割方法跟踪人员的面部和手部。 二、模型 下图显示了颜色分割示例模型&#xff1a; 三、颜色分割结果 为了为示例创建准确的颜色模型&#xff0c;处理了许多包含肤色样本的图像&#xff0c;以计算 Cb 和 Cr 颜色通道的均值 &#xf…

基于Unity2017版本的2D3D Infinite Runner Engine 1.5.1二维三维跑酷游戏模板

基于Unity2017版本的2D3D Infinite Runner Engine 1.5.1二维三维跑酷游戏模板 有多种游戏模式 还有个竖屏的玩法 工程地址&#xff1a;https://download.csdn.net/download/Highning0007/88020755

MFC学习日记(一)——创建新项目

此系列所有文章参考链接&#xff1a;http://www.jizhuomi.com/software/141.html 点击file新建项目创建一个MFC新项目 点击确定 点击下一步 选择应用程序类型 我们看到有四种类型&#xff1a;Single document&#xff08;单文档&#xff09;、Multiple documents&#xff…

传统图像处理之目标检测——人脸识别

代码实战&#xff1a;人脸识别 import numpy as np import cv2 img cv2.imread("3.webp")face_cascade cv2.CascadeClassifier(r./haarcascade_frontalface_default.xml)gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#探测图片中的人脸 faces face_cascade.detec…

Android kotlin 实现把多个控件整体上下拉回弹功能(添加是否禁用顶部和底部回弹的参数设置,以及回弹效果结束监听)

目录 一、实现效果二、源码1、上下拉回弹,自定义ScrollView2、主activity一、实现效果 二、源码 1、上下拉回弹,自定义ScrollView 上下拉回弹,自定义ScrollView,ReboundScrollView.kt package com.example.myapplication3.myviewimport android.content.Context import

Netty核心技术九--TCP 粘包和拆包及解决方案

1. TCP 粘包和拆包基本介绍 **TCP是面向连接的&#xff0c;面向流的&#xff0c;提供高可靠性服务。收发两端&#xff08;客户端和服务器端&#xff09;都要有一一成对的socket&#xff0c;因此&#xff0c;发送端为了将多个发给接收端的包&#xff0c;更有效的发给对方&#x…

OpenCV 入门教程:图像的基本操作和处理

OpenCV 入门教程&#xff1a;图像的基本操作和处理 导语一、图像的基本操作1.1 获取图像的大小1.2 访问图像的像素1.3 修改图像的像素值 二、图像的基本处理2.1 图像的灰度化2.2 图像的平滑处理2.3 图像的边缘检测 总结 导语 在计算机视觉和图像处理领域&#xff0c;对图像进行…

Spring Boot 中的 CompletableFuture 类是什么,如何使用?

Spring Boot 中的 CompletableFuture 类是什么&#xff0c;如何使用&#xff1f; 介绍 在开发企业级应用程序时&#xff0c;我们经常需要异步执行任务。异步执行任务可以提高应用程序的性能和响应能力。在 Java 8 中&#xff0c;引入了 CompletableFuture 类&#xff0c;它提…