在软件开发中,代理模式是解耦业务逻辑与横切关注点(如日志、权限、事务)的核心手段。静态代理通过硬编码实现,灵活性不足;而 JDK动态代理 借助Java反射机制,在运行时动态生成代理对象,完美解决了静态代理的局限。本文将从核心概念、实现细节、底层原理到实际应用,全方位解析JDK动态代理。
InvocationHandler
:invoke()
方法。所有代理方法的调用都会转发到该方法,在此可插入增强逻辑(如前置日志、后置处理)。Proxy
:newProxyInstance()
动态生成代理类实例。$Proxy0
),实现目标接口,并将方法调用转发给 InvocationHandler
。Subject
),因为代理类会实现这些接口。// 业务接口
public interface Subject {
void sayHi(String name); // 方法1:无返回值
int doWork(int num); // 方法2:有返回值
}
// 接口实现类(目标对象)
public class SubjectImpl implements Subject {
@Override
public void sayHi(String name) {
System.out.println("Hi, " + name);
}
@Override
public int doWork(int num) {
System.out.println("Working on " + num);
return num * 2;
}
}
InvocationHandler
(增强逻辑)import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler {
private Object target; // 目标对象(被代理的真实对象)
public MyInvocationHandler(Object target) {
this.target = target;
}
/**
* 代理方法的核心逻辑:
* @param proxy 代理对象($Proxy0实例)
* @param method 目标方法(如sayHi、doWork)
* @param args 方法参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 前置增强:日志输出
System.out.println("[Before] Method: " + method.getName() + ", Args: " + Arrays.toString(args));
// 2. 调用目标方法(反射执行)
Object result = method.invoke(target, args);
// 3. 后置增强:处理返回值
System.out.println("[After] Method: " + method.getName() + ", Result: " + result);
return result;
}
}
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class ProxyDemo {
public static void main(String[] args) {
// 1. 创建目标对象
Subject target = new SubjectImpl();
// 2. 创建InvocationHandler(绑定目标对象)
InvocationHandler handler = new MyInvocationHandler(target);
// 3. 生成代理对象:
// - 类加载器:与目标对象一致
// - 接口数组:目标对象实现的所有接口
// - InvocationHandler:处理代理逻辑
Subject proxy = (Subject) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
// 4. 调用代理方法(触发增强逻辑)
proxy.sayHi("Alice");
int result = proxy.doWork(5);
System.out.println("Final Result: " + result);
}
}
[Before] Method: sayHi, Args: [Alice]
Hi, Alice
[After] Method: sayHi, Result: null
[Before] Method: doWork, Args: [5]
Working on 5
[After] Method: doWork, Result: 10
Final Result: 10
proxy.sayHi()
时,先执行前置日志,再调用目标方法,最后执行后置日志。Proxy.newProxyInstance()
内部执行以下步骤:
ProxyClassFactory
动态生成代理类的字节码(基于目标接口),类名格式为 $Proxy0
、$Proxy1
等。InvocationHandler
)创建实例。生成的代理类(如 $Proxy0
)伪代码如下:
public final class $Proxy0 extends Proxy implements Subject {
// 1. 静态初始化:获取接口方法的Method对象
private static Method m_sayHi; // Subject.sayHi
private static Method m_doWork; // Subject.doWork
static {
try {
m_sayHi = Class.forName("Subject").getMethod("sayHi", String.class);
m_doWork = Class.forName("Subject").getMethod("doWork", int.class);
} catch (Exception e) {
throw new NoSuchMethodError(e.getMessage());
}
}
// 2. 构造方法:传入InvocationHandler
public $Proxy0(InvocationHandler h) {
super(h);
}
// 3. 代理方法:转发调用到InvocationHandler
@Override
public void sayHi(String name) throws Throwable {
super.h.invoke(this, m_sayHi, new Object[]{name});
}
@Override
public int doWork(int num) throws Throwable {
return (Integer) super.h.invoke(this, m_doWork, new Object[]{num});
}
// 4. 代理Object的方法(equals、toString、hashCode)
@Override
public boolean equals(Object obj) { ... }
@Override
public String toString() { ... }
@Override
public int hashCode() { ... }
}
Proxy
,因此无法再继承其他类(Java单继承限制)。Object
方法都会被代理,调用时转发到 InvocationHandler.invoke()
。通过系统属性保存生成的代理类字节码,便于调试:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
运行代码后,当前目录会生成 $Proxy0.class
文件,可通过反编译工具(如jad)查看内容。
InvocationHandler
,可复用(一个Handler代理多个接口)。method.invoke()
是反射调用,性能略低于直接方法调用(现代JVM已优化,影响可忽略)。特性 | JDK动态代理 | CGLIB动态代理 |
---|---|---|
代理方式 | 基于接口(实现接口) | 基于继承(子类化目标类) |
目标类限制 | 必须实现接口 | 不能是 final 类 |
方法限制 | 无(代理接口方法) | 不能是 final 方法(无法重写) |
性能 | 反射调用(JVM优化后接近CGLIB) | 字节码增强(通常更快) |
依赖 | JDK标准库 | 需引入CGLIB库(如Spring已集成) |
典型场景 | Spring AOP(接口代理) | Spring AOP(无接口代理) |
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibDemo {
public static void main(String[] args) {
SubjectImpl target = new SubjectImpl();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SubjectImpl.class); // 继承目标类
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("[Before] CGLIB: " + method.getName());
Object result = proxy.invokeSuper(obj, args); // 调用父类方法
System.out.println("[After] CGLIB: " + method.getName());
return result;
}
});
SubjectImpl proxy = (SubjectImpl) enhancer.create(); // 代理类是子类
proxy.sayHi("Bob");
}
}
若目标类实现多个接口(如 Subject
和 AnotherInterface
),代理类会自动实现所有接口,InvocationHandler
可统一处理:
public interface AnotherInterface {
void doOther();
}
public class SubjectImpl implements Subject, AnotherInterface {
// ... 实现两个接口的方法
}
// InvocationHandler中统一处理:
@Override
public Object invoke(...) {
if (method.getDeclaringClass() == Subject.class) {
// 处理Subject的方法
} else if (method.getDeclaringClass() == AnotherInterface.class) {
// 处理AnotherInterface的方法
}
}
Object
方法(避免递归)代理类会代理 equals
、toString
、hashCode
,若在 invoke
中调用这些方法,可能触发递归调用(如 proxy.toString()
会再次进入 invoke
)。解决方案:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 特殊处理Object的方法
if (method.getDeclaringClass() == Object.class) {
if (method.getName().equals("equals")) {
return proxy == args[0]; // 比较代理对象本身
} else if (method.getName().equals("toString")) {
return "Proxy@" + target.hashCode(); // 自定义toString
} else if (method.getName().equals("hashCode")) {
return target.hashCode(); // 复用目标对象的哈希
}
}
// 其他方法正常增强...
}
在 invoke
中捕获异常,统一处理或转换:
@Override
public Object invoke(...) {
try {
// 前置增强...
Object result = method.invoke(target, args);
// 后置增强...
return result;
} catch (InvocationTargetException e) {
// 转换异常(如包装为自定义异常)
throw new BusinessException("方法调用失败", e.getTargetException());
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
Spring默认使用JDK动态代理(若Bean实现接口),通过 @Aspect
定义切面,将日志、事务等逻辑通过动态代理织入:
@Aspect
public class LogAspect {
@Around("execution(* com.example.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("方法执行前");
Object result = joinPoint.proceed(); // 调用目标方法
System.out.println("方法执行后");
return result;
}
}
Dubbo通过JDK动态代理生成服务接口的代理,将方法调用转换为网络请求:
// 客户端代理工厂
public class DubboProxyFactory {
public static <T> T createProxy(Class<T> interfaceClass) {
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class[]{interfaceClass},
(proxy, method, args) -> {
// 构造RPC请求,发送到服务端
return rpcClient.invoke(method, args);
}
);
}
}
在敏感方法调用前校验权限,或记录操作日志:
@Override
public Object invoke(...) {
// 权限校验
if (!SecurityContext.hasPermission(method)) {
throw new PermissionDeniedException();
}
// 日志记录
Logger.info("调用方法: " + method.getName());
// 调用目标方法
return method.invoke(target, args);
}
ClassCastException
)Proxy
的子类,只能转换为接口类型,不能转换为目标类类型(如 SubjectImpl
)。Subject
)引用代理对象。target.sayHi()
)而非代理对象(proxy.sayHi()
)。Method
对象(减少反射查找开销)。InvocationHandler
,保持单一职责。System.setProperty
保存代理类字节码,辅助调试。通过本文的深入解析,相信你已掌握JDK动态代理的核心原理与应用技巧。动态代理作为AOP的基石,在框架开发和业务解耦中发挥着关键作用,建议结合实际项目反复实践,深化理解。
附录:
java.lang.reflect.Proxy