一、什么是动态代理?
想象一下,你有一个对象,但你想在调用它的方法前后加点“料”,比如记录日志、检查权限或者计算耗时。这时候,动态代理就派上用场了。它可以在运行时动态创建一个代理对象,帮你拦截对原始对象的调用,并在调用前后插入自定义逻辑。
动态代理有两种常见实现方式:基于接口的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,这就是动态代理的核心机制。
四、动态代理的应用场景
- AOP编程:比如Spring的
@Transactional事务管理,就是通过动态代理实现的。 - 日志记录:统一记录方法调用日志,无需修改业务代码。
- 权限校验:在方法调用前检查用户权限。
- 远程调用:RPC框架(如Dubbo)用动态代理封装网络通信细节。
五、动态代理的优缺点
优点:
- 无侵入性:不需要修改原始代码。
- 灵活:可以动态添加或修改代理逻辑。
缺点:
- 只能代理接口:JDK动态代理要求目标对象必须实现接口。
- 性能开销:反射调用比直接调用稍慢,但在大多数场景下可以忽略。
六、注意事项
- 代理对象和原始对象的关系:代理对象是一个全新的对象,和原始对象是兄弟关系,不是子类。
equals和hashCode方法:默认情况下,代理对象的这两个方法会调用InvocationHandler,可能导致意外行为。final方法:JDK动态代理无法代理final方法,因为无法重写。
七、总结
动态代理是Java中非常强大的特性,它让我们可以在不修改原始代码的情况下增强功能。理解它的底层机制(尤其是字节码生成和反射调用)有助于更好地使用它。虽然它有一些限制,但在大多数场景下,它仍然是实现AOP和横切关注点的最佳选择。
评论