漫谈代理模式,静态代理到 JDK 和 CGLIB 动态代理

本文主要串联动态代理中的知识点,并梳理一下代理模式的发展史,进而映出其如何迭代实现现在开发中常用的代理模式,如Spring AOP、MyBatis等框架。

思路

知识点串联

这里面主要涉及到了这几个技术:字节码操作、类加载、反射、动态代理。

JVM 基于类加载器方法区 支撑实现了java.lang.reflect反射API

通过使用反射 ,对设计原则代理模式 的实现静态代理 进行改进,基于反射实现了动态代理

  • JVM 角色 :JVM 的类加载器和方法区(元空间)为 Java 反射提供了必要的运行时数据基础 ,但反射本身是通过 java.lang.reflect 这套 API 实现的。
  • 反射与动态代理JDK 动态代理 的实现核心依赖于 Java 反射机制 (特别是 Proxy 类的生成和 Method.invoke() 的调用)。反射使得在运行时动态创建代理类和动态调用目标方法成为可能,从而克服了静态代理的局限性,实现了更灵活的代理模式。
  • 改进关系 :可以说,利用反射技术 ,对传统的静态代理模式进行了改进,实现了更加灵活和通用的动态代理

概要

本质上动态代理只是对静态代理中,代理对象的生成从编写固定的代理类代码实现到采取的动态生成代理对象的改进。从而实现一个类似的代理对象,来对目标对象包装进行内部调用。

根据实现动态代理的方式不同,主要分为基于接口实现 的JDK动态代理和基于子类实现的CGLIB。

JDK 动态代理,通过代理对象实现目标对象的相同接口,用多态实现代理,对目标对象进行包裹然后内部调用,通过接口可以保证代理对象和目标对象的类同;

CGLIB则是通过继承代理,通过字节码生成代理子类对象包装目标对象,重写和super调用父类原方法进行代理)

代理模式的由来,有什么用?

在我们平常的代码中,会有一些重复的功能(比如日志记录、事务管理、权限验证等)。在传统的编程方式中,如果我们要为一个类添加这些功能,通常有两种选择:一种是在类代码中直接添加before()after()这样的函数调用,并在这些函数中实现相应的逻辑;另一种是通过组合方式引入功能类,将这些功能作为参数传递给目标类。但无论采用哪种方式,都会导致代码冗余或耦合度增加的问题。

如果在类中直接实现这些功能,会导致代码冗余。例如,当多个类都需要添加日志功能时,每个类都需要重复编写相同的日志记录代码,这不仅违反了"不要重复自己"(DRY)原则,还增加了维护成本。如果日志格式需要变更,就需要修改所有包含日志代码的类,容易出错且效率低下。

如果通过组合方式引入功能类,会导致耦合入侵。例如,为实现事务管理功能,目标类需要持有事务管理器的引用,并在方法执行前后调用事务管理器的开始和提交方法。这种做法虽然避免了代码重复,但目标类却被迫知晓事务管理的实现细节,破坏了单一职责原则,增加了类之间的依赖关系。

代理模式正是为了解决这一矛盾而诞生的设计模式。它提供了一种既不造成代码冗余,又不引入耦合的方式,实现横切关注点的集中管理。通过代理对象,可以在不修改目标类代码的情况下,为其添加额外功能,使目标类专注于核心业务逻辑。

代理模式的实现

在开始讨论代理模式的具体实现之前,我们先通过一幅图片来简单抽象地描述代理模式中各个部分:

静态代理

静态代理是最基础的代理模式实现方式,它通过预先编写代理类来实现代理功能。静态代理的实现需要三个关键组件:

1. 接口定义:定义客户端与目标对象交互的契约。

java 复制代码
public interface Service {
    void execute();
}

2. 被代理类实现:实现接口,提供核心业务逻辑。

java 复制代码
public class RealService implements Service {
    @Override
    public void execute() {
        System.out.println("执行核心业务逻辑");
    }
}

3. 代理类实现:实现同一接口,内部持有目标对象引用,并在方法调用前后添加额外逻辑。

java 复制代码
public class ProxyService implements Service {
    private Service realService;

    public ProxyService(Service realService) {
        this.realService = realService;
    }

    @Override
    public void execute() {
        before();  // 添加额外逻辑(如日志记录)
        realService.execute();  // 调用目标对象方法
        after();   // 添加额外逻辑(如事务提交)
    }

