Java 动态代理

·Java, 动态代理

动态代理的实现原理

动态代理就是程序在运行期间,动态地创建一个“中介”对象,这个中介对象可以代理另一个对象,这种特性也是实现 Spring AOP 的基础。

既然有“动态”代理,当然也有“静态”代理,静态代理就是代码还没跑之前,就先把“经纪人”写死了。

动态代理是程序跑起来之后,Java 利用反射机制,临时在内存里创建出一个“经纪人”对象,这个对象可以代理周杰伦,拦截对周杰伦的访问,然后做一些附加操作,(比如收钱,签合同等),然后再让周杰伦唱歌。


1. 类比:明星和经纪人

  • 原对象:周杰伦 -> 只负责唱歌,其他的不管
  • 代理对象:经纪人 -> 解决周杰伦的运营问题,比如代言、演唱会等
  • 客户端:粉丝 -> 只需要找到经纪人,就能找到周杰伦

理解动态代理(Dynamic Proxy)几乎是理解 Spring 核心原理(AOP, 事务管理)的钥匙。

简单来说,动态代理就是在程序运行期间,动态地创建一个“中介”对象,代替原对象做事。


2. JDK 动态代理核心代码

JDK 的动态代理主要依赖 java.lang.reflect.Proxy 类和 InvocationHandler 接口。

关键规则: JDK 动态代理要求被代理的对象必须实现一个接口

第一步:定义接口 (Singer) 和实现类 (JayChou)

  • 接口
// 接口:定义功能
public interface Singer {
    String sing();
}
  • 实现类
// 实现类:真正的周杰伦
class JayChou implements Singer {
    @Override
    public String sing() {
        System.out.println("周杰伦:快使用双截棍,哼哼哈兮~");
        return "谢谢";
    }
}

第二步:编写动态代理逻辑(经纪人的工作手册)

先要实现 InvocationHandler,这里面定义了“中介”要干什么。

  • 代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Agent {
    // 1. 创建被代理的对象(真正的周杰伦)
    static Singer realSinger = new JayChou();

    // 2. 动态生成代理对象(经纪人)
    public static Singer agent = (Singer) Proxy.newProxyInstance(
            realSinger.getClass().getClassLoader(), // 类加载器
            realSinger.getClass().getInterfaces(),  // 实现了哪些接口
            new InvocationHandler() {               // 处理器(核心逻辑)

                /*
                 * public Object invoke(Object proxy, Method method, Object[] args) 此方法签名的注解:
                 * @param proxy -- 这是代理对象本身,也是最危险的参数
                 * @param method -- 此参数的作用是告诉外界调用的方法,比如是想让周杰伦 sing() 还是 dance()
                 * @param args -- 方法参数,比如传递进来的是 agent.sing("七里香"),那么 args 数组里装的就是 ["七里香"]
                 */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    // --- 方法执行前的增强逻辑 ---
                    System.out.println("经纪人:先谈出场费,安排行程...");

                    // --- 真正的方法执行 ---
                    // 利用反射,调用 realSinger 的方法
                    Object result = method.invoke(realSinger, args);

                    // --- 方法执行后的增强逻辑 ---
                    System.out.println("经纪人:演出结束,结算尾款。");

                    return result;
                }
            }
    );
}
  • 启动类
public class ProxyDemo {
    public static void main(String[] args) {
        // 3. 调用代理对象的方法
        String sing = Agent.agent.sing();
        // sing 有一个返回的值,这里接收了 JayChou 返回过来的 “谢谢”
        System.out.println(sing);
    }
}

运行结果:

result

3. 动态代理的用处

这就是动态代理的强大之处——解耦(Decoupling)

  1. 无侵入式修改: 如果有 100 个类(Singer, Dancer, Actor…),都需要在做事之前打印日志。如果去改这 100 个类,代码会乱成一锅粥。用动态代理,只需要写一个 Handler,就能通吃。
  2. 复用性: 那个 InvocationHandler 可以应用在任何对象上,只要逻辑是通用的(比如计算方法执行时间)。
  3. 灵活性: 可以在任何时间点,动态地添加增强逻辑。

4. 与 Spring 的关系

在 Spring Boot 里经常用的功能,底层全都是动态代理:

  • @Transactional 事务控制: 当调用一个加了 @Transactional 的方法时,Spring 并没有直接调用代码。

    • Spring 生成了一个动态代理。
    • 代理对象在调用方法前,执行 conn.setAutoCommit(false)(开启事务)。
    • 运行代码。
    • 如果没有异常,代理对象执行 conn.commit()
    • 如果有异常,代理对象执行 conn.rollback()
    • 完全感知不到这个过程,这就是 AOP(面向切面编程)。
  • MyBatis 的 Mapper 接口:UserMapper 接口时,从来没有写过它的实现类,为什么能直接 @Autowired 注入使用?

    • 因为 MyBatis 在运行时生成了一个动态代理对象,它拦截了方法调用,把方法名变成了 SQL 语句去数据库跑了一遍。

5. CGLIB (Code Generation Library)

JDK 动态代理有一个缺陷:目标对象必须实现接口。 如果的类没有实现任何接口(只是一个普通的类),JDK 代理就废了。

这时候 Spring 会自动切换到 CGLIB 动态代理

  • 原理: CGLIB 不通过接口,而是通过继承。它在内存里动态生成一个被代理类的子类,并重写父类的方法来实现增强。
  • 限制: 因为是基于继承,所以被代理的类或方法不能是 final 的(因为 final 不能被继承或重写)。

总结: 在现代 Spring Boot (2.x 以后) 中,只要引入了 AOP 依赖,Spring 默认倾向于使用 CGLIB,因为它性能越来越好,且不再强制要求接口。