设计模式之代理模式的懂静态代理和动态代理

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 设计模式之代理模式的懂静态代理和动态代理

1 概述

代理模式(Proxy Pattern)是一种结构型设计模式,它的概念很简单,它通过创建一个代理对象来控制对原始对象的访问。代理模式主要涉及两个角色:代理角色和真实角色。代理类负责代理真实类,为真实类提供控制访问的功能,真实类则完成具体的业务逻辑。这样,当我们不方便或者不能直接访问真实对象时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。

6373fd699d7e5102e972bb7fa29e3fc4.png

小提示:文中所述真实类,原始类,目标类,是一个意思,都是指被代理角色。

1.1 如何实现?

代理模式的原理是将真实类的功能封装在代理类中,最基本的方式就是创建一个代理类,代理类实现和真实类相同的接口,并且在代理类中引用真实类的实例。这样就可以在不修改真实类代码的情况下,通过代理类来控制对真实类的访问。代理类还可以在调用真实类的方法前后添加一些额外的逻辑,来实现对真实类的访问控制、缓存、日志记录等功能。


实现代理模式有两种方案:静态代理和动态代理。静态代理是指代理类在编译期就已经确定,即需要事先手动编写一个代理类。动态代理则是在运行时动态生成代理类。


动态代理方案有两种实现:其一,通过Java本身自带 java.lang.reflect.Proxy 类来实现动态代理功能。其二,通过额外引入一个开源的高性能代码生成包CGlib来动态生成代理类。二者底层实现上都应用了反射和操作字节码技术。


JDK动态代理是基于接口实现的代理,只能代理实现了接口的类。


CGlib方式是基于继承实现的代理,它不是指真实类需要继承某个父类,而是生成的代理类作为真实类的子类去代理父类,即代理类继承自真实类。这种方式不需实现接口,可以作为JDK代理方式的补充方案。

2879c65536ef4a6c497ab7eda55fcffa.png

1.2 优点

  • 代理对象可以隐藏原始对象的实现细节,使得客户端无需了解原始对象的具体实现。
  • 代理对象可以在原始对象的基础上添加额外的功能,例如缓存、安全验证等。
  • 代理对象可以控制对原始对象的访问,保护原始对象不被非法访问。
  • 代理对象可以在客户端和原始对象之间起到中介作用,使得客户端与原始对象之间的耦合度降低。

1.3 缺点

  • 引入代理类会增加系统的复杂性,增加了学习和理解的成本。
  • 由于增加了代理层,导致请求处理速度变慢。

1.4 适用场景

代理模式主要适用于需要控制、增强或隐藏对象访问的场景。适合代理访问的具体场景如下:


远程代理:当客户端需要访问远程对象(位于不同地址空间或网络中)时,可以使用代理模式来隐藏底层网络通信的复杂性,代理对象负责处理网络通信,并将结果返回给客户端。

虚拟代理:当创建和初始化对象的开销很大时,可以使用代理模式延迟对象的实例化,只有在需要真正使用对象时才进行初始化。这样可以提高系统的性能和资源利用率。

安全代理:代理模式可以用于控制对敏感资源的访问,代理对象可以验证客户端的权限或者在访问资源前执行一些安全检查,从而保护真实对象。

日志记录代理:通过代理模式,我们可以在真实对象的方法执行前后进行日志记录,以实现日志记录、调试和性能监测等功能。

延迟加载代理:当需要使用的对象具有较大的开销时,可以使用代理模式来实现延迟加载,只有在真正需要时才加载对象,以节省资源和提高响应速度。

缓存代理:代理模式可以用于实现对象的缓存,当客户端请求某个对象时,代理对象先检查缓存中是否存在该对象,如果存在则直接返回,否则创建新对象并缓存起来,从而提高系统性能。

2 静态代理实现

实现方式比较简单,很容易理解,直接上代码

NO.1 抽象接口:定义视频播放器接口Player

public interface Player {
    void loadVideo(String filename);
    void playVideo(String filename);
}

NO.2 真实类:定义接口实现类VPlayer

public class VPlayer implements Player {
    @Override
    public void loadVideo(String filename) {
        System.out.println("加载MP4视频文件:"+filename);
    }
    @Override
    public void playVideo(String filename) {
        System.out.println("播放MP4视频:"+filename);
    }
}

NO.3 代理类:定义代理类VPlayerProxy,实现同样的接口

public class VPlayerProxy implements Player {
    private Player player;
    public VPlayerProxy(Player player) {
        this.player = player;
    }
    @Override
    public void loadVideo(String filename) {
        player.loadVideo(filename);
    }
    @Override
    public void playVideo(String filename) {
        player.playVideo(filename);
    }
}

NO.4 客户端调用

public class Client1 {
    public static void main(String[] args) {
        //直连方式
        Player vplay=new VPlayer();
        vplay.playVideo("aaa.mp4");
        System.out.println();
        //代理方式
        Player proxy=new VPlayerProxy(vplay);
        proxy.loadVideo("aaa.mp4");
        proxy.playVideo("aaa.mp4");
    }
}

