杰瑞科技汇

Java CGLIB动态代理底层实现原理是什么?

这是一个在 Java 开发中非常重要且实用的技术,尤其与 Spring AOP 等框架紧密相关。

Java CGLIB动态代理底层实现原理是什么?-图1
(图片来源网络,侵删)

什么是动态代理?

在深入 CGLIB 之前,我们先理解“动态代理”这个概念。

代理模式 是一种设计模式,它为其他对象提供一种代理以控制对这个对象的访问,就是创建一个“替身”对象,这个替身对象可以在不改变原始对象代码的情况下,为原始对象的功能添加额外的操作(如日志、事务、权限校验等)。

动态代理 是指代理类的字节码在程序运行时动态生成,而不是在编译时就确定,这意味着我们可以在运行时决定为哪个接口或类创建代理,以及代理要执行哪些额外逻辑。

Java 官方提供了两种动态代理机制:

  1. JDK 动态代理:基于接口实现,它只能为实现了接口的类创建代理。
  2. CGLIB 动态代理:基于类继承实现,它可以为目标类创建一个子类,并重写其非 final、非 private 的方法,从而实现代理。

为什么需要 CGLIB?

JDK 动态代理虽然方便,但它有一个致命的缺点:它只能代理接口,如果一个类没有实现任何接口,我们就无法使用 java.lang.reflect.Proxy 为其创建代理。

这时,CGLIB (Code Generation Library) 就派上用场了,CGLIB 是一个强大的、高性能的代码生成库,它可以在运行时动态生成一个类的子类,CGLIB 可以代理那些没有实现任何接口的普通类

核心区别: | 特性 | JDK 动态代理 | CGLIB 动态代理 | | :--- | :--- | :--- | | 实现原理 | 基于接口,实现 InvocationHandler 接口 | 基于继承,生成目标类的子类 | | 代理对象 | 代理的是接口,proxy.getClass().getInterfaces() 能获取到接口 | 代理的是类,proxy.getClass().getSuperclass() 能获取到目标类 | | 目标要求 | 目标类必须至少实现一个接口 | 目标类可以是任意普通类(非 final) | | 性能 | 相对较低 | 相对较高(生成代理类后,方法调用效率高) | | 局限性 | 无法代理 final 类和 final 方法 | 无法代理 final 类和 final 方法 |


CGLIB 的工作原理

CGLIB 的工作流程如下:

  1. 创建 Enhancer 对象Enhancer 是 CGLIB 的核心类,用于生成代理类。
  2. 设置父类:通过 setSuperclass() 方法,指定要代理的目标类。
  3. 设置回调方法:通过 setCallback()setCallbacks() 方法,指定一个或多个 MethodInterceptorMethodInterceptor 是一个拦截器接口,它会在目标方法被调用时执行。
  4. 创建代理对象:调用 Enhancercreate() 方法,CGLIB 在底层会:
    • 动态生成一个目标类的子类。
    • 在这个子类中,它会重写所有非 final、非 private 的方法。
    • 当调用这些重写的方法时,会转而调用 MethodInterceptorintercept() 方法。
  5. 方法拦截:当客户端调用代理对象的方法时,实际执行的是 MethodInterceptorintercept() 方法,在这个方法中,我们可以决定:
    • 不执行目标方法:直接返回一个自定义值。
    • 执行目标方法:通过 methodProxy.invokeSuper()method.invoke() 调用原始目标类的方法。
    • 在执行前后添加逻辑:这是最常见的情况,比如添加日志、开启事务等。

CGLIB 实战示例

让我们通过一个具体的例子来理解 CGLIB 的使用。

1 添加 Maven 依赖

确保你的项目中包含 CGLIB 依赖,如果你使用 Spring Boot,它通常已经包含了,否则,可以手动添加:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version> <!-- 使用较新版本 -->
</dependency>

2 创建目标类

我们创建一个没有任何接口的普通类 UserService

// UserService.java
package com.example.demo;
public class UserService {
    public void addUser(String username) {
        System.out.println("正在添加用户: " + username);
        // 模拟添加用户的业务逻辑
    }
    public void deleteUser(Long id) {
        System.out.println("正在删除用户 ID: " + id);
        // 模拟删除用户的业务逻辑
    }
}

3 创建 MethodInterceptor 拦截器

这是 CGLIB 的核心,我们在这里添加额外的逻辑。

