引言:一个看似简单却精妙的设计
在Spring框架中,我们经常看到这样的代码片段:
public Object invoke(Object proxy, Method method, Object[] args) {
// 前置增强逻辑
System.out.println("Before method: " + method.getName());
// 调用原始方法
Object result = method.invoke(target, args);
// 后置增强逻辑
System.out.println("After method: " + method.getName());
return result;
}
这段看似简单的代码背后,隐藏着JDK动态代理的精妙设计。许多开发者在使用Spring AOP时,都会有一个疑问:当一个接口有多个方法时,为什么这样简单的代码就能精确调用正确的方法? 本文将深入剖析JDK动态代理的工作原理,揭示其精确路由的奥秘。
一、JDK动态代理的核心机制
1.1 代理对象的生成过程
当调用Proxy.newProxyInstance()时,JDK在运行时动态生成代理类。这个过程涉及三个核心组件:
public static Object newProxyInstance(
ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler handler
)
- ClassLoader:定义代理类的类加载器
- Interfaces:代理类要实现的接口列表
- InvocationHandler:所有方法调用的统一入口
1.2 动态生成的代理类结构
假设我们有如下接口:
public interface UserService {
void createUser(User user);
User getUserById(Long id);
void deleteUser(Long id);
}
JDK动态生成的代理类大致如下(伪代码):
public final class $Proxy0 extends Proxy implements UserService {
// 静态初始化块:预先获取所有方法的Method对象
private static Method m1; // createUser
private static Method m2; // getUserById
private static Method m3; // deleteUser
static {
try {
m1 = UserService.class.getMethod("createUser", User.class);
m2 = UserService.class.getMethod("getUserById", Long.class);
m3 = UserService.class.getMethod("deleteUser", Long.class);
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
public $Proxy0(InvocationHandler h) {
super(h);
}
@Override
public void createUser(User user) {
try {
// 调用InvocationHandler,传递预先绑定的Method对象
h.invoke(this, m1, new Object[]{user});
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public User getUserById(Long id) {
try {
return (User) h.invoke(this, m2, new Object[]{id});
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void deleteUser(Long id) {
try {
h.invoke(this, m3, new Object[]{id});
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}
1.3 关键设计:方法签名绑定
从上面的伪代码可以看出,JDK动态代理的核心秘密在于:
- 静态初始化阶段:预先获取每个接口方法的
Method对象 - 方法实现阶段:每个方法实现中硬编码绑定特定
Method对象 - 调用委托阶段:将绑定的
Method对象传递给InvocationHandler
这种设计确保了每个方法调用都携带了精确的方法签名信息。
二、多方法接口的精确路由
2.1 调用流程分析
当通过代理对象调用方法时:
UserService proxy = (UserService) Proxy.newProxyInstance(...);
proxy.getUserById(123L);
实际执行流程如下:
1. 调用代理对象的getUserById()方法
2. 代理对象内部调用:h.invoke(this, m2, new Object[]{123L})
3. InvocationHandler的invoke方法接收到:
- proxy: 代理对象本身
- method: 预先绑定的getUserById的Method对象
- args: [123L]
4. 执行method.invoke(target, args)
→ 实际调用原始对象的getUserById(123L)方法
2.2 方法签名唯一性保障
Java通过方法签名(方法名+参数类型)唯一标识方法。例如:
| 方法 | 方法签名(描述符) |
|---|---|
createUser(User) | createUser(Lcom/example/User;)V |
getUserById(Long) | getUserById(Ljava/lang/Long;)Lcom/example/User; |
deleteUser(Long) | deleteUser(Ljava/lang/Long;)V |
这种设计保证了即使方法重名,只要参数类型不同,就是不同的方法。
2.3 方法调用的精确匹配
在InvocationHandler.invoke()方法中:
public Object invoke(Object proxy, Method method, Object[] args) {
// 根据method对象可以获取精确的方法信息
String methodName = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
// 调用原始对象的对应方法
return method.invoke(target, args);
}
method对象:包含方法的完整签名信息- 动态分派:JVM根据方法签名找到具体实现
三、方法重载的特殊处理
3.1 重载方法示例
考虑以下包含重载方法的接口:
public interface DataService {
String query(String sql);
String query(String sql, Map<String, Object> params);
String query(String sql, int timeout);
}
3.2 代理类的生成策略
JDK动态代理会为每个重载方法生成独立的实现:
public class $Proxy1 extends Proxy implements DataService {
private static Method m1; // query(String)
private static Method m2; // query(String, Map)
private static Method m3; // query(String, int)
static {
m1 = DataService.class.getMethod("query", String.class);
m2 = DataService.class.getMethod("query", String.class, Map.class);
m3 = DataService.class.getMethod("query", String.class, int.class);
}
// 每个方法独立实现
public String query(String sql) {
return (String) h.invoke(this, m1, new Object[]{sql});
}
public String query(String sql, Map params) {
return (String) h.invoke(this, m2, new Object[]{sql, params});
}
public String query(String sql, int timeout) {
return (String) h.invoke(this, m3, new Object[]{sql, timeout});
}
}
3.3 调用路由示例
dataService.query("SELECT * FROM users"); // 调用m1
dataService.query("SELECT...", params); // 调用m2
dataService.query("SELECT...", 5000); // 调用m3
在InvocationHandler中:
public Object invoke(Object proxy, Method method, Object[] args) {
// 对于不同的调用,method参数是不同的:
// 1. 调用1: method = m1 (query(String))
// 2. 调用2: method = m2 (query(String, Map))
// 3. 调用3: method = m3 (query(String, int))
// 精确调用原始对象的对应方法
return method.invoke(target, args);
}
四、Object类方法的特殊处理
4.1 问题背景
代理类继承自Proxy,而Proxy继承自Object。因此代理对象也包含所有Object类的方法:
public String toString()
public boolean equals(Object obj)
public int hashCode()
// 等
这些方法的调用也会被路由到InvocationHandler.invoke()。
4.2 正确处理Object方法
在Spring的JdkDynamicAopProxy中,我们可以看到专门的处理:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 特殊处理Object类的方法
if (method.getDeclaringClass() == Object.class) {
return invokeObjectMethod(proxy, method, args);
}
// 处理AOP逻辑
// ...
}
private Object invokeObjectMethod(Object proxy, Method method, Object[] args) {
// 直接调用代理对象的方法,而不是目标对象
switch(method.getName()) {
case "equals":
return (proxy == args[0]);
case "hashCode":
return System.identityHashCode(proxy);
case "toString":
return proxy.getClass().getName() + "@" +
Integer.toHexString(System.identityHashCode(proxy));
// 其他方法...
}
}
4.3 为什么需要特殊处理?
-
对象标识一致性:
toString()应返回代理对象的字符串表示equals()和hashCode()应基于代理对象而不是目标对象
-
避免无限递归: 如果错误地在
toString()中调用目标对象的toString(),可能导致代理逻辑被重复执行
1170

被折叠的 条评论
为什么被折叠?