    private void before() {
        System.out.println("代理方法前的额外操作");
    }

    private void after() {
        System.out.println("代理方法后的额外操作");
    }
}

小结

静态代理的核心优势在于简单直观。它通过接口保证类型兼容性,使客户端无需修改即可使用代理对象。代理类通过持有目标对象的引用,可以在调用目标方法前后添加额外功能。

但静态代理也存在明显的局限性

  • 代码冗余:每个需要代理的接口都需要单独编写代理类,当系统中存在大量接口时,代码量会急剧增加。
  • 维护困难:如果接口方法发生变化,所有相关的代理类都需要同步修改,容易遗漏或出错。
  • 扩展性差:无法在运行时动态决定是否代理某个对象,也无法灵活地添加或移除代理功能。

动态代理

动态代理是对静态代理的革命性改进,它通过运行时自动生成代理类的方式,解决了静态代理的上述局限性。动态代理主要有两种实现方式:

多态实现(JDK 动态代理)和子类实现(CGLIB/Byte Buddy)这两种技术路径,其核心目标是为了解决动态代理模式中的一个根本性问题类型兼容性 (Type Compatibility)无缝替换 (Seamless Substitution)

JDK动态代理------基于接口实现

JDK动态代理基于接口实现,利用反射机制在运行时动态生成代理类。其核心组件包括:

1. 接口定义:与静态代理相同,定义客户端与目标对象交互的契约。

java 复制代码
public interface Service {
    void execute();
}

2. 被代理类实现:实现接口,提供核心业务逻辑。

java 复制代码
public class RealService implements Service {
    @Override
    public void execute() {
        System.out.println("执行核心业务逻辑");
    }
}

3. 代理处理器 :实现InvocationHandler接口,定义代理逻辑。

java 复制代码
public class ServiceProxy implementsInvocationHandler {
    private Object target;

    public ServiceProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();  // 添加额外逻辑(如日志记录)
        Object result = method.invoke(target, args);  // 反射调用目标方法
        after();   // 添加额外逻辑(如事务提交)
        return result;
    }

    private void before() {
        System.out.println("代理方法前的额外操作");
    }

    private void after() {
        System.out.println("代理方法后的额外操作");
    }

    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new ServiceProxy(target)
        );
    }
}

小结

JDK动态代理的核心原理

  • 在运行时,通过Proxy.newProxyInstance()方法动态生成代理类。
  • 生成的代理类实现了目标对象的所有接口。
  • 代理对象通过InvocationHandler接口的invoke()方法实现对目标方法的拦截和增强。

JDK动态代理的优势

  • 简洁性 :只需编写一个InvocationHandler实现类,即可代理所有实现特定接口的类。
  • 类型安全:通过接口保证类型兼容性,确保代理对象可以无缝替换目标对象。
  • 支持多接口:可以代理实现多个接口的目标对象。

JDK动态代理的局限性

  • 依赖接口:必须要有接口才能使用JDK动态代理,无法代理没有接口的普通类。
  • 性能开销:反射调用比直接方法调用有更高的性能开销。
  • 功能受限 :无法代理final方法。

CGLIB动态代理------基于继承实现

CGLIB(Code Generation Library)是一种基于继承的动态代理技术,它直接通过字节码操作生成目标类的子类实现代理功能。其核心原理如下:

1. 目标类定义:普通Java类,无需实现特定接口。

java 复制代码
public class RealService {
    public void execute() {
        System.out.println("执行核心业务逻辑");
    }
}

2. 方法拦截器 :实现MethodInterceptor接口,定义代理逻辑。

java 复制代码
public class ServiceInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        before();  // 添加额外逻辑(如日志记录)
        Object result = methodProxy.invokeSuper(proxy, args);  // 调用父类方法
        after();   // 添加额外逻辑(如事务提交)
        return result;
    }

    private void before() {
        System.out.println("代理方法前的额外操作");
    }

    private void after() {
        System.out.println("代理方法后的额外操作");
    }
}

3. 代理对象创建 :使用Enhancer类动态生成代理对象。

java 复制代码
public static RealService getProxy() {
    Enhancer enhancer = new Enhancer();
    enhancer.set Superclass(RealService.class);
    enhancer.setCallback(new ServiceInterceptor());
    return (RealService) enhancer.create();
}

