首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >探索Java动态代理的奥秘:JDK vs CGLIB

探索Java动态代理的奥秘:JDK vs CGLIB

原创
作者头像
用户7954602
发布于 2025-02-04 17:30:25
发布于 2025-02-04 17:30:25
5240
举报
文章被收录于专栏:JavaJava

一、关于动态代理

1.1 简介

动态代理是一种在 运行时动态生成代理类 的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。

image
image

​

‍

1.2 发展

  1. 早期概念与雏形
  • 1995年 - Java诞生:Java最初发布时,并没有直接支持动态代理的功能。然而,随着面向对象编程(OOP)理念的普及,开发者开始寻求更灵活的方式来处理代码复用和模块化问题。
  • 1998年 - JDK 1.2引入反射API:虽然JDK 1.2并未直接提供动态代理的支持,但它引入了反射API (java.lang.reflect​),这为后续动态代理的实现奠定了基础。反射允许程序在运行时检查或"反射"自身,并操作内部属性和方法。
  1. 正式引入动态代理(JDK 1.3)
  • 2000年 - JDK 1.3:这是Java动态代理的一个重要里程碑。JDK 1.3正式引入了java.lang.reflect.Proxy​类和java.lang.reflect.InvocationHandler​接口,提供了对动态代理的支持。开发者可以通过这些API在运行时创建实现了指定接口的代理对象。
    • Proxy类:用于创建动态代理实例。
    • InvocationHandler接口:所有方法调用都将通过此接口传递给代理对象。

    这一版本使得Java能够在运行时生成代理类,而无需事先定义具体的代理类,极大地增强了开发的灵活性。

  1. 增强与优化(JDK 1.4及之后)
  • 2002年 - JDK 1.4:尽管JDK 1.4并没有对动态代理本身做出重大改进,但这一时期Java整体性能的提升以及对反射机制的进一步优化,间接促进了动态代理的应用与发展。
  • 2004年 - JDK 5 (1.5) :引入了注解(Annotations)、泛型(Generics)等新特性,这些特性虽然不是专门为动态代理设计的,但它们扩展了动态代理的应用场景。例如,利用注解可以更方便地配置代理行为。
  • 2006年 - JDK 6 (1.6) :继续对反射机制进行优化,提升了动态代理的性能。同时,随着Spring框架等流行框架的广泛使用,动态代理技术得到了更广泛的实践和验证。
  1. 现代应用与扩展
  • CGLIB库:由于动态代理只能代理实现了接口的类,对于没有实现接口的类,则需要使用其他技术,如CGLIB(Code Generation Library)。CGLIB通过生成目标类的子类来实现代理功能,适用于那些未实现接口的情况。
  • AspectJ与AOP:随着面向切面编程(AOP)理念的兴起,动态代理成为实现AOP的关键技术之一。AspectJ是一个流行的AOP框架,它利用动态代理实现了诸如日志记录、事务管理等功能。
  • 现代框架中的应用:许多现代Java框架,如Spring和Hibernate,都广泛使用了动态代理技术来实现AOP、依赖注入等功能。这些框架通过动态代理简化了复杂系统的开发,提高了代码的可维护性和灵活性。

‍

Spring AOP 示例:

代码语言:javascript
复制
@Aspect
@Component
public class LogAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void before(JoinPoint joinPoint) {
        System.out.println("前置增强: " + joinPoint.getSignature().getName());
    }
}

‍

阶段

技术

特点

局限性

静态代理时代

手动编写代理类

简单直接

代码冗余,灵活性差

JDK 动态代理时代

基于接口的动态代理

运行时生成代理类,减少代码冗余

只能代理接口,反射性能较低

CGLIB 动态代理时代

基于继承的动态代理

支持代理普通类,性能更优

无法代理 final 类/方法,需第三方库

Spring 时代

动态代理的全面应用

结合 AOP,支持灵活配置

依赖 Spring 框架

动态代理技术的演进,体现了 Java 生态对 代码复用 和 灵活性 的不懈追求。未来,随着技术的进一步发展,动态代理将在更多场景中发挥重要作用。

‍

1.3 特点

优点

缺点

无需手动编写代理类,减少代码冗余