3 JDK 动态代理实现

JDK动态代理是Java标准库中提供的一种代理方式,它可以在运行时动态生成一个代理对象,代理对象实现和原始类一样的接口,并将方法调用转发给被代理对象,同时还可以在方法调用前后执行额外的增强处理。JDK动态代理通过反射机制实现代理功能,其原理分为以下几个步骤:


1.创建实现InvocationHandler接口的代理类工厂:在调用Proxy类的静态方法newProxyInstance时,会动态生成一个代理类。该代理类实现了目标接口,并且持有一个InvocationHandler类型的引用。

2.InvocationHandler接口:InvocationHandler是一个接口,它只有一个方法invoke。在代理对象的方法被调用时,JVM会自动调用代理类的invoke方法,并将被调用的方法名、参数等信息传递给该方法。

3.调用代理对象的方法:当代理对象的方法被调用时,JVM会自动调用代理类的invoke方法。在invoke方法中,可以根据需要执行各种逻辑,比如添加日志、性能统计、事务管理等。

4.invoke方法调用:在invoke方法中,通过反射机制调用目标对象的方法,并返回方法的返回值。在调用目标对象的方法前后,可以执行额外的逻辑。

通用实现代码

public class JDKProxyFactory implements InvocationHandler {
    //需要被代理的对象
    private Object object;
    public JDKProxyFactory(Object object) {
        this.object = object;
    }
    @SuppressWarnings("unchecked")
    public <T> T getProxy(){
        return (T) Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),//当前线程的上下文ClassLoader
                object.getClass().getInterfaces(), //代理需要实现的接口
                this); // 处理器自身
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
    //进行方法匹配,调用对应方法名的方法
        if ("loadVideo".equals(method.getName())) {
            result=method.invoke(object, args);
        }
        if ("playVideo".equals(method.getName())) {
            System.out.println("前置增强");
            result=method.invoke(object, args);
            System.out.println("后置增强");
        }
        return result;
    }
}

客户端调用

public class Client2 {
    public static void main(String[] args) {
        Player player=new VPlayer();
        Player proxy=new JDKProxyFactory(player).getProxy();
        proxy.loadVideo("aaa.mp4");
        proxy.playVideo("aaa.mp4");
/*      或者
        Player p=new VPlayer();
        Player o = (Player) Proxy.newProxyInstance(
                p.getClass().getClassLoader(),
                p.getClass().getInterfaces(),
                new VPlayerProxyFactory(p)
        );
        o.loadVideo("aaaa.mp4");
*/
    }
}

4 CGlib 动态代理实现

CGLIB(Code Generation Library)是一个基于ASM(Java字节码操作框架)实现的代码生成库,它可以在运行时动态生成目标类的子类作为代理类,并覆盖其中的方法来实现代理功能。与Java自带的JDK动态代理不同,CGlib动态代理可以代理没有实现接口的类。其原理分为以下几个步骤:


1.创建Enhancer对象:Enhancer是CGLIB库中用于动态生成子类的主要类。通过创建Enhancer对象并设置需要代理的目标类、拦截器等参数,可以生成一个代理类。

2.设置回调拦截器:在生成代理类时,需要指定拦截器。拦截器是实现代理逻辑的关键,它会在代理类的方法被调用时拦截调用,并执行相应的逻辑。在CGLIB中,拦截器需要实现MethodInterceptor接口。

3.创建代理对象:通过调用Enhancer对象的create方法,可以生成一个代理对象。代理对象会继承目标类的方法,并且在调用代理对象的方法时会先调用拦截器的intercept方法,再执行目标方法。

4.调用代理对象:通过调用代理对象的方法,会触发拦截器的intercept方法。在intercept方法中,可以根据需要执行各种逻辑,比如添加日志、性能统计、事务管理等。

单独定义一个没有接口的真实类APlayer

//音频播放器
public class APlayer {
    public void loadAudio(String filename) {
        System.out.println("加载MP3音频文件:"+filename);
    }
    public void playAudio(String filename) {
        System.out.println("播放MP3:"+filename);
    }
}

通用实现代码

public class CglibProxyFactory implements MethodInterceptor {
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class<T> clazz) {
        Enhancer en = new Enhancer();
        //设置代理的父类
        en.setSuperclass(clazz);
        //设置方法回调
        en.setCallback(this);
        //创建代理实例
        return (T)en.create();
    }
    @Override
    //参数中的object是目标对象,method和args是目标对象的方法和参数,methodProxy是方法代理
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object result = null;
        if ("loadAudio".equals(method.getName())) {
            //通过继承的方法实现代理,因此这里调用invokeSuper
            result = methodProxy.invokeSuper(object, args);
        }
        if ("playAudio".equals(method.getName())) {
            result = methodProxy.invokeSuper(object, args);
        }
        return result;
    }
}

客户端调用

public class Client3 {
    public static void main(String[] args) {
        APlayer aplayer=new APlayer();
        APlayer proxy = new CglibProxyFactory().getProxy(aplayer.getClass());
        //验证代理类的父类
        System.out.println("代理类的父类:"+proxy.getClass().getSuperclass().getSimpleName());
        System.out.println();
        proxy.loadAudio("荷塘月色.mp3");
        proxy.playAudio("荷塘月色.mp3");
    }
}

