动态代理详解

206 阅读5分钟

Java动态代理通过JDK(需接口)和CGLIB(继承)在运行时生成代理类,实现方法拦截与增强,广泛应用于Spring AOP的日志、事务管理,但各有性能与使用限制。

1. 代理模式基础

在理解动态代理前,需先掌握 代理模式(Proxy Pattern) 的核心思想:

  • 核心目标:通过一个 代理对象 控制对真实对象的访问,实现 增强功能(如日志、事务、权限校验)。

  • 分类

    • 静态代理:手动编写代理类,需为每个被代理类创建对应的代理类。
    • 动态代理:运行时动态生成代理类,无需手动编码。

2. 动态代理的核心原理

动态代理在 运行时 动态生成代理类,拦截对目标方法的调用,并在调用前后插入增强逻辑。其核心流程如下:

  1. 代理类生成:根据接口或父类生成代理类的字节码。
  2. 方法拦截:通过回调机制(如 InvocationHandler)处理目标方法的调用。
  3. 逻辑增强:在目标方法执行前后插入额外逻辑(如日志、事务)。

3. Java 动态代理的两种实现方式

(1) JDK 动态代理

  • 依赖条件目标类必须实现接口

  • 核心类

    • java.lang.reflect.Proxy:生成代理类。
    • java.lang.reflect.InvocationHandler:定义方法拦截逻辑。
  • 实现步骤

    1. 定义接口和实现类。
    2. 实现 InvocationHandler,编写增强逻辑。
    3. 通过 Proxy.newProxyInstance() 生成代理对象。

代码示例

// 1. 定义接口
public interface UserService {
    void saveUser(String user);
}
​
// 2. 实现类
public class UserServiceImpl implements UserService {
    @Override
    public void saveUser(String user) {
        System.out.println("保存用户:" + user);
    }
}
​
// 3. 实现 InvocationHandler
public class LogInvocationHandler implements InvocationHandler {
    private final Object target;
​
    public LogInvocationHandler(Object target) {
        this.target = target;
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法调用前:" + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("方法调用后:" + method.getName());
        return result;
    }
}
​
// 4. 生成代理对象
public class Main {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new LogInvocationHandler(target)
        );
        proxy.saveUser("张三");
    }
}

输出

方法调用前:saveUser
保存用户:张三
方法调用后:saveUser

(2) CGLIB 动态代理

  • 依赖条件:通过继承目标类生成代理,无需接口

  • 核心库cglib(Spring 核心包已内置)。

  • 核心类

    • Enhancer:生成代理类。
    • MethodInterceptor:定义方法拦截逻辑。
  • 实现步骤

    1. 定义目标类(无需实现接口)。
    2. 实现 MethodInterceptor,编写增强逻辑。
    3. 通过 Enhancer 生成代理对象。

代码示例

// 1. 目标类
public class UserService {
    public void saveUser(String user) {
        System.out.println("保存用户:" + user);
    }
}
​
// 2. 实现 MethodInterceptor
public class LogMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("方法调用前:" + method.getName());
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("方法调用后:" + method.getName());
        return result;
    }
}
​
// 3. 生成代理对象
public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(new LogMethodInterceptor());
        UserService proxy = (UserService) enhancer.create();
        proxy.saveUser("李四");
    }
}

输出

方法调用前:saveUser
保存用户:李四
方法调用后:saveUser

4. JDK 动态代理 vs CGLIB 动态代理

特性JDK 动态代理CGLIB 动态代理
依赖条件目标类必须实现接口目标类无需实现接口
生成方式基于接口生成代理类通过继承目标类生成子类代理
性能调用方法时反射性能较低通过 FastClass 直接调用,性能更高
局限性无法代理未实现接口的类无法代理 final 类或 final 方法
应用场景需要接口的轻量级代理无接口或需要高性能代理

5. 动态代理在 Spring 中的应用

Spring AOP(面向切面编程)的核心机制基于动态代理,具体规则如下:

  • 默认策略

    • 若目标类实现了接口 → 使用 JDK 动态代理
    • 若目标类未实现接口 → 使用 CGLIB 动态代理
  • 强制使用 CGLIB:通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 配置。

示例:Spring AOP 事务管理

@Service
public class UserService {
    @Transactional
    public void createUser(String user) {
        // 数据库操作
    }
}
​
// Spring 通过动态代理为 UserService 生成代理类,添加事务管理逻辑。

6. 动态代理的底层实现

(1) JDK 动态代理字节码生成

  • 生成类名$Proxy0$Proxy1...

  • 核心逻辑

    • 代理类继承 Proxy 并实现目标接口。
    • 每个方法调用转发到 InvocationHandler.invoke()

反编译代理类示例

public final class $Proxy0 extends Proxy implements UserService {
    public $Proxy0(InvocationHandler h) { super(h); }
​
    public final void saveUser(String user) {
        try {
            super.h.invoke(this, 
                m3, // Method 对象:UserService.saveUser
                new Object[]{user});
        } catch (Throwable e) { /*...*/ }
    }
}

(2) CGLIB 动态代理字节码生成

  • 生成类名UserService$$EnhancerByCGLIB$$12345678

  • 核心逻辑

    • 代理类继承目标类,重写非 final 方法。
    • 方法调用转发到 MethodInterceptor.intercept()

7. 动态代理的应用场景

  1. 日志记录:记录方法调用参数、耗时。
  2. 事务管理:方法执行前开启事务,执行后提交或回滚。
  3. 权限校验:拦截方法调用,验证用户权限。
  4. 缓存优化:方法结果缓存,避免重复计算。
  5. RPC 框架:透明化远程方法调用(如 Dubbo、Feign)。

8. 动态代理的局限性

  • JDK 代理:无法代理未实现接口的类。

  • CGLIB 代理

    • 无法代理 final 类或 final 方法。
    • 生成代理类较慢,首次调用性能开销较大。
  • 自调用问题:代理对象内部方法相互调用时,增强逻辑可能失效。


9. 实战:手动实现简单动态代理

以下是一个简化版的动态代理实现逻辑:

public class SimpleProxy {
    public static Object createProxy(Object target, Advice advice) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            (proxy, method, args) -> {
                advice.before();
                Object result = method.invoke(target, args);
                advice.after();
                return result;
            });
    }
}
​
// 定义增强接口
interface Advice {
    void before();
    void after();
}
​
// 使用示例
UserService proxy = (UserService) SimpleProxy.createProxy(new UserServiceImpl(), new Advice() {
    @Override
    public void before() { System.out.println("前置增强"); }
    @Override
    public void after() { System.out.println("后置增强"); }
});

总结

动态代理通过 运行时生成代理类 实现方法拦截与功能增强,是 Java 高级编程和框架设计的核心技术之一。掌握其原理与实现方式,能够深入理解 Spring AOP、MyBatis 等框架的底层机制,并为开发高性能、可扩展的应用提供坚实基础。