深入理解 Java 动态代理:JDK 动态代理与 CGLIB 全解析

在 Java 开发中,动态代理是实现 AOP(面向切面编程)、事务管理、日志增强等核心功能的基石,也是面试高频考点。它能在不修改原始类代码的前提下,为目标对象动态添加额外功能,完美遵循开闭原则。

Java 中主流的动态代理实现有两种:

  1. JDK 动态代理:Java 原生提供,基于接口实现

  2. CGLIB 动态代理:第三方库,基于继承实现

本文将从原理、代码实战、核心区别、应用场景四个维度,彻底讲透两种动态代理。

一、代理模式基础

动态代理是代理模式的动态实现,先回顾代理模式的核心:

  • 目标对象:实际执行业务逻辑的对象(被代理对象)

  • 代理对象:拦截目标对象的方法调用,添加额外功能

  • 核心作用:方法增强(日志、事务、权限校验等)、方法拦截

静态代理需要手动为每个目标类编写代理类,代码冗余且难以维护;动态代理在程序运行时,通过反射/字节码技术自动生成代理类,无需手动编写,灵活性极高。

二、JDK 动态代理(原生、基于接口)

1. 核心原理

JDK 动态代理是 Java 原生 API(java.lang.reflect 包),必须基于接口实现:

  1. 目标类必须实现至少一个接口

  2. 代理类和目标类实现同一个接口

  3. 通过 InvocationHandler 接口拦截方法调用,通过反射执行目标方法

  4. 运行时动态生成代理类字节码,无需手动创建

2. 核心 API

  • InvocationHandler:调用处理器,重写 invoke() 方法实现方法增强

  • Proxy:工具类,通过 newProxyInstance() 方法生成代理对象

3. 代码实战

步骤1:定义业务接口(JDK 代理必须)


// 用户业务接口
public interface UserService {
    // 目标方法:添加用户
    void addUser(String username);
}

步骤2:实现目标类


// 目标类:实现接口
public class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("执行核心业务:添加用户 -> " + username);
    }
}

步骤3:自定义 InvocationHandler(增强逻辑)


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

// 调用处理器:实现方法增强
public class CustomInvocationHandler implements InvocationHandler {
    // 目标对象(被代理对象)
    private final Object target;

    // 构造方法注入目标对象
    public CustomInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * 拦截目标对象的所有方法调用
     * @param proxy 代理对象
     * @param method 被调用的目标方法
     * @param args 方法参数
     * @return 方法返回值
     * @throws Throwable 异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 【前置增强】:方法执行前添加逻辑
        System.out.println("===== 前置增强:日志记录开始 =====");
        System.out.println("调用方法:" + method.getName());

        // 执行目标方法(反射调用)
        Object result = method.invoke(target, args);

        // 【后置增强】:方法执行后添加逻辑
        System.out.println("===== 后置增强:日志记录结束 =====");

        return result;
    }
}

步骤4:生成代理对象并测试


import java.lang.reflect.Proxy;

public class JdkProxyTest {
    public static void main(String[] args) {
        // 1. 创建目标对象
        UserService target = new UserServiceImpl();

        // 2. 创建调用处理器
        CustomInvocationHandler handler = new CustomInvocationHandler(target);

        // 3. 生成代理对象(核心方法)
        UserService proxy = (UserService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),  // 目标类类加载器
                target.getClass().getInterfaces(),   // 目标类实现的接口
                handler                               // 调用处理器
        );

        // 4. 调用代理对象的方法(触发增强逻辑)
        proxy.addUser("张三");
    }
}

输出结果


===== 前置增强:日志记录开始 =====
调用方法:addUser
执行核心业务:添加用户 -> 张三
===== 后置增强:日志记录结束 =====

4. 优缺点

✅ 优点:

  • Java 原生支持,无需引入第三方依赖

  • 无额外性能开销,稳定性高

❌ 缺点:

  • 强制要求目标类实现接口,无接口的类无法代理

  • 只能代理接口中定义的方法,无法代理类的私有/静态方法

三、CGLIB 动态代理(第三方、基于继承)

1. 核心原理

CGLIB(Code Generation Library)是第三方字节码生成库,无需目标类实现接口:

  1. 底层通过继承目标类生成代理子类

  2. 重写目标类的非 final 方法,实现方法拦截

  3. 通过 MethodInterceptor 接口拦截方法调用

  4. 运行时动态生成字节码,效率高于 JDK 动态代理(高版本)

注意:Spring 框架已内置 CGLIB,无需单独引入;普通项目需引入依赖。

