在理解了静态代理之后,再来看动态代理,其实更像是对同一种思想的进一步演化,而不是一种完全全新的机制。静态代理已经解决了一个关键问题------如何在不修改原有业务代码的前提下,对方法调用进行增强,但它本身也暴露出了明显的局限,比如代理类数量膨胀、代码重复严重等。
动态代理正是在这样的背景下出现的。它的核心价值并不只是"动态"这两个字,而在于它通过运行时生成代理类的方式,把原本需要手写的大量重复代码统一收敛到了一个更抽象的层面,从而让代理机制真正具备了通用性。
从表面上看,动态代理似乎只是"少写了几个类",但从设计角度来说,它改变的是整个扩展方式。
为什么需要动态代理
如果回到静态代理的场景中去思考,当系统中只有一个接口时,手写一个代理类其实并不算什么问题。但一旦接口数量开始增加,比如系统中有十几个甚至几十个服务接口,并且这些接口的方法都需要统一增加日志、权限校验或事务处理,那么问题就会变得非常明显。
每一个接口都需要一个对应的代理类,而这些代理类在结构上几乎完全相同,唯一的区别只是持有的目标对象不同以及调用的方法不同。这种重复不仅让代码显得臃肿,也会让维护成本迅速上升。
因此,一个更自然的想法是:既然这些代理类的结构是高度一致的,那么是否可以不再手写这些类,而是让程序在运行时自动帮我们生成?
动态代理正是对这个问题的回答。
动态代理在做什么
动态代理本质上仍然遵循代理模式的基本思想,即通过一个"中间对象"来控制对目标对象的访问,只不过这个中间对象不再由开发者手动编写,而是由 JDK 在运行时动态生成。
也就是说,从调用者的角度来看,它依然是在调用一个实现了某个接口的对象,但这个对象实际上是运行时生成的代理对象,而不是我们手写的具体类。
更关键的一点在于,所有方法调用都会被统一拦截,并交由一段集中定义的逻辑来处理,这使得"增强逻辑"从原本分散在各个代理类中,变成了集中在一个地方进行管理。
两个核心角色:Proxy 与 InvocationHandler
理解 JDK 动态代理时,可以重点关注两个核心组成部分:Proxy 类和 InvocationHandler 接口。
InvocationHandler 可以看作是"代理逻辑的承载体"。在这个接口中,最重要的方法是 invoke,它定义了当代理对象的方法被调用时,实际要执行的逻辑。换句话说,所有通过代理对象发起的方法调用,最终都会落到这个 invoke 方法中。
java
public interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
在这个方法中,method 表示当前被调用的方法,args 表示方法参数,而 proxy 则是当前的代理对象本身。通过这些信息,我们可以在方法执行前后插入任意逻辑,例如日志记录、权限校验或事务控制。
相比之下,Proxy 类则负责另一件事情:在运行时生成代理对象。它提供的 newProxyInstance 方法会根据指定的接口列表以及 InvocationHandler 的实现,动态创建一个实现了这些接口的代理类,并返回对应的实例。
java
public class Proxy {
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h) {
// 运行时生成代理类
}
}
可以把这个过程理解为:我们提供"规则"(InvocationHandler),JDK 根据这些规则生成一个"可执行的代理对象"。
一个完整的实现过程
为了更直观地理解动态代理的使用方式,可以通过一个简单的示例来看整个过程。
首先定义一个业务接口:
java
public interface UserService {
void save();
void update();
}
然后实现这个接口,作为被代理的目标对象:
java
public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("保存用户数据");
}
@Override
public void update() {
System.out.println("更新用户数据");
}
}
接下来定义 InvocationHandler 的实现类。在这个类中,我们可以集中编写所有增强逻辑:
java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class UserServiceInvocationHandler implements InvocationHandler {
private Object target;
public UserServiceInvocationHandler(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;
}
}
可以看到,这里并没有针对某一个具体方法去写逻辑,而是统一通过 method 对象来处理所有方法调用,这也是动态代理能够"通用化"的关键所在。
最后,通过 Proxy 创建代理对象并进行调用:
java
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
InvocationHandler handler = new UserServiceInvocationHandler(target);
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
proxy.save();
proxy.update();
}
}
执行结果可以看到,每个方法在执行前后都被统一增强,而目标类本身完全没有发生变化。
从调用流程理解动态代理
如果把整个调用过程展开来看,大致可以分为几个步骤。
首先,在调用 Proxy.newProxyInstance 时,JDK 会在运行时生成一个代理类,这个类实现了目标对象所实现的接口。这个过程对开发者是透明的,我们不需要关心具体生成了什么代码。
当通过代理对象调用方法时,这个调用不会直接进入目标对象,而是先被代理类拦截,并统一转发到 InvocationHandler 的 invoke 方法中。在 invoke 方法里,我们可以决定是否调用目标方法,以及在调用前后执行哪些增强逻辑。
最后,通过反射调用 method.invoke(target, args),才真正进入目标对象的方法实现。
也就是说,整个调用路径从原来的"调用者 → 目标对象",变成了"调用者 → 代理对象 → InvocationHandler → 目标对象"。正是因为中间多了这一层拦截机制,我们才有机会对方法执行过程进行控制。
动态代理的优势与局限
相比静态代理,动态代理最明显的优势在于它极大减少了重复代码。原本需要为每一个接口编写一个代理类的工作,现在只需要一个 InvocationHandler 实现类就可以覆盖所有方法调用,这在接口数量较多的系统中尤为重要。
同时,由于代理对象是在运行时生成的,它可以针对不同的接口和对象动态创建,从而使得整个机制更加灵活,也更容易扩展。这一点在框架开发中尤其重要,因为框架往往需要面对各种不同的业务对象。
不过,动态代理也并非没有代价。由于它依赖反射机制,在方法调用时会引入一定的性能开销,虽然在大多数业务场景下影响并不明显,但在高频调用或性能敏感的场景中仍然需要谨慎使用。
另外,从理解角度来说,动态代理比静态代理更抽象一些,尤其是涉及反射和运行时生成类的部分,对于初学者来说会有一定的门槛。
与静态代理的关系
如果把静态代理和动态代理放在一起看,可以发现它们的核心思想其实完全一致,都是通过引入一个中间层来增强方法调用。不同之处在于实现方式:静态代理依赖手写类,而动态代理则将这部分工作交给了运行时系统来完成。
可以把静态代理看作是一种"显式实现",而动态代理则是一种"自动生成"的实现。在理解上,静态代理更直观,而动态代理则更具实用价值。
在实际开发中的体现
在日常开发中,虽然很少直接使用 JDK 动态代理来手写代码,但它的思想却广泛存在于各种框架之中。以 Spring 为例,其 AOP 实现本质上就是基于动态代理,通过在运行时生成代理对象,在方法执行前后织入横切逻辑,从而实现事务管理、日志记录等功能。
从这个角度来看,理解动态代理并不仅仅是掌握一种技术细节,更重要的是理解一种通用的扩展机制,这种机制使得我们可以在不修改原有业务代码的前提下,对系统行为进行灵活控制。