在 Java 编程领域,动态代理是一种强大的设计模式,它允许开发者在运行时创建代理对象,对目标对象的行为进行增强、监控或修改,而无需在编译期就确定代理的具体逻辑。这一特性在诸多框架中广泛应用,如 Spring AOP(面向切面编程),为实现横切面关注点的模块化提供了关键支撑。
一、JDK 动态代理实现原理
JDK 动态代理依托于 Java 反射机制构建,核心涉及 java.lang.reflect 包下的几个重要类。
首先,需要定义一个接口,该接口代表目标对象与代理对象共同遵循的行为规范。假设我们有一个简单的图形绘制接口 Shape :
public interface Shape {
void draw();
}
接着是目标类实现此接口,例如 Circle 类:
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
创建代理对象的关键在于实现 java.lang.reflect.InvocationHandler 接口,它定义了一个 invoke 方法,用于处理代理对象上所有方法的调用逻辑:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ShapeInvocationHandler implements InvocationHandler {
private final Object target;
public ShapeInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在目标方法调用前添加额外逻辑,如日志记录
System.out.println("Before method invocation");
Object result = method.invoke(target, args);
// 在目标方法调用后添加额外逻辑,如性能统计
System.out.println("After method invocation");
return result;
}
}
最后,通过 java.lang.reflect.Proxy 类生成代理对象:
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
Shape circle = new Circle();
ShapeInvocationHandler handler = new ShapeInvocationHandler(circle);
Shape proxyShape = (Shape) Proxy.newProxyInstance(
Shape.class.getClassLoader(),
new Class[]{Shape.class},
handler);
proxyShape.draw();
}
}
在上述代码中,当调用代理对象 proxyShape 的 draw 方法时,实际上控制权转移到 ShapeInvocationHandler 的 invoke 方法,在此可以灵活插入前置、后置逻辑,实现对目标对象方法的动态增强。
二、CGLIB 动态代理实现原理
CGLIB(Code Generation Library)动态代理采取了截然不同的字节码生成策略。它不依赖接口,而是直接对目标类进行字节码扩展。
引入 CGLIB 相关依赖后,以同样的 Circle 类为例(此时无需实现接口):
public class Circle {
public void draw() {
System.out.println("Drawing a circle");
}
}
创建一个实现 net.sf.cglib.proxy.MethodInterceptor 接口的拦截器类,它类似于 JDK 代理中的 InvocationHandler :
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibShapeInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("CGLIB: Before method call");
Object result = proxy.invokeSuper(obj, args);
System.out.println("CGLIB: After method call");
return result;
}
}
利用 net.sf.cglib.proxy.Enhancer 类生成代理对象:
import net.sf.cglib.proxy.Enhancer;
public class Main {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Circle.class);
enhancer.setCallback(new CglibShapeInterceptor());
Circle proxyCircle = (Circle) enhancer.create();
proxyCircle.draw();
}
}
这里, Enhancer 通过修改目标类的字节码,在原始方法执行前后织入自定义逻辑,生成的代理对象继承自目标类,能直接调用目标类非 private 的方法,无需像 JDK 代理那样依赖接口。
三、JDK 动态代理与 CGLIB 动态代理的区别
(一)实现基础
- JDK 动态代理:基于接口实现,要求目标对象必须实现至少一个接口。它利用 Java 反射在运行时动态生成代理类,该代理类同样实现目标对象的接口,通过接口回调机制将方法调用转发至 InvocationHandler 处理。
- CGLIB 动态代理:基于继承机制,直接操作目标类的字节码,生成目标类的子类作为代理对象。它通过重写子类方法,在其中插入拦截逻辑,调用父类(即目标类)原始方法实现功能,无需目标对象有接口实现。
(二)性能表现
- 在创建代理对象阶段,JDK 动态代理相对较快,因为它只需基于接口信息利用反射生成简单代理类结构;而 CGLIB 需要通过复杂的字节码生成技术创建子类,耗时较长。
- 但在方法调用时,JDK 动态代理由于每次都要经过反射查找方法等操作,性能开销较大;CGLIB 代理对象调用方法类似普通对象调用,因为方法已在字节码层面重写优化,若频繁调用代理方法,CGLIB 在性能上更具优势。
(三)适用场景
- 当目标对象有接口且注重开发便捷性、遵循接口编程规范时,JDK 动态代理是首选。例如在标准的企业级 Java 应用开发中,服务层接口常使用 JDK 代理实现日志记录、权限验证等 AOP 功能,与 Spring 等框架无缝集成。
- 若目标对象没有接口,或者需要对 final 修饰的类进行代理(JDK 代理无法做到,因 final 类不可继承),CGLIB 动态代理便能大显身手。像一些遗留代码改造、底层工具类增强场景,CGLIB 可突破接口限制,灵活实现代理需求。
JDK 动态代理与 CGLIB 动态代理各具特色,它们从不同角度为 Java 开发者提供了动态代理的实现路径,深入理解二者原理与区别,有助于在面对复杂多变的编程需求时,精准选择合适的代理方式,打造高效、健壮的 Java 应用。