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

在这里插入图片描述

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

在这里插入图片描述



一,初识代理模式

什么是代理设计模式?

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

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

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


二,静态代理模式

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采用的方式


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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力学习.java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值