在 Spring 框架中,我们常常通过 @Transactional
、@Cacheable
或自定义切面来为方法添加日志 📝、事务、缓存等横切逻辑。这些功能无需修改原有代码,却能"自动"生效------这背后的核心技术,正是 动态代理(Dynamic Proxy) ⚙️。
Java 中最常用的两种动态代理实现是 JDK 动态代理 🔌 和 CGLIB 动态代理 🛠️。Spring AOP 正是基于它们,在运行时为目标对象创建代理,从而实现方法的增强。两者的实现机制截然不同:JDK 代理依赖接口与反射 ,CGLIB 则通过继承生成子类 。
你是否曾遇到过事务失效 、代理不生效的问题?这往往与代理方式的选择有关 🤔。理解 JDK 代理与 CGLIB 的原理与差异,不仅能帮你深入掌握 Spring AOP 🔍,还能在实际开发中做出更合理的架构决策。
本文将带你从代码 💻 到原理 📚,全面解析这两种代理机制,并梳理它们在 Spring 中的自动选择策略 🧭。
🎯 JDK 动态代理
JDK 动态代理是 Java 原生支持的代理机制,要求目标类必须实现接口。代理对象在运行时通过 Proxy
类创建,并需要一个 InvocationHandler 来处理方法调用。
✨ 关键特点:
- 基于接口:JDK 动态代理只支持接口的代理,代理对象必须实现目标类的接口。
- 运行时代理:代理对象是运行时通过反射机制动态生成的,而不是编译时生成。
- 灵活性和可扩展性:JDK 动态代理非常适用于接口的代理,可以在运行时选择代理对象的行为。
🧩 JDK 动态代理原理
JDK 动态代理的核心是 InvocationHandler
接口,通过该接口可以拦截所有方法调用,并在目标方法执行前后执行增强逻辑。Proxy.newProxyInstance()
方法用来生成代理类,代理类的所有方法都会被 invoke()
方法拦截。
java
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()
方法,在目标方法执行前后进行增强。
🔍 JDK 动态代理的限制:
- 只能代理接口:只能用于代理实现了接口的类。如果目标类没有实现接口,JDK 动态代理无法使用。
- 性能相对较差:由于代理对象通过反射机制生成,并且每次方法调用都会触发反射,性能会略微受到影响。
🎯 CGLIB 动态代理
CGLIB (Code Generation Library) 是一个第三方库,它通过 继承 目标类并重写其方法来生成动态代理。与 JDK 动态代理不同,CGLIB 代理不需要目标类实现接口,因此能够代理普通类。
✨ 关键特点:
- 基于继承:CGLIB 动态代理通过生成目标类的子类来创建代理对象。目标类无需实现接口。
- 字节码生成:CGLIB 使用字节码技术生成代理类,能够高效地实现代理。
- 代理生成方式:CGLIB 会通过继承目标类并重写目标类的方法来实现增强。
🧩 CGLIB 动态代理原理
CGLIB 动态代理的原理是通过 字节码操作,生成目标类的代理子类,并重写目标类的方法来进行增强。这意味着生成的代理类是目标类的一个子类,并且对方法调用进行拦截。
java
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
用来生成代理类。- CGLIB 动态代理通过继承目标类生成代理子类,并重写目标类的方法进行增强。
- CGLIB 动态代理 使用
invokeSuper
是因为代理是目标的子类 ,invokeSuper
是 CGLIB 提供的高效方式来调用父类方法,避免反射开销,并保持代理链的完整性。
🔍 CGLIB 的限制:
-
不能代理
final
类和方法 :由于 CGLIB 是基于继承的方式实现代理,因此它无法代理final
类和final
方法。 -
内存消耗较大:CGLIB 需要生成新的字节码类,因此在内存和性能上较为消耗,特别是在大量代理对象的情况下。
🔬 JDK 动态代理 vs CGLIB 动态代理
特性 | JDK 动态代理 | CGLIB 动态代理 |
---|---|---|
代理方式 | 基于接口代理 | 基于类的继承代理 |
目标要求 | 目标类必须实现接口 | 目标类无需实现接口 |
代理类生成方式 | 使用反射生成代理类 | 通过字节码生成目标类的子类 |
性能 | 在早期版本中性能较差,现代 JVM(尤其是开启 JIT 后)性能已大幅提升 | 性能较好,因为直接生成子类 |
线程安全 | 和 CGLIB 一样,默认非线程安全 | 和 JDK 动态代理一样,默认非线程安全 |
限制 | 只能代理实现了接口的类的public 方法 |
无法代理 final 类和方法 |
📊 适用场景:
- JDK 动态代理 :适用于目标类实现了接口,并且我们希望基于接口进行代理的场景。常见的应用场景是 Spring AOP,它通常使用 JDK 动态代理来增强服务层接口的方法。
- CGLIB 动态代理 :适用于目标类没有实现接口,或者需要对类的内部方法进行代理的场景。例如,Spring 的事务管理(
@Transactional
)通常使用 CGLIB 代理,如果目标类没有实现接口。
🔬 Spring AOP 中的动态代理
在 Spring AOP 中,动态代理用于增强业务逻辑。当你使用注解(例如 @Transactional
)时,Spring AOP 在底层自动选择使用 JDK动态代理 或 CGLIB动态代理:
- 如果目标对象实现了接口,默认使用JDK动态代理。
- 如果目标对象没有实现接口,使用CGLIB动态代理。
- 可通过配置强制使用 CGLIB(如
@EnableAspectJAutoProxy(proxyTargetClass = true)
)。
特点:
- 开发者无需手动编写代理逻辑,只需通过注解(如
@Aspect
,@Before
,@After
)定义切面。 - 更高级的 AOP(如 AspectJ)可以实现编译期或类加载期织入,功能更强。
示例(Spring AOP):
java
@Aspect
@Component
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void before(JoinPoint jp) {
System.out.println("方法执行前:" + jp.getSignature());
}
}
📅 总结
- JDK 动态代理适用于有接口的类,性能较差但灵活。
- CGLIB 动态代理 适用于没有接口的类,性能较好,但无法代理
final
类和方法。 - 在实际项目中,根据目标对象的实现方式(是否有接口),可以选择合适的代理方式。特别是在 Spring 框架中,选择 JDK 动态代理或 CGLIB 动态代理取决于目标类是否实现了接口。