只能代理接口,无法代理类

支持统一处理多个方法的增强逻辑

反射调用性能略低于直接调用

更灵活,适合AOP等横切关注点的实现

‍

1.4 实现方式

Java 提供了两种动态代理方式:

  • JDK 动态代理:基于接口实现(Java 原生支持)
  • CGLIB 动态代理:基于继承实现(需第三方库)

‍

JDK 动态代理与 CGLIB 动态代理的对比

特性

JDK 动态代理

CGLIB 动态代理

代理类型

只能代理实现了接口的类

可以代理普通类(没有接口)

实现方式

使用反射生成代理对象,委托给 InvocationHandler​ 处理方法调用

通过继承目标类生成代理类,重写目标类的方法

性能

因为基于反射,性能较低

性能较好,但会生成字节码

使用限制

只能对接口进行代理

对目标类有继承限制,不能代理 final​ 类及 final​ 方法

框架支持

适用于 Spring AOP、JDBC 动态代理等

Spring AOP 在目标类没有接口时使用 CGLIB

‍

1.5 应用场景

动态代理的应用场景非常广泛,以下是一些常见的用途:

  1. 日志记录:在方法执行前后记录日志。
  2. 事务管理:在方法执行前启动事务,执行后提交事务。
  3. 权限控制:在执行目标方法前进行权限校验。
  4. 延迟加载:代理可以延迟初始化目标对象,直到需要使用时才加载。
  5. 性能监控:监控方法执行的时间或次数。

‍

‍

二、JDK动态代理

2.1 简介

JDK动态代理是Java提供的一种在运行时创建代理对象的机制,它允许开发者在不修改原始类的情况下,为接口实现类添加额外的功能(如日志记录、权限检查等)。

这种机制基于java.lang.reflect.Proxy​类和java.lang.reflect.InvocationHandler​接口。

image
image

​

‍

2.2 特点

  1. 灵活性高:可以在运行时动态创建代理对象,无需为每个具体类编写代理类。
  2. 减少代码量:避免了静态代理中为每个具体类编写代理类的繁琐工作。
  3. 增强功能:可以轻松地为多个不同接口的对象添加统一的横切关注点(如日志记录、事务管理等)。
  4. 仅限接口代理:JDK动态代理只能代理实现了接口的类。如果需要代理没有实现接口的类,则需要使用其他技术,如CGLIB库。

‍

2.3 核心概念

JDK的动态代理需要了解两个类

  1. Proxy类:位于java.lang.reflect​包中,提供了用于创建动态代理类和实例的方法。 Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)​ 方法用于创建代理实例。
  2. InvocationHandler接口:所有方法调用都将通过此接口传递给代理对象。该接口包含一个invoke​方法,代理对象上的任何方法调用都会触发此方法。 invoke()​ 方法接受三个参数:
  • proxy​:代理对象。
  • method​:被调用的方法。
  • args​:方法的参数。

‍

Proxy ( 代理)

image
image

​

‍

InvocationHandler(调用处理程序)

image
image

​

‍

2.4 工作原理

JDK 动态代理通过反射机制生成代理类,代理类实现目标接口并将方法调用转发到 InvocationHandler​ 实现类。具体的工作原理如下:

  1. 创建代理类:Proxy.newProxyInstance()​ 方法会创建一个动态代理类。这个代理类实现了指定的接口,并将所有的方法调用委派给 InvocationHandler​ 接口的 invoke()​ 方法。
  2. 方法拦截:当代理类的方法被调用时,JDK 会通过反射机制调用 InvocationHandler​ 的 invoke()​ 方法。
  3. 调用目标方法:在 invoke()​ 方法中,我们可以加入自定义的逻辑(例如日志记录、权限控制等),然后使用反射调用目标对象的方法。

‍

2.5 实现步骤

以下是使用JDK动态代理的基本步骤:

  1. 定义业务接口:首先定义一个或多个业务接口。
  2. 实现业务逻辑的具体类:编写具体的业务逻辑实现类。
  3. 实现InvocationHandler接口:编写一个类实现InvocationHandler​接口,在invoke​方法中定义代理行为。
  4. 创建代理实例:使用Proxy.newProxyInstance​方法创建代理对象。