// LogInterceptor.java
package com.example.demo;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class LogInterceptor implements MethodInterceptor {
    /**
     * @param o           代理对象,即目标类的子类实例
     * @param method      目标方法对象
     * @param args        目标方法的参数
     * @param methodProxy 代理方法对象,用于调用父类(即目标类)的方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("----- CGLIB 动态代理: 方法 [" + method.getName() + "] 开始执行 -----");
        // 调用目标类(父类)的方法
        // 使用 methodProxy.invokeSuper() 是最高效的方式
        Object result = methodProxy.invokeSuper(o, args);
        // 如果使用 method.invoke(o, args) 也可以,但效率稍低,且可能引发问题(如递归调用)
        // Object result = method.invoke(o, args);
        System.out.println("----- CGLIB 动态代理: 方法 [" + method.getName() + "] 执行结束 -----");
        return result;
    }
}

4 创建并使用代理对象

我们编写一个主类来创建和使用 CGLIB 代理。

// Main.java
package com.example.demo;
import net.sf.cglib.proxy.Enhancer;
public class Main {
    public static void main(String[] args) {
        // 1. 创建 Enhancer 对象
        Enhancer enhancer = new Enhancer();
        // 2. 设置目标类(父类)
        enhancer.setSuperclass(UserService.class);
        // 3. 设置回调(拦截器)
        enhancer.setCallback(new LogInterceptor());
        // 4. 创建代理对象
        UserService userServiceProxy = (UserService) enhancer.create();
        // 5. 通过代理对象调用方法
        System.out.println("代理对象的实际类型: " + userServiceProxy.getClass().getName());
        System.out.println("它的父类是: " + userServiceProxy.getClass().getSuperclass().getName());
        System.out.println("\n--- 调用代理对象的方法 ---");
        userServiceProxy.addUser("张三");
        userServiceProxy.deleteUser(123L);
    }
}

5 运行结果

代理对象的实际类型: com.example.demo.UserService$$EnhancerByCGLIB$$b1f2d8c3
它的父类是: com.example.demo.UserService
--- 调用代理对象的方法 ---
----- CGLIB 动态代理: 方法 [addUser] 开始执行 -----
正在添加用户: 张三
----- CGLIB 动态代理: 方法 [addUser] 执行结束 -----
----- CGLIB 动态代理: 方法 [deleteUser] 开始执行 -----
正在删除用户 ID: 123
----- CGLIB 动态代理: 方法 [deleteUser] 执行结束 -----

从结果可以看出:

  1. 代理对象 userServiceProxy 的类型是 UserService$$EnhancerByCGLIB$$...,这是一个由 CGLIB 动态生成的子类。
  2. 当我们调用 addUser 方法时,并没有直接执行 UserService 中的代码,而是先进入了 LogInterceptorintercept 方法,打印了日志,然后通过 methodProxy.invokeSuper() 执行了原始方法,执行完毕后又打印了日志。

CGLIB 与 Spring AOP

在 Spring 框架中,AOP(面向切面编程)的实现就大量使用了 CGLIB。

  • 默认策略:如果你的目标类实现了至少一个接口,Spring 默认会使用 JDK 动态代理 来创建 AOP 代理。
  • 降级策略:如果你的目标类没有实现任何接口,Spring 会自动降级使用 CGLIB 来创建代理。

你可以通过配置强制 Spring 使用 CGLIB:

@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用 CGLIB
public class AppConfig {
    // ...
}

Spring AOP 的 @Around@Before 等注解,其底层原理就是通过类似 CGLIB 的 MethodInterceptor 来实现的,当你定义一个切面时,Spring 会创建一个代理对象,并在你定义的切点(Pointcut)匹配到方法时,执行你的通知逻辑。


CGLIB 的注意事项

  1. 无法代理 final:因为 CGLIB 使用继承,而 Java 不允许继承 final 类。
  2. 无法代理 final 方法:因为 final 方法不能被子类重写,CGLIB 无法拦截其调用。
  3. 构造函数调用问题:在 CGLIB 代理对象的构造函数中,目标类的构造函数会被调用两次,一次是在生成代理类字节码时,另一次是在创建代理实例时,这可能会对某些依赖构造函数逻辑的代码产生影响。
  4. 性能:虽然 CGLIB 的方法调用效率比 JDK 代理高,但生成代理类的过程本身比 JDK 代理要慢,它不适合为大量短生命周期的对象创建代理。
  5. 类加载器问题:在复杂的应用(如 OSGi)或某些特殊环境中,类加载器可能会导致问题。
特性
核心作用 为没有实现接口的 Java 类提供动态代理能力。
实现原理 通过字节码技术,在运行时动态生成目标类的子类,并重写其方法。
核心组件 Enhancer(生成器)、MethodInterceptor(拦截器)。
关键方法 Enhancer.create()MethodInterceptor.intercept()MethodProxy.invokeSuper()
应用场景 Spring AOP 的默认代理方式之一、Mock 测试(如 Mockito)、RPC 框架等。
优缺点 优点:代理类,适用范围广,性能高。缺点:不能代理 final 类/方法,生成代理慢,构造函数可能被调用两次。

理解 CGLIB 不仅能让你更深入地掌握 Java 动态代理,也能帮助你更好地理解像 Spring 这样的框架是如何工作的。

分享:
扫描分享到社交APP
上一篇
下一篇