嘿,各位Javaer!关于动态代理。
你可能每天都在用它,但又不完全知道它是什么。比如,当你潇洒地在Service方法上写下 @Transactional
时,有没有想过,这个注解是如何像魔法一样,自动帮你开启和提交事务的?这背后的大功臣,就是我们今天要聊的动态代理。
在Java世界里,实现动态代理主要有两大门派:JDK动态代理 和 CGLIB。它们就像是武林中的“南拳”和“北腿”,各有千秋,共同撑起了AOP(面向切面编程)的半壁江山。
啥是代理?为啥要用它?
在说“动态代理”之前,我们先得明白“代理”是干嘛的。
想象一下,你想请一位大明星(目标对象)来演出,但你不能直接联系到他。你得通过他的经纪人(代理对象)。这个经纪人除了会帮你安排演出(调用目标方法),还可能会在演出前后做一些额外的事情,比如:谈合同、安排安保、演出后收款(增强逻辑)。
在程序世界里,代理模式就是这么个意思。我们不想直接访问某个对象,而是通过一个代理对象来间接访问。这样做的好处是,我们可以在不修改原对象代码的情况下,对它的功能进行增强或控制。这就是所谓的“开闭原则”(对扩展开放,对修改关闭)。
而“动态代理”的“动态”二字,指的是代理类是在程序运行时动态生成的,而不是我们手动写好的。这给了我们极大的灵活性。
一:JDK官方亲儿子 —— JDK动态代理
JDK动态代理是Java官方提供的、根正苗红的实现方式。它的核心思想是:面向接口代理。
核心原理
JDK动态代理有一个死规定:被代理的目标类必须实现至少一个接口。它在运行时,会动态地创建一个新的代理类(比如 $Proxy0
),这个代理类会实现目标类所实现的所有接口,并继承 java.lang.reflect.Proxy
类。
当你调用代理对象的任何一个接口方法时,这个调用都会被转发到一个叫做 InvocationHandler
的处理器上。你所有的增强逻辑,比如日志、权限校验、事务管理,都写在这个处理器的 invoke
方法里。
架构图解
图解说明:
-
客户端(Client) 不直接与 目标对象(Real Object) 交互,而是与 代理对象(Proxy Object) 交互。
-
代理对象和目标对象都实现了同一个 接口(Some Interface)。
-
当客户端调用代理对象的方法时,这个调用会被分派到
InvocationHandler
的invoke
方法中。 -
InvocationHandler
可以在调用真实方法前后,执行我们自定义的增强逻辑。 -
最后,通过Java的反射机制,
InvocationHandler
调用目标对象的真实方法,完成整个流程。
实战代码
1. 定义一个用户服务接口
// 1. The interface that defines the business logic
public interface IUserService {
void createUser(String username);
}
2. 创建一个实现类
// 2. The concrete implementation of the service
public class UserServiceImpl implements IUserService {
@Override
public void createUser(String username) {
// Core business logic
System.out.println("Executing: Creating user '" + username + "' in the database.");
}
}
3. 创建我们的“经纪人”—— InvocationHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
// 3. The invocation handler, which contains the enhancement logic
public class LoggingInvocationHandler implements InvocationHandler {
private final Object target; // The real object to be proxied
public LoggingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("===> [JDK Proxy] Method " + method.getName() + " is about to be executed.");
// Execute the target method via reflection
Object result = method.invoke(target, args);
System.out.println("===> [JDK Proxy] Method " + method.getName() + " has been executed.");
return result;
}
}
4. 客户端调用
import java.lang.reflect.Proxy;
public class JdkProxyDemo {
public static void main(String[] args) {
// 1. Create the real target object
IUserService realService = new UserServiceImpl();
// 2. Create the invocation handler
InvocationHandler handler = new LoggingInvocationHandler(realService);
// 3. Create the proxy instance
IUserService proxyService = (IUserService) Proxy.newProxyInstance(
realService.getClass().getClassLoader(), // ClassLoader
realService.getClass().getInterfaces(), // Interfaces implemented by the target
handler // The invocation handler
);
// 4. Call the method on the proxy object
proxyService.createUser("Alice");
}
}
运行结果:
===> [JDK Proxy] Method createUser is about to be executed.
Executing: Creating user 'Alice' in the database.
===> [JDK Proxy] Method createUser has been executed.
看,我们成功地在不修改 UserServiceImpl
的情况下,给 createUser
方法加上了日志功能!
二:能力更强的外援 —— CGLIB
有时候,我们的目标类很“任性”,它就是没有实现任何接口。这时候JDK动态代理就束手无策了。别急,CGLIB(Code Generation Library)闪亮登场!
核心原理
CGLIB的实现方式更加激进:继承父类,创建子类。
它不要求目标类实现接口。在运行时,CGLIB会动态地创建一个目标类的子类作为代理对象,并重写父类中所有非 final
的方法。
当你调用代理对象的任何方法时,这个调用会被一个叫做 MethodInterceptor
的拦截器捕获。你所有的增强逻辑,都写在这个拦截器的 intercept
方法里。
架构图解
图解说明:
-
CGLIB代理对象 是 目标类 的一个子类。
-
当客户端调用代理对象的方法时,这个调用会被
MethodInterceptor
拦截。 -
MethodInterceptor
执行完增强逻辑后,通过调用proxy.invokeSuper(obj, args)
来调用父类(也就是目标类)的原始方法。
实战代码
假设我们有一个支付服务,它没有接口。
1. 创建一个普通的类
// 1. A plain class without any interface
public class PaymentService {
public void pay(double amount) {
// Core business logic
System.out.println("Executing: Processing payment of $" + amount);
}
// A final method cannot be proxied by CGLIB
public final void closeTransaction() {
System.out.println("Transaction closed.");
}
}
2. 创建我们的“拦截器”—— MethodInterceptor
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 2. The method interceptor for CGLIB
public class TimingMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("===> [CGLIB] Method " + method.getName() + " is about to be executed.");
long start = System.nanoTime();
// Execute the superclass method
Object result = proxy.invokeSuper(obj, args);
long end = System.nanoTime();
System.out.println("===> [CGLIB] Method " + method.getName() + " executed in " + (end - start) + " ns.");
return result;
}
}
注意:你需要添加CGLIB的依赖,例如在Maven中:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
3. 客户端调用
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyDemo {
public static void main(String[] args) {
// 1. Create an Enhancer instance
Enhancer enhancer = new Enhancer();
// 2. Set the superclass (the class to be proxied)
enhancer.setSuperclass(PaymentService.class);
// 3. Set the callback (the interceptor)
enhancer.setCallback(new TimingMethodInterceptor());
// 4. Create the proxy object
PaymentService proxyService = (PaymentService) enhancer.create();
// 5. Call the method on the proxy object
proxyService.pay(100.50);
// This call will not be intercepted because the method is final
proxyService.closeTransaction();
}
}
运行结果:
===> [CGLIB] Method pay is about to be executed.
Executing: Processing payment of $100.5
===> [CGLIB] Method pay executed in 12345 ns.
Transaction closed.
完美!我们成功代理了一个没有接口的类。
注意,对
final
方法closeTransaction
的调用没有被拦截,这印证了CGLIB的原理——它无法重写final
方法。
JDK动态代理 vs CGLIB
特性 |
JDK 动态代理 |
CGLIB |
---|---|---|
核心原理 |
基于接口实现,运行时生成接口的实现类 |
基于继承,运行时生成目标类的子类 |
依赖 |
JDK自带,无需额外依赖 |
需要引入第三方 |
代理对象 |
必须有接口 |
无需接口,但不能是 |
代理方法 |
只能代理接口中定义的方法 |
可以代理类中所有非 |
性能 |
早期版本反射调用较慢,但JDK 8后性能大幅优化 |
通过字节码操作,早期性能优于JDK,现在两者差距很小 |
限制 |
目标类必须实现接口 |
目标类不能是 |
一句话总结:
-
JDK代理:轻量、官方、稳定,但有“接口洁癖”。
-
CGLIB:强大、灵活,能代理普通类,但有点“恐高”(怕
final
)。
Spring Boot 3 中的代理栗子
Spring AOP的实现,底层就是靠JDK动态代理和CGLIB。Spring会非常智能地为你选择使用哪一种。
Spring的代理选择策略
-
老规矩 (Spring 2.0 之前):如果一个Bean实现了接口,Spring就默认使用JDK动态代理。如果没实现接口,就用CGLIB。
-
新风尚 (Spring Boot 2.0 及以后):默认统一使用CGLIB。
为什么Spring Boot要改变默认策略,更偏爱CGLIB呢?
-
统一行为:不管你有没有实现接口,代理的行为都一样,避免了一些因代理方式不同而产生的怪异问题。
-
解决类型转换问题:使用JDK代理时,从Spring容器中获取的代理对象不能被强制转换为它的原始实现类类型,只能转换为接口类型。而CGLIB是子类,不存在这个问题。
// 如果用JDK代理,这行会抛出 ClassCastException // UserServiceImpl service = (UserServiceImpl) context.getBean("userService"); // 只能这样 IUserService service = (IUserService) context.getBean("userService");
使用CGLIB则没有这个烦恼,这让开发体验更加顺滑。
在Spring Boot 3中,这个默认使用CGLIB的策略被延续了下来。你可以在 application.properties
中手动修改它,但通常没有必要。
# 如果你非要用JDK代理(不推荐),可以这样设置
# spring.aop.proxy-target-class=false
Spring AOP代码示例
让我们看一个最经典的 @Transactional
例子。
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional // <--- Spring AOP 在这里施展魔法
public void createOrder(Order order) {
// 1. 在这个方法执行前,Spring会创建一个代理
// 2. 代理对象会开启一个数据库事务
orderRepository.save(order);
if (order.isInvalid()) {
throw new RuntimeException("Invalid order!"); // 如果这里抛异常
}
// 3. 如果方法正常结束,代理对象会提交事务
// 4. 如果方法抛出运行时异常,代理对象会回滚事务
}
}
当你调用 orderService.createOrder(order)
时,你实际上调用的不是原始的 OrderService
对象,而是Spring用CGLIB(默认)为它创建的一个代理子类。这个代理子类在调用真实的 createOrder
方法前后,插入了事务管理(开启、提交、回滚)的逻辑。
这一切都悄无声息地发生了,让我们能专注于业务逻辑,这就是AOP和动态代理的魅力所在。
总结
-
代理模式 是一种在不修改原代码的情况下增强对象功能的设计模式。
-
JDK动态代理 是Java官方的、基于接口的实现。它要求目标类必须实现接口,通过
InvocationHandler
来织入逻辑。 -
CGLIB 是一个强大的第三方库,基于继承实现。它可以代理没有实现接口的普通类,通过
MethodInterceptor
来织入逻辑,但无法代理final
类或方法。 -
Spring Boot 3 默认使用 CGLIB 来实现AOP,因为它更强大、更一致。像
@Transactional
,@Cacheable
等注解的背后,都是动态代理在默默付出。