2. 核心 API

  • MethodInterceptor:方法拦截器,重写 intercept() 方法实现增强

  • Enhancer:增强器,用于生成代理子类

3. 代码实战

步骤1:引入依赖(Maven)


<!-- CGLIB 依赖 -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

步骤2:定义目标类(无需实现接口)


// 普通类:无接口,CGLIB 可直接代理
public class OrderService {
    // 目标方法:创建订单
    public void createOrder(String orderNo) {
        System.out.println("执行核心业务:创建订单 -> " + orderNo);
    }
}

步骤3:自定义 MethodInterceptor(拦截增强)


import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

// 方法拦截器:CGLIB 核心增强类
public class CustomMethodInterceptor implements MethodInterceptor {
    /**
     * 拦截目标类的所有方法调用
     * @param obj 代理对象(子类)
     * @param method 被调用的目标方法
     * @param args 方法参数
     * @param methodProxy 方法代理(用于快速调用父类方法)
     * @return 方法返回值
     * @throws Throwable 异常
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // 【前置增强】
        System.out.println("===== CGLIB 前置增强:权限校验 =====");

        // 执行目标方法(调用父类方法,比反射更快)
        Object result = methodProxy.invokeSuper(obj, args);

        // 【后置增强】
        System.out.println("===== CGLIB 后置增强:事务提交 =====");

        return result;
    }
}

步骤4:生成代理对象并测试


import net.sf.cglib.proxy.Enhancer;

public class CglibProxyTest {
    public static void main(String[] args) {
        // 1. 创建目标对象
        OrderService target = new OrderService();

        // 2. 创建拦截器
        CustomMethodInterceptor interceptor = new CustomMethodInterceptor();

        // 3. 创建 Enhancer 增强器(CGLIB 核心)
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass()); // 设置父类(目标类)
        enhancer.setCallback(interceptor);         // 设置拦截器

        // 4. 生成代理对象(子类)
        OrderService proxy = (OrderService) enhancer.create();

        // 5. 调用代理方法
        proxy.createOrder("ORDER_001");
    }
}

输出结果


===== CGLIB 前置增强:权限校验 =====
执行核心业务:创建订单 -> ORDER_001
===== CGLIB 后置增强:事务提交 =====

4. 优缺点

✅ 优点:

  • 无需目标类实现接口,适用范围更广

  • 可代理任意普通类,性能优于 JDK 动态代理

  • 能代理类的公有方法(非 final)

❌ 缺点:

  • 需引入第三方依赖(Spring 已内置)

  • 无法代理 final 类、final 方法、static 方法(继承限制)

  • 会生成目标类的子类,可能影响类结构

四、JDK 动态代理 VS CGLIB 动态代理(核心对比)

维度 JDK 动态代理 CGLIB 动态代理
实现原理 基于接口,实现同一接口 基于继承,生成子类
目标类要求 必须实现至少一个接口 无需实现接口,普通类即可
代理限制 只能代理接口方法 不能代理 final 类/方法
依赖 Java 原生,无第三方依赖 需引入 CGLIB 依赖
性能 较低(反射调用) 较高(字节码生成,快速调用)
Spring 默认选择 Spring Boot 2.0+ 新版本默认统一使用 CGLIB,无论目标类是否实现接口 目标类无接口时使用

五、动态代理的应用场景

动态代理是 Java 框架的核心技术,实际开发中无处不在:

  1. Spring AOP:Spring 事务、日志、权限校验底层均用动态代理

  2. MyBatis Mapper:MyBatis 接口无需实现类,通过 JDK 动态代理生成代理对象执行 SQL

  3. RPC 框架:Dubbo 等 RPC 框架通过动态代理屏蔽远程调用细节

  4. 全局日志/事务:不修改业务代码,统一添加日志、事务管理

六、总结

  1. JDK 动态代理:基于接口,原生支持,适合有接口的类代理,简单稳定

  2. CGLIB 动态代理:基于继承,无接口要求,性能更高,适合无接口的类代理

  3. Spring Boot 2.0+ 新版本中,默认统一使用 CGLIB,不再根据目标类是否实现接口选择,仅当手动配置时可切换为 JDK 动态代理

  4. 核心思想:不修改原始代码,动态增强方法功能,是 AOP 的核心实现


关键点回顾

  1. JDK动态代理:必须基于接口,原生实现,通过InvocationHandler增强

  2. CGLIB动态代理:基于继承,无需接口,通过MethodInterceptor拦截

  3. 核心区别:接口 vs 继承,Spring Boot 2.0+ 新版本默认统一使用 CGLIB

  4. 核心价值:无侵入式方法增强,支撑 AOP、事务等核心功能

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容