5 总结

代理模式可以在不修改真实类代码的情况下,实现对真实类的访问控制、性能优化等功能。Java 中有两种实现代理模式的方法:静态代理和动态代理。静态代理需要在编译之前手动编写代理类,而动态代理可以在运行时动态生成代理类。


其中动态代理又分为JDK动态代理和CGlib动态代理,一般情况下我们使用前者,当代理类没有接口时选用后者作为前者的补充方案。代理模式的优点包括降低系统耦合度、增强代码可扩展性、提高系统安全性和简化接口,但它也会增加系统的复杂性和可能影响性能。在需要控制访问、远程代理、功能增强等场景下,代理模式是一个很好的选择。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
3天前
|
设计模式 缓存 Java
「全网最细 + 实战源码案例」设计模式——代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,通过代理对象控制对目标对象的访问并添加额外功能。它分为静态代理和动态代理,后者包括JDK动态代理和CGLIB动态代理。JDK动态代理基于接口反射生成代理类,而CGLIB通过继承目标类生成子类。代理模式适用于延迟初始化、访问控制、远程服务、日志记录和缓存等场景,优点是职责分离、符合开闭原则和提高安全性,缺点是增加系统复杂性。
54 25
|
2月前
|
设计模式 前端开发 数据安全/隐私保护
前端必须掌握的设计模式——代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,通过引入“替身”对象来间接访问真实对象,从而解耦并提升性能和安全性。例如,知名艺人复出后,经纪人作为代理筛选商单,确保只处理符合团队利益的请求。代码实现中,定义接口`IService`,艺人和经纪人都实现该接口,经纪人在访问时进行过滤和转发。代理模式常用于权限控制、性能优化等场景,如前端中的Tree-shaking和ES6的Proxy构造方法。
前端必须掌握的设计模式——代理模式
|
4月前
|
设计模式 网络协议 Java
06.动态代理设计模式
本文详细介绍了动态代理设计模式,包括其必要性、概念、实现方式及案例分析。动态代理允许在运行时动态创建代理对象,增强代码复用性和灵活性,减少类膨胀。文章通过对比静态代理,深入解析了动态代理的实现机制,如基于接口和类的动态代理,以及其在Retrofit中的应用。同时,讨论了动态代理的优势和潜在问题,如性能开销和调试难度。最后,提供了丰富的学习资源链接,帮助读者进一步理解和掌握动态代理。
34 1
|
4月前
|
设计模式 网络协议 Java
05.静态代理设计模式
《静态代理设计模式》详细介绍了静态代理的基本概念、原理与实现、应用场景及优缺点。主要内容包括静态代理的由来、定义、使用场景、实现方式、结构图与时序图,以及其在降低耦合、保护对象权限等方面的优势。同时,文章也指出了静态代理的局限性,如缺乏灵活性、难以复用、难以动态添加功能等,并介绍了动态代理如何弥补这些不足。最后,通过多个实际案例和代码示例,帮助读者更好地理解和应用静态代理模式。
54 4
|
5月前
|
设计模式 缓存 安全
设计模式——代理模式
静态代理、JDK动态代理、Cglib 代理
设计模式——代理模式
|
5月前
|
设计模式 Java Spring
spring源码设计模式分析-代理设计模式(二)
spring源码设计模式分析-代理设计模式(二)
|
5月前
|
设计模式 Java 数据安全/隐私保护
Java设计模式-代理模式(7)
Java设计模式-代理模式(7)
|
6月前
|
设计模式 缓存 Java
【十一】设计模式~~~结构型模式~~~代理模式(Java)
文章详细介绍了代理模式(Proxy Pattern),这是一种对象结构型模式,用于给对象提供一个代理以控制对它的访问。文中阐述了代理模式的动机、定义、结构、优点、缺点和适用环境,并探讨了远程代理、虚拟代理、保护代理等不同代理形式。通过一个商务信息查询系统的实例,展示了如何使用代理模式来增加身份验证和日志记录功能,同时保持客户端代码的无差别对待。此外,还讨论了代理模式在分布式技术和Spring AOP中的应用,以及动态代理的概念。
【十一】设计模式~~~结构型模式~~~代理模式(Java)
|
2月前
|
设计模式 前端开发 搜索推荐
前端必须掌握的设计模式——模板模式
模板模式(Template Pattern)是一种行为型设计模式,父类定义固定流程和步骤顺序,子类通过继承并重写特定方法实现具体步骤。适用于具有固定结构或流程的场景,如组装汽车、包装礼物等。举例来说,公司年会节目征集时,蜘蛛侠定义了歌曲的四个步骤:前奏、主歌、副歌、结尾。金刚狼和绿巨人根据此模板设计各自的表演内容。通过抽象类定义通用逻辑,子类实现个性化行为,从而减少重复代码。模板模式还支持钩子方法,允许跳过某些步骤,增加灵活性。
109 11
|
3月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式