小结

CGLIB动态代理的核心原理

  • 在运行时,通过字节码操作技术(基于ASM框架)动态生成目标类的子类。
  • 生成的子类重写目标类的所有非final方法。
  • 在重写的方法中,插入对拦截器的调用,实现对目标方法的拦截和增强。

CGLIB动态代理的优势

  • 无接口限制:可以代理没有实现接口的普通类。
  • 性能更好:基于字节码生成的代理,性能优于JDK的反射机制。
  • 灵活性更高:可以代理类的方法,而不仅仅是接口方法。

CGLIB动态代理的局限性

  • 无法代理final类或方法 :Java的final关键字限制了类或方法的继承和重写,因此CGLIB无法代理这些内容。
  • 依赖字节码操作:需要引入额外的依赖库(如ASM)。
  • 复杂性更高:实现和理解比JDK动态代理更复杂。

静态代理与动态代理的对比

对比维度 静态代理 JDK动态代理 CGLIB动态代理
生成方式 手动编写代理类 运行时基于接口生成代理类 运行时基于类生成子类
依赖要求 依赖接口或继承 必须依赖接口 可代理普通类,但不能代理final
维护成本 高(接口变更需同步修改所有代理类) 低(只需修改InvocationHandler 低(只需修改拦截器)
扩展性 差(需手动修改代理类) 好(支持多接口,可动态代理) 更好(无接口限制,可动态代理)
性能开销 高(反射调用) 中(字节码生成)
代码量 大(每个接口需一个代理类) 小(一个处理器代理多个接口) 最小(一个拦截器代理多个类)

代理模式的意义

到最后我们再重新从一个整体的视角去对代理模式意义进行一个审视总结:

1. 解耦与复用:通过代理对象,可以将核心业务逻辑与横切关注点(如日志、事务、安全等)分离,避免代码冗余和入侵式修改。例如,一个系统中有多个服务类需要添加日志功能,通过代理模式,只需在代理对象中实现日志逻辑,而无需在每个服务类中重复编写。

2. 灵活性:动态代理支持运行时生成代理对象,可以根据需要动态决定是否代理某个对象,以及代理哪些方法。例如,在Spring框架中,可以通过配置决定哪些Bean需要事务管理,而无需修改这些Bean的代码。

3. 扩展性:动态代理使得系统更容易扩展新功能。例如,当需要为系统添加新的横切关注点(如性能监控)时,只需修改代理逻辑,而无需修改所有目标类。

4. 框架基石:动态代理是许多主流框架的核心技术。例如:

  • Spring AOP:通过动态代理实现面向切面编程,支持声明式事务管理、安全控制等功能。
  • Hibernate:使用动态代理实现延迟加载、属性访问控制等功能。
  • RPC框架:通过动态代理实现远程方法调用,隐藏网络通信细节。

总之,动态代理是代理模式的重要演进。它通过将代理类的生成从编译时转移到运行时,解决了静态代理的维护困难和扩展性差的问题,使代理模式成为解决横切关注点的标准方案。在现代软件开发中,动态代理已成为实现松耦合、高内聚系统架构的关键技术之一。

相关推荐
2401_8315017318 分钟前
Linux之Docker虚拟化技术(一)
java·linux·docker
TPBoreas27 分钟前
架构设计模式七大原则
java·开发语言
自由的疯38 分钟前
Java 实现TXT文件导入功能
java·后端·架构
开开心心就好38 分钟前
PDF转长图工具,一键多页转图片
java·服务器·前端·数据库·人工智能·pdf·推荐算法
现在没有牛仔了41 分钟前
SpringBoot实现操作日志记录完整指南
java·spring boot·后端
小蒜学长1 小时前
基于django的梧桐山水智慧旅游平台设计与开发(代码+数据库+LW)
java·spring boot·后端·python·django·旅游
浮游本尊1 小时前
Java学习第16天 - 分布式事务与数据一致性
java
浮游本尊1 小时前
Java学习第15天 - 服务网关与API管理
java
熙客2 小时前
Java:LinkedList的使用
java·开发语言
blueblood2 小时前
🗄️ JFinal 项目在 IntelliJ IDEA 中的 Modules 配置指南
java·后端