一、什么是动态代理?

想象一下,你有一个对象,但你想在调用它的方法前后加点“料”,比如记录日志、检查权限或者计算耗时。这时候,动态代理就派上用场了。它可以在运行时动态创建一个代理对象,帮你拦截对原始对象的调用,并在调用前后插入自定义逻辑。

动态代理有两种常见实现方式:基于接口的JDK动态代理和基于类的CGLIB动态代理。这里我们主要聊JDK动态代理,因为它是JVM原生支持的,不需要引入额外依赖。

二、JDK动态代理的核心机制

JDK动态代理的核心是java.lang.reflect.Proxy类和InvocationHandler接口。简单来说,Proxy负责创建代理对象,而InvocationHandler负责定义代理逻辑。

来看一个完整示例:

// 技术栈:Java  
public interface UserService {  
    void saveUser(String name);  
}  

public class UserServiceImpl implements UserService {  
    @Override  
    public void saveUser(String name) {  
        System.out.println("保存用户:" + name);  
    }  
}  

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;  
    }  
}  

public class Main {  
    public static void main(String[] args) {  
        UserService realService = new UserServiceImpl();  
        UserService proxy = (UserService) Proxy.newProxyInstance(  
            realService.getClass().getClassLoader(),  
            realService.getClass().getInterfaces(),  
            new LogInvocationHandler(realService)  
        );  
        proxy.saveUser("张三");  
    }  
}  

运行结果:

调用方法前,记录日志:saveUser  
保存用户:张三  
调用方法后,记录日志:saveUser  

这个例子中,Proxy.newProxyInstance动态生成了一个UserService的代理对象,所有方法调用都会被LogInvocationHandler拦截,并在前后打印日志。

三、从字节码层面看动态代理

动态代理生成的类是什么样的?我们可以通过System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")将代理类保存到磁盘,然后用反编译工具查看。

生成的代理类大致长这样(简化版):

public final class $Proxy0 extends Proxy implements UserService {  
    private static Method m1;  
    private static Method m2;  
    private static Method m3;  

    static {  
        m1 = Class.forName("java.lang.Object").getMethod("equals", Object.class);  
        m2 = Class.forName("java.lang.Object").getMethod("toString");  
        m3 = Class.forName("UserService").getMethod("saveUser", String.class);  
    }  

    public $Proxy0(InvocationHandler h) {  
        super(h);  
    }  

    @Override  
    public void saveUser(String name) {  
        super.h.invoke(this, m3, new Object[]{name});  
    }  
}  

可以看到,代理类继承了Proxy并实现了目标接口UserService。所有方法调用都会转发给InvocationHandler.invoke,这就是动态代理的核心机制。

四、动态代理的应用场景

  1. AOP编程:比如Spring的@Transactional事务管理,就是通过动态代理实现的。
  2. 日志记录:统一记录方法调用日志,无需修改业务代码。
  3. 权限校验:在方法调用前检查用户权限。
  4. 远程调用:RPC框架(如Dubbo)用动态代理封装网络通信细节。

五、动态代理的优缺点

优点

  • 无侵入性:不需要修改原始代码。
  • 灵活:可以动态添加或修改代理逻辑。

缺点

  • 只能代理接口:JDK动态代理要求目标对象必须实现接口。
  • 性能开销:反射调用比直接调用稍慢,但在大多数场景下可以忽略。

六、注意事项

  1. 代理对象和原始对象的关系:代理对象是一个全新的对象,和原始对象是兄弟关系,不是子类。
  2. equalshashCode方法:默认情况下,代理对象的这两个方法会调用InvocationHandler,可能导致意外行为。
  3. final方法:JDK动态代理无法代理final方法,因为无法重写。

七、总结

动态代理是Java中非常强大的特性,它让我们可以在不修改原始代码的情况下增强功能。理解它的底层机制(尤其是字节码生成和反射调用)有助于更好地使用它。虽然它有一些限制,但在大多数场景下,它仍然是实现AOP和横切关注点的最佳选择。