‍

2.6 实现示例

示例1—日志功能

以下是一个完整的示例,展示了如何使用JDK动态代理为一个简单的业务接口添加日志记录功能。

代码语言:javascript
复制
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
​
// 1. 定义业务接口
interface UserService {
    void addUser(String name);
}
​
// 2. 实现业务逻辑的具体类
class UserServiceImpl implements UserService {
    @Override
    public void addUser(String name) {
        System.out.println("添加用户:" + name);
    }
}
​
// 3. 实现InvocationHandler接口
class UserServiceInvocationHandler implements InvocationHandler {
    private Object target;
​
    public UserServiceInvocationHandler(Object target) {
        this.target = target;
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置操作:权限检查
        System.out.println("前置操作:权限检查");
​
        // 调用目标对象的方法
        Object result = method.invoke(target, args);
​
        // 后置操作:日志记录
        System.out.println("后置操作:日志记录");
​
        return result;
    }
}
​
public class JdkDynamicProxyExample {
    public static void main(String[] args) {
        // 创建目标对象
        UserService userService = new UserServiceImpl();
​
        // 创建InvocationHandler
        InvocationHandler handler = new UserServiceInvocationHandler(userService);
​
        // 使用Proxy.newProxyInstance方法创建代理对象
        UserService proxy = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                handler);
​
        // 使用代理对象调用方法
        proxy.addUser("张三");
    }
}

关键方法解析

  • Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) :
    • loader​:指定代理类的类加载器。
    • interfaces​:代理类需要实现的接口列表。
    • h​:实现InvocationHandler​接口的对象,负责处理代理对象的所有方法调用。
  • InvocationHandler.invoke(Object proxy, Method method, Object[] args) :
    • proxy​:代理对象本身。
    • method​:被调用的方法对象。
    • args​:方法调用时传入的实际参数列表。

‍

输出结果:

代码语言:javascript
复制
前置操作:权限检查
添加用户:张三
后置操作:日志记录

‍

image
image

​

‍

‍

三、CGLIB 动态代理

3.1 简介

CGLIB(Code Generation Library)是一个强大的字节码生成库,广泛用于在运行时动态生成类的子类作为代理对象。与 JDK 动态代理不同,CGLIB 可以代理普通类,而不仅仅是接口

‍

3.2 发展

CGLIB(Code Generation Library)是一个强大的字节码生成库,最初的目的是通过动态生成字节码来增强 Java 程序的功能,尤其是实现动态代理。CGLIB 作为一种基于继承的动态代理技术,得到了广泛应用,特别是在 Spring 框架中。它的主要发展历程如下:

  1. CGLIB 的初始版本与诞生

CGLIB 最初由 Rod Johnson(Spring 框架的创始人)在 2000 年代初期为解决 Java 中动态代理的不足而开发。它的设计初衷是为了解决 Java 中基于接口的动态代理(如 JDK 动态代理)存在的局限性——JDK 动态代理只能代理实现了接口的类,而 CGLIB 动态代理不依赖接口,可以代理任何类,甚至没有接口的类。

  1. CGLIB 作为 Spring 的核心组件

在 Spring Framework 早期版本中,CGLIB 被用作 AOP(面向切面编程)的核心实现之一。在 Spring AOP 中,CGLIB 被广泛使用来生成代理对象,从而在方法调用时插入切面逻辑,如事务管理、日志记录等。

特别是在 Spring 中的 代理模式 和 AOP(Aspect-Oriented Programming) 的实现,CGLIB 提供了非常有效的方式来拦截对象方法的调用,并进行增强。Spring 将 CGLIB 与 JDK 动态代理结合使用,具体选择哪种代理方式取决于目标对象是否实现了接口。

  1. CGLIB 发展与更新

CGLIB 项目的版本演化主要体现在性能优化、功能增强、兼容性提升以及 bug 修复等方面。CGLIB 在 2000 年代中期到 2010 年前后获得了较为稳定的版本,但主要的变动集中在:

  • 性能优化:为了提高字节码生成的效率,CGLIB 做了许多优化,例如通过缓存机制避免重复生成相同类的字节码。
  • 增强功能:CGLIB 不仅支持方法拦截,还支持更为复杂的字节码修改,比如动态代理字段、构造函数等。
  • 兼容性提升:随着 Java 语言和 JVM(Java Virtual Machine)的演进,CGLIB 不断修复兼容性问题,使其能够支持新的 Java 特性和版本。
  1. CGLIB 的分支与衍生

