动态代理是 Java 里一个很实用的机制——你不用为每个类单独写代理,就能给一堆方法织入横切逻辑。Spring AOP 背后靠的就是它。不过,Java 原生提供了一种动态代理方案,但还有第三方的 CGLIB 常被用到,两者思路不同,场景也不一样。

简单说,动态代理是在运行时生成代理类,而不是编译前写死的。这里面有三个角色:目标类、代理类和增强逻辑(Advice)。你调代理的方法,它会在中间插一脚,执行增强逻辑后再调目标方法。

JDK 动态代理要求目标类必须实现接口。它用 Proxy 和 InvocationHandler 生成一个实现了那些接口的代理类,方法调用都会被转发到 invoke 方法里。你在 invoke 里可以写日志、权限检查,然后再调用目标方法。

下面是一个用日志增强的完整例子:
// 1. 定义接口
public interface UserService {
void addUser(String name);
}
// 2. 实现目标类
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("添加用户:" + name);
}
}
// 3. 实现 InvocationHandler(增强逻辑)
public class LogInvocationHandler implements InvocationHandler {
private 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() + "开始执行,参数:" + Arrays.toString(args));
Object result = method.invoke(target, args);
System.out.println("方法" + method.getName() + "执行结束");
return result;
}
}
// 4. 生成代理类并测试
public class JdkProxyTest {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);
proxy.addUser("张三");
}
}
JDK 代理的好处是不用第三方库,生成代理快,调用效率也高。缺点就是必须要有接口,早期项目里有的类没抽象出接口,这就用不了。

CGLIB 正好弥补了这个缺口。它通过生成目标类的子类来做代理,所以不需要接口。你只要在 MethodInterceptor 的 intercept 方法里写增强逻辑就行。
引入依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
示例:
// 1. 目标类(无需接口)
public class OrderService {
public void createOrder(String orderId) {
System.out.println("创建订单:" + orderId);
}
}
// 2. 实现 MethodInterceptor(增强逻辑)
public class LogMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("方法" + method.getName() + "开始执行,参数:" + Arrays.toString(args));
Object result = methodProxy.invokeSuper(o, args);
System.out.println("方法" + method.getName() + "执行结束");
return result;
}
}
// 3. 生成代理类并测试
public class CglibProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class);
enhancer.setCallback(new LogMethodInterceptor());
OrderService proxy = (OrderService) enhancer.create();
proxy.createOrder("ORDER_001");
}
}
CGLIB 不需要接口,但无法代理 final 类或 final 方法,因为不能生成子类或覆写。另外它要依赖第三方库,生成代理类时操作字节码会慢一点,调用效率也稍低——不过 JDK 8 以后差距已经很小了。
实际中该怎么选?Spring AOP 的决策逻辑很清晰:如果目标类实现了接口,默认用 JDK 代理;否则用 CGLIB。这也是一般项目里最省心的做法。如果你自己写工具类,目标类有接口,我建议优先选 JDK 原生方案,零依赖且性能更好;如果迫不得已要代理一个没有接口的类,那就只能上 CGLIB。
搞懂这两者的原理,除了应付面试,更多是让你在线上排查代理失效问题(比如为什么事务没生效?)时能立刻反应过来:是不是类内部调用绕过了代理。

