在业务开发中,我们经常会遇到这样的问题:
一个业务方法已经写好了,但现在还需要在它执行前后增加一些通用功能,例如:
- 记录接口日志
- 校验用户权限
- 统计方法耗时
- 开启和提交事务
- 查询缓存
- 接口额度校验
- 异常统一处理
最直接的写法,是把这些代码全部写进业务方法中:
public void createOrder() {
System.out.println("记录请求日志");
long start = System.currentTimeMillis();
System.out.println("创建订单");
long cost = System.currentTimeMillis() - start;
System.out.println("方法耗时:" + cost + "ms");
}
这种代码虽然能运行,但存在明显问题:
业务代码和通用代码混在一起
多个方法需要重复编写相同逻辑
后续修改日志或权限逻辑时,需要修改大量业务代码
代理模式就是用来解决这类问题的。
代理对象挡在调用者和目标对象之间,在不修改原业务代码的前提下,对目标方法进行增强。
调用者
↓
代理对象
↓
目标对象
动态代理则是在程序运行过程中,自动创建代理对象。
Java 中常见的动态代理有两种:
JDK 动态代理
CGLIB 动态代理
Spring AOP 的底层也主要依赖这两种代理方式。
一、什么是代理模式
假设我们有一个订单服务:
public class OrderService {
public void createOrder() {
System.out.println("创建订单");
}
}
调用者直接调用:
OrderService orderService = new OrderService();
orderService.createOrder();
调用关系是:
调用者 → OrderService
使用代理后,调用关系变成:
调用者 → OrderServiceProxy → OrderService
代理对象可以在调用目标方法之前和之后增加逻辑:
代理前置处理
目标方法执行
代理后置处理
例如:
记录开始时间
创建订单
计算执行耗时
代理模式的核心不是替代目标对象,而是:
在不修改目标类代码的情况下,对目标对象的方法进行增强。
二、静态代理
理解动态代理之前,可以先看静态代理。
定义一个接口:
public interface OrderService {
void createOrder();
}
目标类:
public class OrderServiceImpl implements OrderService {
@Override
public void createOrder() {
System.out.println("创建订单");
}
}
代理类:
public class OrderServiceProxy implements OrderService {
private final OrderService target;
public OrderServiceProxy(OrderService target) {
this.target = target;
}
@Override
public void createOrder() {
long start = System.currentTimeMillis();
System.out.println("开始创建订单");
target.createOrder();
long cost = System.currentTimeMillis() - start;
System.out.println("方法耗时:" + cost + "ms");
}
}
使用:
public class Main {
public static void main(String[] args) {
OrderService target = new OrderServiceImpl();
OrderService proxy = new OrderServiceProxy(target);
proxy.createOrder();
}
}
输出:
开始创建订单
创建订单
方法耗时:1ms
静态代理本身没有问题,但它有一个明显缺点:
每代理一个接口,通常都需要手动写一个代理类。
假如项目中有:
OrderService
UserService
ProductService
PaymentService
就需要分别创建:
OrderServiceProxy
UserServiceProxy
ProductServiceProxy
PaymentServiceProxy
这些代理类的逻辑可能都一样,只是目标对象不同。
动态代理解决的就是这个问题:
不再手动为每个目标对象编写代理类,而是在程序运行时自动生成代理对象。
三、JDK 动态代理
JDK 动态代理由 Java 标准库提供,核心类是:
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler
它有一个重要限制:
被代理的目标类必须实现接口。
四、JDK 动态代理完整示例
先定义业务接口:
public interface OrderService {
void createOrder(String productName);
String queryOrder(Long orderId);
}
实现类:
public class OrderServiceImpl implements OrderService {
@Override
public void createOrder(String productName) {
System.out.println("创建订单,商品:" + productName);
}
@Override
public String queryOrder(Long orderId) {
System.out.println("查询订单:" + orderId);
return "ORDER-" + orderId;
}
}
创建动态代理处理器:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LogInvocationHandler implements InvocationHandler {
private final Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(
Object proxy,
Method method,
Object[] args
) throws Throwable {
long start = System.currentTimeMillis();
System.out.println(
"开始执行方法:" + method.getName()
);
try {
Object result = method.invoke(target, args);
System.out.println(
"方法执行成功:" + method.getName()
);
return result;
} catch (Exception e) {
System.out.println(
"方法执行失败:" + method.getName()
);
throw e;
} finally {
long cost =
System.currentTimeMillis() - start;
System.out.println(
"方法耗时:" + cost + "ms"
);
}
}
}
创建代理对象:
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
OrderService target =
new OrderServiceImpl();
OrderService proxy =
(OrderService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);
proxy.createOrder("Java 并发编程");
String orderNo = proxy.queryOrder(1001L);
System.out.println("查询结果:" + orderNo);
}
}
输出:
开始执行方法:createOrder
创建订单,商品:Java 并发编程
方法执行成功:createOrder
方法耗时:1ms
开始执行方法:queryOrder
查询订单:1001
方法执行成功:queryOrder
方法耗时:0ms
查询结果:ORDER-1001
五、Proxy.newProxyInstance 的三个参数
创建代理对象的核心代码是:
Proxy.newProxyInstance(
classLoader,
interfaces,
invocationHandler
);
完整写法:
OrderService proxy =
(OrderService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);
三个参数分别表示什么?
1. ClassLoader
target.getClass().getClassLoader()
代理类也是一个 Java 类。
既然是类,就需要类加载器把它加载进 JVM。
一般直接使用目标类的类加载器即可。
2. interfaces
target.getClass().getInterfaces()
表示代理对象需要实现哪些接口。
目标类:
public class OrderServiceImpl
implements OrderService
所以生成的代理对象也会实现:
OrderService
调用者最终拿到的是一个实现了 OrderService 接口的代理对象。
3. InvocationHandler
new LogInvocationHandler(target)
它负责处理所有被代理方法的调用。
当执行:
proxy.createOrder("Java");
实际上会进入:
InvocationHandler.invoke(...)
因此可以把 InvocationHandler 理解为:
代理对象所有方法调用的统一入口。
六、InvocationHandler 的三个参数
public Object invoke(
Object proxy,
Method method,
Object[] args
)
proxy
当前生成的代理对象。
一般不需要直接使用它。
特别注意,不要随便在 invoke() 中调用:
proxy.toString();
因为它可能再次进入 invoke(),造成递归调用。
method
当前被调用的方法对象。
例如调用:
proxy.createOrder("Java");
那么:
method.getName()
得到:
createOrder
还可以获取注解、返回值类型和参数信息:
method.getAnnotations();
method.getReturnType();
method.getParameterTypes();
args
当前方法的参数。
例如:
proxy.createOrder("Java");
那么:
args[0]
就是:
Java
七、method.invoke(target, args) 做了什么
这行代码是 JDK 动态代理中真正执行目标方法的地方:
Object result = method.invoke(target, args);
它表示:
使用反射,在目标对象 target 上调用当前 method,并把 args 作为参数传进去。
例如当前调用的是:
proxy.createOrder("Java");
最终相当于执行:
target.createOrder("Java");
只不过这里通过反射完成。
完整流程是:
调用 proxy.createOrder()
↓
进入 InvocationHandler.invoke()
↓
执行前置增强
↓
method.invoke(target, args)
↓
执行 OrderServiceImpl.createOrder()
↓
执行后置增强
↓
返回结果
八、封装通用的 JDK 代理工厂
为了避免每次都写 Proxy.newProxyInstance(),可以封装一个代理工厂。
import java.lang.reflect.Proxy;
public class JdkProxyFactory {
private final Object target;
public JdkProxyFactory(Object target) {
this.target = target;
}
public Object getProxy() {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);
}
}
使用:
public class Main {
public static void main(String[] args) {
OrderService target =
new OrderServiceImpl();
OrderService proxy =
(OrderService) new JdkProxyFactory(target)
.getProxy();
proxy.createOrder("MySQL 实战");
}
}
使用泛型进一步优化:
import java.lang.reflect.Proxy;
public class JdkProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);
}
}
调用:
OrderService proxy =
JdkProxyFactory.createProxy(
new OrderServiceImpl()
);
proxy.createOrder("Spring Boot");
九、CGLIB 动态代理
JDK 动态代理要求目标类必须实现接口。
但有些类没有接口:
public class ProductService {
public void queryProduct() {
System.out.println("查询商品");
}
}
这时无法直接使用 JDK 动态代理。
CGLIB 的解决方案是:
动态创建目标类的子类,通过重写父类方法实现代理。
原来的类:
ProductService
CGLIB 运行时大致生成:
public class ProductServiceProxy
extends ProductService {
@Override
public void queryProduct() {
// 代理逻辑
}
}
所以 CGLIB 不要求目标类实现接口。
十、CGLIB 依赖
现代 Spring 项目中一般已经包含 CGLIB 相关能力。
单独演示时,可以使用 Spring 提供的 CGLIB 包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
使用的类通常位于:
org.springframework.cglib.proxy.Enhancer
org.springframework.cglib.proxy.MethodInterceptor
org.springframework.cglib.proxy.MethodProxy
十一、CGLIB 动态代理完整示例
目标类不实现任何接口:
public class ProductService {
public void createProduct(String productName) {
System.out.println("创建商品:" + productName);
}
public String queryProduct(Long productId) {
System.out.println("查询商品:" + productId);
return "PRODUCT-" + productId;
}
}
创建 CGLIB 方法拦截器:
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class LogMethodInterceptor
implements MethodInterceptor {
@Override
public Object intercept(
Object proxy,
Method method,
Object[] args,
MethodProxy methodProxy
) throws Throwable {
long start = System.currentTimeMillis();
System.out.println(
"开始执行方法:" + method.getName()
);
try {
Object result =
methodProxy.invokeSuper(proxy, args);
System.out.println(
"方法执行成功:" + method.getName()
);
return result;
} catch (Exception e) {
System.out.println(
"方法执行失败:" + method.getName()
);
throw e;
} finally {
long cost =
System.currentTimeMillis() - start;
System.out.println(
"方法耗时:" + cost + "ms"
);
}
}
}
创建代理对象:
import org.springframework.cglib.proxy.Enhancer;
public class CglibProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T createProxy(
Class<T> targetClass
) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(
new LogMethodInterceptor()
);
return (T) enhancer.create();
}
}
使用:
public class Main {
public static void main(String[] args) {
ProductService proxy =
CglibProxyFactory.createProxy(
ProductService.class
);
proxy.createProduct("机械键盘");
String productNo =
proxy.queryProduct(1001L);
System.out.println(productNo);
}
}
输出:
开始执行方法:createProduct
创建商品:机械键盘
方法执行成功:createProduct
方法耗时:1ms
开始执行方法:queryProduct
查询商品:1001
方法执行成功:queryProduct
方法耗时:0ms
PRODUCT-1001
十二、CGLIB 的执行流程
当调用:
proxy.queryProduct(1001L);
执行流程大致是:
调用 CGLIB 生成的子类对象
↓
进入 MethodInterceptor.intercept()
↓
执行前置增强
↓
调用父类原方法
↓
执行后置增强
↓
返回结果
核心代码:
methodProxy.invokeSuper(proxy, args);
它表示:
调用代理对象父类中的原始方法。
因为代理对象本身就是目标类的子类,所以可以调用父类实现。
十三、为什么使用 invokeSuper,而不是 method.invoke
CGLIB 中推荐使用:
methodProxy.invokeSuper(proxy, args);
而不是:
method.invoke(proxy, args);
因为 proxy 本身是代理对象。
如果执行:
method.invoke(proxy, args);
可能再次调用代理对象的方法,再次进入 intercept(),最终形成递归调用。
正确方式是:
methodProxy.invokeSuper(proxy, args);
直接执行父类原始方法。
十四、带构造参数的 CGLIB 代理
如果目标类没有无参构造方法:
public class ProductService {
private final String tenantId;
public ProductService(String tenantId) {
this.tenantId = tenantId;
}
public void queryProduct() {
System.out.println(
tenantId + " 查询商品"
);
}
}
直接调用:
enhancer.create();
会失败,因为 CGLIB 创建子类对象时需要调用父类构造方法。
需要传入构造参数:
ProductService proxy =
(ProductService) enhancer.create(
new Class[]{String.class},
new Object[]{"tenant-1001"}
);
完整代码:
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ProductService.class);
enhancer.setCallback(
new LogMethodInterceptor()
);
ProductService proxy =
(ProductService) enhancer.create(
new Class[]{String.class},
new Object[]{"tenant-1001"}
);
十五、JDK 动态代理和 CGLIB 的核心区别
| 对比项 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 实现方式 | 实现目标接口 | 继承目标类 |
| 是否要求接口 | 必须有接口 | 不要求接口 |
| 代理对象类型 | 接口实现类 | 目标类的子类 |
| 核心入口 | InvocationHandler | MethodInterceptor |
| 目标方法调用 | Method.invoke | MethodProxy.invokeSuper |
| final 类 | 可以代理接口,但不能代理类本身 | 不能代理 |
| final 方法 | 接口方式通常不受影响 | 不能重写,因此无法增强 |
| private 方法 | 不能通过接口代理 | 不能被子类重写 |
| 常见应用 | 面向接口开发 | 无接口类代理 |
可以这样记:
JDK 动态代理是“实现接口”,CGLIB 动态代理是“继承目标类”。
十六、为什么 CGLIB 不能代理 final 类
CGLIB 的原理是继承目标类:
class ProxyClass extends TargetClass {
}
如果目标类是:
public final class ProductService {
}
Java 不允许其他类继承它。
因此 CGLIB 无法创建:
class ProductServiceProxy
extends ProductService
同理,如果方法是:
public final void queryProduct() {
}
子类不能重写这个方法,所以 CGLIB 也无法对它进行增强。
十七、业务示例:统一记录接口耗时
假设系统中有多个业务服务:
public interface UserService {
void createUser(String username);
String getUser(Long userId);
}
实现类:
public class UserServiceImpl
implements UserService {
@Override
public void createUser(String username) {
System.out.println("创建用户:" + username);
}
@Override
public String getUser(Long userId) {
System.out.println("查询用户:" + userId);
return "user-" + userId;
}
}
定义通用耗时代理:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PerformanceInvocationHandler
implements InvocationHandler {
private final Object target;
public PerformanceInvocationHandler(
Object target
) {
this.target = target;
}
@Override
public Object invoke(
Object proxy,
Method method,
Object[] args
) throws Throwable {
long start = System.nanoTime();
try {
return method.invoke(target, args);
} finally {
long cost =
System.nanoTime() - start;
System.out.printf(
"%s.%s 执行耗时:%.3f ms%n",
target.getClass().getSimpleName(),
method.getName(),
cost / 1_000_000.0
);
}
}
}
代理工厂:
import java.lang.reflect.Proxy;
public class PerformanceProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T create(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new PerformanceInvocationHandler(target)
);
}
}
使用:
UserService userService =
PerformanceProxyFactory.create(
new UserServiceImpl()
);
userService.createUser("zhangsan");
userService.getUser(1001L);
以后其他实现了接口的服务,也可以直接使用同一个代理工厂:
OrderService orderService =
PerformanceProxyFactory.create(
new OrderServiceImpl()
);
这就是动态代理相对静态代理的价值:
一套代理逻辑可以复用到多个不同的目标对象上。
十八、业务示例:接口额度校验
假设查询接口执行前,需要检查用户剩余额度。
业务接口:
public interface ReportService {
String generateReport(Long userId);
}
实现类:
public class ReportServiceImpl
implements ReportService {
@Override
public String generateReport(Long userId) {
System.out.println("生成数据报告");
return "report-success";
}
}
额度服务:
public class QuotaService {
public boolean hasQuota(Long userId) {
return userId != null && userId > 0;
}
public void consume(Long userId) {
System.out.println(
"用户 " + userId + " 扣减一次额度"
);
}
}
代理处理器:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class QuotaInvocationHandler
implements InvocationHandler {
private final Object target;
private final QuotaService quotaService;
public QuotaInvocationHandler(
Object target,
QuotaService quotaService
) {
this.target = target;
this.quotaService = quotaService;
}
@Override
public Object invoke(
Object proxy,
Method method,
Object[] args
) throws Throwable {
Long userId = (Long) args[0];
if (!quotaService.hasQuota(userId)) {
throw new RuntimeException(
"用户额度不足"
);
}
Object result =
method.invoke(target, args);
quotaService.consume(userId);
return result;
}
}
创建代理:
ReportService target =
new ReportServiceImpl();
QuotaService quotaService =
new QuotaService();
ReportService proxy =
(ReportService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new QuotaInvocationHandler(
target,
quotaService
)
);
调用:
String result =
proxy.generateReport(1001L);
System.out.println(result);
执行流程:
检查额度
↓
执行报告查询
↓
查询成功
↓
扣减额度
这和我们在 Spring AOP 中编写的 @Around 切面非常相似。
十九、动态代理与 Spring AOP 的关系
Spring AOP 本质上就是基于代理实现的。
假设我们写了一个切面:
@Around("@annotation(apiLog)")
public Object around(
ProceedingJoinPoint joinPoint,
ApiLog apiLog
) throws Throwable {
System.out.println("执行前");
Object result = joinPoint.proceed();
System.out.println("执行后");
return result;
}
表面上看,是 Spring 自动拦截了业务方法。
底层实际上可以理解为:
Spring 创建业务 Bean
↓
Spring 为业务 Bean 创建代理对象
↓
容器中保存的是代理对象
↓
调用代理对象的方法
↓
进入切面
↓
调用目标方法
例如:
@Service
public class UserServiceImpl
implements UserService {
}
注入:
@Resource
private UserService userService;
这里注入的 userService 很可能不是原始的 UserServiceImpl,而是 Spring 创建的代理对象。
二十、Spring 如何选择 JDK 和 CGLIB
通常可以这样理解:
目标类实现了接口
Spring 可以使用 JDK 动态代理:
代理对象实现目标接口
目标类没有实现接口
Spring 使用 CGLIB:
代理对象继承目标类
在 Spring Boot 中,也可以通过配置影响代理方式:
spring:
aop:
proxy-target-class: true
true 表示优先使用基于类的代理,也就是 CGLIB 代理。
不过具体行为还会受到 Spring 版本、代理配置和目标类特征等因素影响。
二十一、为什么同类内部调用会导致 AOP 失效
这是 Spring AOP 中非常常见的问题。
例如:
@Service
public class OrderService {
public void createOrder() {
this.saveOrder();
}
@Transactional
public void saveOrder() {
System.out.println("保存订单");
}
}
外部调用:
orderService.createOrder();
执行 createOrder() 时,会进入代理对象。
但是在 createOrder() 内部:
this.saveOrder();
这里的 this 指向的是目标对象本身,不是代理对象。
调用关系变成:
代理对象.createOrder()
↓
目标对象.createOrder()
↓
目标对象.this.saveOrder()
saveOrder() 没有再次经过代理对象,所以事务、日志、缓存等切面可能不会生效。
正确的代理调用流程应该是:
调用者
↓
代理对象
↓
切面逻辑
↓
目标方法
而 this.xxx() 绕过了代理对象。
常见解决方法有:
把被调用方法拆到另一个 Bean
通过代理对象调用
重新设计业务边界
最推荐的是拆分到另一个 Bean:
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderPersistenceService
orderPersistenceService;
public void createOrder() {
orderPersistenceService.saveOrder();
}
}
@Service
public class OrderPersistenceService {
@Transactional
public void saveOrder() {
System.out.println("保存订单");
}
}
二十二、异常处理中的一个细节
JDK 动态代理中常见写法:
method.invoke(target, args);
如果目标方法抛出异常,反射会把原异常包装成:
InvocationTargetException
可以把真实异常取出来:
try {
return method.invoke(target, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
完整写法:
@Override
public Object invoke(
Object proxy,
Method method,
Object[] args
) throws Throwable {
try {
return method.invoke(target, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
这样调用者拿到的是业务方法本身抛出的异常,而不是反射包装异常。
二十三、动态代理适合解决什么问题
动态代理适合处理横跨多个业务模块的通用逻辑。
例如:
日志记录
权限校验
事务管理
接口耗时统计
缓存处理
接口额度校验
统一异常处理
审计记录
重试机制
数据权限
这些逻辑的共同特点是:
它们不是某一个具体业务的核心,但很多业务方法都需要使用。
例如订单创建方法的核心应该是:
创建订单
而不是:
检查权限
记录日志
开启事务
统计耗时
提交事务
发送监控指标
动态代理可以把这些通用逻辑从业务代码中抽离出来。
二十四、动态代理不适合解决什么问题
代理不是所有问题都应该使用。
如果逻辑只属于某个具体方法,例如:
订单价格计算
商品库存判断
用户等级计算
报告数据组装
这些属于业务本身,不应该为了“解耦”而全部放进代理。
代理更适合:
通用、重复、横跨多个模块的逻辑
不适合:
和具体业务强相关的核心逻辑
二十五、JDK 动态代理和 CGLIB 怎么选择
如果自己编写动态代理,可以按照下面的方式选择。
有接口,且面向接口编程
优先考虑 JDK 动态代理:
public class UserServiceImpl
implements UserService
优点是结构清晰,调用者依赖接口。
没有接口
使用 CGLIB:
public class ProductService {
}
CGLIB 可以直接创建目标类的子类。
使用 Spring AOP
大部分情况下不需要手动选择。
交给 Spring 创建代理即可。
开发者更需要关注的是:
方法是否被 Spring Bean 管理
调用是否经过代理对象
方法是否可以被代理
是否存在同类内部调用
final 方法是否阻止 CGLIB 增强
二十六、总结
代理模式的核心是:
在不修改目标类代码的前提下,通过代理对象对目标方法进行增强。
静态代理需要手动编写代理类:
一个目标类型对应一个代理类
动态代理则是在运行时自动创建代理对象:
一套代理逻辑可以复用到多个目标对象
JDK 动态代理的核心:
目标类实现接口
Proxy.newProxyInstance()
InvocationHandler.invoke()
Method.invoke()
CGLIB 动态代理的核心:
创建目标类的子类
Enhancer
MethodInterceptor.intercept()
MethodProxy.invokeSuper()
两者最核心的区别可以记成一句话:
JDK 动态代理通过实现接口创建代理,CGLIB 通过继承目标类创建代理。
而 Spring AOP 可以理解为:
Spring 自动帮我们创建代理对象
↓
把日志、事务、权限、缓存等通用逻辑
织入目标方法的执行过程
当我们真正理解 JDK 动态代理和 CGLIB 后,再看 Spring AOP、@Transactional、@Async、缓存注解和自定义切面,就不会只停留在“加一个注解就能生效”的层面,而是能够理解它们背后的代理调用过程。
评论
填写昵称与邮箱即可评论,无需登录。