随着 Spring 的发展,CGLIB 动态代理的使用逐渐被限制在特定场景中。例如,Spring 在后期引入了 Java 的 JDK 动态代理,并根据对象的特性(是否实现了接口)来选择代理方式。

与此同时,随着 Java 代理技术的发展和对性能要求的提升,CGLIB 的某些特性逐渐被更高效的技术所取代,比如 JDK 动态代理、Java Instrumentation API 等。

尽管如此,CGLIB 仍然在某些场景中(如需要代理没有接口的类)保持着其不可替代的地位。它的设计和思想影响了后来的许多字节码增强技术。

  1. CGLIB 与 Spring 4.x 后期的变化

随着 Spring 4.x 版本的推出,Spring 框架逐渐更多地使用了 JDK 动态代理,并且在 Spring AOP 中开始默认使用基于接口的 JDK 动态代理。当目标对象实现了接口时,Spring 会使用 JDK 动态代理;当目标对象没有实现接口时,Spring 才会退回使用 CGLIB 代理。

Spring 对 CGLIB 的使用有所减少,但 CGLIB 仍然作为 Spring AOP 的重要选择之一存在,尤其是在需要代理的类没有接口时,CGLIB 仍然是最佳选择。

  1. CGLIB 的当前状态与替代技术
  • Java 8 的引入:Java 8 引入了默认方法(default methods​),使得许多使用 CGLIB 的场景能够通过接口的方式解决。Java 8 默认方法让 JDK 动态代理的使用场景更加广泛,从而减少了对 CGLIB 的需求。
  • 字节码操作库的进展:现代的字节码增强工具,如 Byte Buddy、ASM、Javassist,也为字节码增强和代理提供了更为灵活和强大的功能。特别是 Byte Buddy,作为一个轻量级的字节码操作库,提供了比 CGLIB 更丰富的功能,并且在许多场景下成为了替代选择。

尽管如此,CGLIB 仍然在一些需要继承的场景中得到使用,特别是在一些成熟的 Java 项目中,它依旧是代理类生成的核心库之一。

  1. CGLIB 目前的状态

CGLIB 的开发社区已经相对活跃度较低,最新的版本更新较为缓慢。目前,CGLIB 更多地被用作一些旧版项目或需要特定字节码增强的场景。在大多数新项目中,CGLIB 的使用被其他字节码增强库(如 Byte Buddy)取代,尤其是在 Spring 5.x 版本中,Spring 不再默认使用 CGLIB,而是更多地倾向于使用 JDK 动态代理。

‍

3.3 特点

优点

  • 代理普通类:CGLIB 可以代理没有实现接口的普通类,这是它与 JDK 动态代理的主要区别。
  • 性能较高:CGLIB 通过生成子类的方式实现代理,避免了 JDK 动态代理的反射调用,性能更高。
  • 方法拦截灵活:通过实现 MethodInterceptor​ 接口,可以灵活地控制方法调用的行为。

局限性

  • 无法代理 final 类或 final 方法:由于 CGLIB 通过生成子类实现代理,因此无法代理 final 类或 final 方法。
  • 需要引入第三方库:CGLIB 是一个第三方库,需要额外引入依赖。
  • 字节码生成可能影响启动性能:在运行时生成字节码可能会对应用的启动性能产生一定影响。

‍

3.4 工作原理

CGLIB通过在运行时动态地创建目标对象的子类来实现代理。这个过程涉及到字节码的操作,CGLIB依赖于ASM这个开源的Java字节码操作框架来修改类的行为。当使用CGLIB创建代理时,它不会像JDK动态代理那样要求被代理类必须实现一个接口,而是直接对类进行扩展。

  • 字节码生成:CGLIB 通过操作字节码在运行时生成目标类的子类,子类重写了父类的方法,从而实现对方法的拦截和增强。
  • 方法拦截:通过实现 MethodInterceptor​ 接口,可以在方法调用前后插入自定义逻辑。

