一、为什么需要动态代理?

在上一篇[手写 Spring 事务管理]中,我们在 Service 层手动写了大量事务控制代码:

public void transfer(...) {
    try {
        transactionManager.beginTransaction(); // 开启事务
        // ...业务逻辑...
        transactionManager.commit();           // 提交
    } catch (Exception e) {
        transactionManager.rollback();         // 回滚
    } finally {
        transactionManager.release();          // 释放
    }
}

这套 try-catch 结构和业务代码完全没有关系,但每个 Service 方法都要写一遍,非常冗余。

有没有办法在不修改原有业务代码的前提下,自动给方法加上这些额外逻辑?

答案就是动态代理。


二、什么是动态代理?

先用一个现实例子理解:

早期,电脑厂家自己负责生产、销售和售后三件事,什么都要亲力亲为。后来生意越做越大,厂家决定只专注生产,把销售和售后交给经销商来做。消费者不再直接找厂家,而是找经销商——经销商在厂家的基础上,额外提供了销售和售后服务。

这就是代理的本质:在不改变原有对象的前提下,通过一个代理对象对原有功能进行增强。

动态代理的"动态"体现在:代理对象不是手写的,而是在程序运行期间由 JVM 动态生成的

Java 中实现动态代理主要有两种方式:基于接口的 JDK 动态代理 和基于子类的 CGLIB 动态代理


三、JDK 动态代理

3.1 使用前提

被代理的类必须实现至少一个接口,JDK 动态代理通过接口来生成代理对象。

3.2 核心 API

使用 java.lang.reflect.Proxy 类的 newProxyInstance 方法创建代理对象,该方法需要三个参数:

参数 类型 说明
ClassLoader 类加载器 和被代理对象使用相同的类加载器,固定写法
Class[] 接口字节码数组 指定代理对象要实现哪些接口,固定写法
InvocationHandler 处理器 在这里编写增强逻辑,每次调用代理方法时触发

3.3 代码示例

第一步:定义接口和被代理类

// 接口
public interface ProductDao {
    void buyProduct(String name);
}

// 被代理类(厂家)
public class Producer implements ProductDao {
    public void buyProduct(String name) {
        System.out.println(name + " 售卖了电脑");
    }
}

第二步:创建代理对象

public class Consumer {
    public static void main(String[] args) {
        final Producer producer = new Producer();

        // 直接买:调用原始对象
        producer.buyProduct("厂家");

        // 通过代理买:在原始方法前后可以加入增强逻辑
        ProductDao proxy = (ProductDao) Proxy.newProxyInstance(
            producer.getClass().getClassLoader(),  // 类加载器,固定写法
            producer.getClass().getInterfaces(),   // 接口数组,固定写法
            new InvocationHandler() {
                /**
                 * 每次调用代理对象的方法时,都会进入这里
                 * @param proxy  当前代理对象的引用(一般不用)
                 * @param method 当前被调用的方法
                 * @param args   方法的参数
                 */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                    // 增强逻辑可以写在这里(前置、后置都可以)
                    System.out.println("经销商:准备开始销售...");
                    Object result = method.invoke(producer, args); // 调用原始方法
                    System.out.println("经销商:售后服务跟进...");
                    return result;
                }
            }
        );

        proxy.buyProduct("经销商");
    }
}

输出结果:

厂家 售卖了电脑
经销商:准备开始销售...
经销商 售卖了电脑
经销商:售后服务跟进...

可以看到,代理对象在调用原始方法的前后,各插入了一段逻辑,而 Producer 类的代码完全没有被修改。

3.4 局限性

JDK 动态代理要求被代理类必须实现接口。如果一个普通的 Java 类没有实现任何接口,就无法使用这种方式。


四、CGLIB 动态代理

4.1 使用前提

CGLIB 通过创建被代理类的子类来实现代理,因此被代理类不能用 final 修饰(final 类无法被继承)。

CGLIB 是第三方库,需要引入依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.1_3</version>
</dependency>

4.2 核心 API

使用 net.sf.cglib.proxy.Enhancer 类的 create 方法创建代理对象,需要两个参数:

参数 类型 说明
Class 字节码 指定被代理类的 Class 对象
Callback 回调处理器 通常传入 MethodInterceptor 实现类,编写增强逻辑

4.3 代码示例

被代理类(不需要实现接口)

public class Producer {
    public void buyProduct(String name) {
        System.out.println(name + " 售卖了电脑");
    }
}

创建代理对象

public class Consumer {
    public static void main(String[] args) {
        final Producer producer = new Producer();

        // 直接买
        producer.buyProduct("厂家");

        // 通过 CGLIB 代理买
        Producer proxy = (Producer) Enhancer.create(
            Producer.class,           // 被代理类的字节码
            new MethodInterceptor() { // 方法拦截器
                /**
                 * 每次调用代理对象的方法时触发
                 * @param o           代理对象的引用
                 * @param method      被调用的方法
                 * @param objects     方法参数
                 * @param methodProxy 方法的代理对象(不常用)
                 */
                @Override
                public Object intercept(Object o, Method method,
                        Object[] objects, MethodProxy methodProxy)
                        throws Throwable {
                    System.out.println("经销商:准备开始销售...");
                    Object result = method.invoke(producer, objects); // 调用原始方法
                    System.out.println("经销商:售后服务跟进...");
                    return result;
                }
            }
        );

        proxy.buyProduct("经销商");
    }
}

输出结果和 JDK 动态代理一致,区别在于这次 Producer 不需要实现任何接口。


五、JDK vs CGLIB 对比

对比项 JDK 动态代理 CGLIB 动态代理
来源 JDK 官方内置 第三方库
核心类 Proxy Enhancer
实现方式 基于接口,生成接口的实现类 基于继承,生成被代理类的子类
被代理类要求 必须实现接口 不能用 final 修饰
拦截接口 InvocationHandler.invoke() MethodInterceptor.intercept()

实际开发中两者经常配合使用:如果被代理类有接口,优先用 JDK 代理;没有接口则用 CGLIB。Spring 框架内部也是根据这个规则自动选择的。


六、动态代理和事务有什么关系?

回到开头的问题:每个 Service 方法都要手写 try-catch 事务控制,太繁琐了。

现在有了动态代理,完全可以用代理对象来做这件事:

  • 在调用每个业务方法之前,自动执行 beginTransaction()
  • 在业务方法正常返回后,自动执行 commit()
  • 抛出异常时,自动执行 rollback()
  • 最终执行 release()

这样业务代码里就不需要写任何事务相关的代码,干净清爽。

这是 Spring AOP 的核心思想, @Transactional 注解背后做的,就是这件事。


更多推荐