狩猎幻想
4.41MB · 2025-11-23
在 Spring 框架中,我们常常通过 @Transactional 、@Cacheable 或自定义切面来为方法添加日志 、事务、缓存等横切逻辑。这些功能无需修改原有代码,却能“自动”生效——这背后的核心技术,正是 动态代理(Dynamic Proxy) ️。
Java 中最常用的两种动态代理实现是 JDK 动态代理 和 CGLIB 动态代理 ️。Spring AOP 正是基于它们,在运行时为目标对象创建代理,从而实现方法的增强。两者的实现机制截然不同:JDK 代理依赖接口与反射 ,CGLIB 则通过继承生成子类 。
你是否曾遇到过事务失效 、代理不生效的问题?这往往与代理方式的选择有关 。理解 JDK 代理与 CGLIB 的原理与差异,不仅能帮你深入掌握 Spring AOP ,还能在实际开发中做出更合理的架构决策。
本文将带你从代码 到原理 ,全面解析这两种代理机制,并梳理它们在 Spring 中的自动选择策略 。
JDK 动态代理是 Java 原生支持的代理机制,要求目标类必须实现接口。代理对象在运行时通过 Proxy 类创建,并需要一个 InvocationHandler 来处理方法调用。
JDK 动态代理的核心是 InvocationHandler 接口,通过该接口可以拦截所有方法调用,并在目标方法执行前后执行增强逻辑。Proxy.newProxyInstance() 方法用来生成代理类,代理类的所有方法都会被 invoke() 方法拦截。
import java.lang.reflect.*;
interface HelloService {
void sayHello(String name);
}
class HelloServiceImpl implements HelloService {
public void sayHello(String name) {
System.out.println("Hello, " + name);
}
}
class HelloServiceProxy implements InvocationHandler {
private Object target;
public HelloServiceProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(target, args);
System.out.println("After method: " + method.getName());
return result;
}
}
public class ProxyExample {
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
HelloService proxy = (HelloService) Proxy.newProxyInstance(
helloService.getClass().getClassLoader(),
helloService.getClass().getInterfaces(),
new HelloServiceProxy(helloService));
proxy.sayHello("World");
}
}
分析:
HelloService 是接口,HelloServiceImpl 是实现类,HelloServiceProxy 是代理类。HelloServiceProxy 实现了 InvocationHandler 接口,重写了 invoke() 方法,在目标方法执行前后进行增强。CGLIB (Code Generation Library) 是一个第三方库,它通过 继承 目标类并重写其方法来生成动态代理。与 JDK 动态代理不同,CGLIB 代理不需要目标类实现接口,因此能够代理普通类。
CGLIB 动态代理的原理是通过 字节码操作,生成目标类的代理子类,并重写目标类的方法来进行增强。这意味着生成的代理类是目标类的一个子类,并且对方法调用进行拦截。
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
class HelloService {
public void sayHello(String name) {
System.out.println("Hello, " + name);
}
}
class HelloServiceProxy implements MethodInterceptor {
private Object target;
public HelloServiceProxy(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After method: " + method.getName());
return result;
}
}
public class CglibProxyExample {
public static void main(String[] args) {
HelloService helloService = new HelloService();
HelloServiceProxy helloServiceProxy = new HelloServiceProxy(helloService);
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloService.class);
enhancer.setCallback(helloServiceProxy);
HelloService proxy = (HelloService) enhancer.create();
proxy.sayHello("World");
}
}
分析:
HelloService 是目标类,HelloServiceProxy 是代理类,Enhancer 用来生成代理类。invokeSuper 是因为代理是目标的子类,invokeSuper 是 CGLIB 提供的高效方式来调用父类方法,避免反射开销,并保持代理链的完整性。不能代理 final 类和方法:由于 CGLIB 是基于继承的方式实现代理,因此它无法代理 final 类和 final 方法。
内存消耗较大:CGLIB 需要生成新的字节码类,因此在内存和性能上较为消耗,特别是在大量代理对象的情况下。
| 特性 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 代理方式 | 基于接口代理 | 基于类的继承代理 |
| 目标要求 | 目标类必须实现接口 | 目标类无需实现接口 |
| 代理类生成方式 | 使用反射生成代理类 | 通过字节码生成目标类的子类 |
| 性能 | 在早期版本中性能较差,现代 JVM(尤其是开启 JIT 后)性能已大幅提升 | 性能较好,因为直接生成子类 |
| 线程安全 | 和 CGLIB 一样,默认非线程安全 | 和 JDK 动态代理一样,默认非线程安全 |
| 限制 | 只能代理实现了接口的类的public方法 | 无法代理 final 类和方法 |
@Transactional)通常使用 CGLIB 代理,如果目标类没有实现接口。在 Spring AOP 中,动态代理用于增强业务逻辑。当你使用注解(例如 @Transactional)时,Spring AOP 在底层自动选择使用 JDK动态代理 或 CGLIB动态代理:
@EnableAspectJAutoProxy(proxyTargetClass = true))。@Aspect, @Before, @After)定义切面。@Aspect
@Component
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void before(JoinPoint jp) {
System.out.println("方法执行前:" + jp.getSignature());
}
}
final 类和方法。