‍

3.5 核心类

  • Enhancer:用于创建代理对象的主要类。通过配置 Enhancer,可以指定目标类、回调方法等。
  • MethodInterceptor:接口,用于定义方法拦截逻辑。实现该接口的类可以在方法调用前后执行自定义操作。
  • Callback:用于指定代理对象的方法调用行为。MethodInterceptor​ 是 Callback​ 的一种实现。
image
image

​

‍

image
image

​

‍

3.6 实现步骤

  1. 定义业务类:首先需要有一个具体的业务类,该类可能包含一些需要被拦截的方法。
  2. 创建拦截器:实现MethodInterceptor​接口,并重写intercept​方法,在这里可以编写自定义逻辑,比如添加前置处理、后置处理等。
  3. 生成代理类:使用Enhancer​类设置父类为业务类,并指定回调拦截器。然后调用create()​方法生成代理实例。

‍

3.7 实现示例

示例1
代码语言:javascript
复制
// 业务类
public class TargetClass {
    public void targetInfo() {
        System.out.println("打印目标类信息");
    }
}
​
// 拦截器
public class MyInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("------插入前置通知代码-------------");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("------插入后置处理代码-------------");
        return result;
    }
}
​
// 测试类
public class CglibProxyTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TargetClass.class);
        enhancer.setCallback(new MyInterceptor());
        TargetClass proxy = (TargetClass) enhancer.create();
        proxy.targetInfo();
    }
}

拦截器 MyInterceptor​

  • 实现接口:实现了MethodInterceptor​接口,该接口要求实现intercept​方法。
  • 参数说明:
    • o​: 代理对象实例。
    • method​: 被拦截的方法对象。
    • objects​: 方法参数数组。
    • methodProxy​: 方法代理对象,用于调用父类的方法。
  • 功能:在intercept​方法中,首先执行前置处理(即打印“插入前置通知代码”),然后通过methodProxy.invokeSuper(o, objects)​调用原始方法,最后执行后置处理(即打印“插入后置处理代码”)。

‍

测试类 CglibProxyTest

  • Enhancer: 这是CGLIB的核心类之一,用于生成代理对象。
    • setSuperclass(TargetClass.class)​: 设置要代理的基类(目标类)。
    • setCallback(new MyInterceptor())​: 设置回调拦截器,这里是之前定义的MyInterceptor​。
    • create()​: 创建代理对象并返回。
  • 测试流程:
    • 使用Enhancer​创建一个TargetClass​的代理对象。
    • 通过代理对象调用targetInfo()​方法。
    • 实际上,这个方法调用会被MyInterceptor.intercept​方法拦截,在执行目标方法前后分别打印出前置和后置处理的信息。

‍

执行结果

当你运行CglibProxyTest​类的main​方法时,程序将输出如下内容:

代码语言:javascript
复制
------插入前置通知代码-------------
打印目标类信息
------插入后置处理代码-------------
image
image

​

‍

通过本文的介绍,我们深入了解了Java动态代理的工作原理、应用场景以及其实现方式。无论是JDK动态代理还是CGLIB,它们都在现代Java开发中扮演着重要角色,特别是在AOP和微服务架构中。

希望这些知识能帮助你在实际项目中灵活运用动态代理技术,提升代码的可维护性和扩展性。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、关于动态代理
    • 1.1 简介
    • 1.2 发展
    • 1.3 特点
    • 1.4 实现方式
    • 1.5 应用场景
  • 二、JDK动态代理
    • 2.1 简介
    • 2.2 特点
    • 2.3 核心概念
      • Proxy ( 代理)
      • InvocationHandler(调用处理程序)
    • 2.4 工作原理
    • 2.5 实现步骤
    • 2.6 实现示例
      • 示例1—日志功能
  • 三、CGLIB 动态代理
    • 3.1 简介
    • 3.2 发展
    • 3.3 特点
      • 优点
      • 局限性
    • 3.4 工作原理
    • 3.5 核心类
    • 3.6 实现步骤
    • 3.7 实现示例
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档