📌 PDF:AI人工智能 — RAG知识库实战项目
大白话说Java设计模式-12-代理模式(业务实战篇):大白商城统一日志 / 权限 / 事务的"隐形管家"
📌 一句话本质:代理模式就是"找人代办",在不动原对象的前提下加新功能。
🏷️ 标签:代理模式 / JDK 动态代理 / CGLIB / Spring AOP / 大白商城 🎯 适合:初中级后端 / 想搞懂 AOP 原理的工程师
目录
- 一、业务场景引入:为什么需要"切面化"?
- 二、反面教材:散乱横切逻辑的"代码污染"
- 三、模式原理:代理的"两件套 + 一张图"
- 四、实战代码:大白商城代理完整实现
- 五、JDK 动态代理 vs CGLIB 代理:怎么选?
- 六、Spring AOP 怎么"无侵入"工作?
- 七、日志 + 权限 + 事务三合一切面
- 八、工程决策 Checklist
- 九、与其他模式协作
- 十、本篇小结 + 下篇预告
一、业务场景引入:为什么需要"切面化"?
大白商城 2024 年初做了一次技术大改造,CTO 提了一个看似简单、实则"动全身"的需求:
“所有 Service 层的关键操作(如下单、支付、退款)都要加统一日志、性能监控、权限校验、事务管理。我不想每个 Service 都改一遍。”
我打开代码一看,100+ 个 Service 类,每个都有相似的样板代码:
/**
* 大白商城老的 Service 写法
*/
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public Order createOrder(OrderRequest request) {
// 1️⃣ 权限校验
if (!permissionService.hasPermission(SecurityContext.getUserId(), "order:create")) {
throw new BusinessException("无权限");
}
// 2️⃣ 性能监控开始
long start = System.currentTimeMillis();
// 3️⃣ 业务日志
log.info("【OrderService】开始创建订单, userId={}", request.getUserId());
try {
// 4️⃣ 业务逻辑
Order order = new Order(/* ... */);
orderRepository.save(order);
// 5️⃣ 业务日志
log.info("【OrderService】创建订单成功, orderId={}", order.getId());
return order;
} catch (Exception e) {
// 6️⃣ 错误日志
log.error("【OrderService】创建订单失败", e);
throw e;
} finally {
// 7️⃣ 性能监控结束
long duration = System.currentTimeMillis() - start;
metricsService.recordDuration("OrderService.createOrder", duration);
}
}
}
这种代码的痛点:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 100+ Service 重复样板代码 | 改一处要改 100 处 |
| ② | 业务代码被横切逻辑污染 | 看不出"业务在干啥" |
| ③ | 横切逻辑散落各处 | 改日志格式要改 100 个文件 |
| ④ | 新人接手难 | 不知道哪行是业务、哪行是横切 |
老板要的"统一处理不加样板代码",怎么搞?
答案就是——代理模式 + AOP。
1.1 大白话讲透代理
继续打比方:
场景:大白商城里所有 Service 都需要"日志 + 权限 + 性能监控"。每个 Service 自己写这些代码,等于每个员工自己去前台领门禁卡、去监控室登记。
- 错误做法:每个 Service 内部写
log.info/permission.check/metrics.record(重复 100 遍)- 正确做法:
- 雇一个"管家"(代理对象)站在 Service 门口
- 业务方调用 Service 时,实际调用的是管家
- 管家自动做日志 + 权限 + 性能监控
- 业务代码完全不变 ✅
管家怎么"代理"业务?
- 管家和业务方实现同样的接口(业务方调用接口时不知道是管家还是原对象)
- 管家在调用前后自动织入横切逻辑
- 业务代码只关心业务,不管横切
代理模式 = 在不修改原对象的前提下,通过代理对象添加新功能。
1.2 代理模式的 3 个真实场景
大白商城里,代理模式用在:
| 场景 | “代理” | 加的"功能" |
|---|---|---|
| Service 层 AOP | 业务 Service 的代理 | 日志 + 权限 + 事务 + 监控 |
| MyBatis Mapper | Mapper 接口的代理 | 动态 SQL + 结果集映射 |
| Dubbo 调用 | 远程服务接口的代理 | 网络通信 + 序列化 |
| Spring 事务 | @Transactional 注解的代理 | 事务开启/提交/回滚 |
| Redis 客户端 | RedisTemplate | 连接池 + 序列化 |
任何"横切关注点"(cross-cutting concern)都用代理。
二、反面教材:散乱横切逻辑的"代码污染"
我们看 4 个反面教材,看它们是怎么一步步崩的。
2.1 反面教材 v1:每个方法手写横切逻辑
/**
* ❌ 反面教材 v1:每个方法手写横切逻辑
*/
@Service
public class OrderServiceV1 {
public Order createOrder(OrderRequest request) {
// 1️⃣ 权限校验
if (!permissionService.hasPermission("order:create")) {
throw new BusinessException("无权限");
}
// 2️⃣ 性能监控
long start = System.currentTimeMillis();
log.info("【开始】创建订单, userId={}", request.getUserId());
try {
// 业务逻辑
Order order = new Order(/* ... */);
orderRepository.save(order);
log.info("【成功】创建订单, orderId={}", order.getId());
return order;
} catch (Exception e) {
log.error("【失败】创建订单", e);
throw e;
} finally {
long duration = System.currentTimeMillis() - start;
metricsService.record("createOrder", duration);
}
}
public Order cancelOrder(Long orderId) {
// 同样的 7 行样板代码...
}
public Order refundOrder(Long orderId) {
// 同样的 7 行样板代码...
}
// ... 每个方法都要写 7 行
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 每个方法 7 行样板 | 100 个方法 = 700 行重复 |
| ② | 业务代码被污染 | 看不到核心业务 |
| ③ | 改横切要改 100 处 | 改日志格式要全改 |
2.2 反面教材 v2:父类继承横切逻辑
/**
* ❌ 反面教材 v2:父类继承
*/
public abstract class BaseService {
protected void before(String method) {
if (!permissionService.hasPermission(method)) {
throw new BusinessException("无权限");
}
long start = System.currentTimeMillis();
log.info("【开始】{}, params={}", method, /* ... */);
}
protected void after(String method, Object result) {
log.info("【成功】{}, result={}", method, result);
}
protected void exception(String method, Exception e) {
log.error("【失败】{}", method, e);
}
}
public class OrderServiceV2 extends BaseService {
public Order createOrder(OrderRequest request) {
before("createOrder"); // 调用父类
try {
Order order = new Order(/* ... */);
orderRepository.save(order);
after("createOrder", order); // 调用父类
return order;
} catch (Exception e) {
exception("createOrder", e); // 调用父类
throw e;
}
}
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 继承关系僵硬 | Service 已经有父类怎么办? |
| ② | 多横切逻辑耦合 | 日志 + 权限 + 事务全在父类,混在一起 |
| ③ | 不能选择性横切 | 父类方法都执行,没法跳过 |
2.3 反面教材 v3:装饰器包装
/**
* ❌ 反面教材 v3:装饰器
*/
public class OrderServiceWithLog implements OrderService {
private final OrderService target;
public OrderServiceWithLog(OrderService target) {
this.target = target;
}
public Order createOrder(OrderRequest request) {
log.info("【开始】创建订单");
Order order = target.createOrder(request);
log.info("【成功】创建订单");
return order;
}
}
public class OrderServiceWithAuth implements OrderService {
private final OrderService target;
public OrderServiceWithAuth(OrderService target) {
this.target = target;
}
public Order createOrder(OrderRequest request) {
if (!permissionService.hasPermission("order:create")) {
throw new BusinessException("无权限");
}
return target.createOrder(request);
}
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 类数量爆炸 | 100 个 Service × 3 个横切 = 300 个类 |
| ② | 顺序问题 | 先日志后权限?还是先权限后日志? |
| ③ | 仍然有样板 | 每个装饰器要写"转发 + 横切"代码 |
2.4 反面教材 v4:完全不用横切(裸奔)
/**
* ❌ 反面教材 v4:完全不用横切
*/
public class OrderServiceV4 {
public Order createOrder(OrderRequest request) {
// 业务逻辑,没日志、没权限、没监控
Order order = new Order(/* ... */);
orderRepository.save(order);
return order;
}
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 出问题无法排查 | 没日志,线上事故追查无门 |
| ② | 权限漏洞 | 没校验,敏感操作可被任意调用 |
| ③ | 性能瓶颈难发现 | 没监控,不知道哪些方法慢 |
2.5 4 个反面教材的共同病根
| 痛点 | 反模式方案能不能解决? |
|---|---|
| 业务代码不被污染 | ❌ 全部要写样板 |
| 加新横切不动业务 | ❌ 全部要改老代码 |
| 多个横切灵活组合 | ❌ 全部要改继承结构 |
| 性能可控 | ❌ 反射有性能损耗 |
必须上代理模式 + AOP。
三、模式原理:代理的"两件套 + 一张图"
3.1 代理的 3 个核心角色
| 角色 | 职责 | 例子 |
|---|---|---|
| 抽象主题(Subject) | 定义代理和真实对象的共同接口 | OrderService 接口 |
| 真实主题(Real Subject) | 真正干活的对象 | OrderServiceImpl |
| 代理主题(Proxy) | 包装真实对象,加新功能 | OrderServiceProxy |
关键点:代理和真实对象实现同一接口,调用方无感知。
3.2 一张图看懂代理
调用方
↓
OrderService (接口)
↓
┌──────────────┬──────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────────┐ ┌─────────────┐
│真实对象 │ │ JDK 动态代理 │ │ CGLIB 代理 │
│Impl │ │ (基于接口) │ │ (基于继承) │
│ │ │ │ │ │
│ 业务逻辑 │ │ 业务逻辑 │ │ 业务逻辑 │
│ │ │ + 日志 │ │ + 日志 │
│ │ │ + 权限 │ │ + 权限 │
│ │ │ + 事务 │ │ + 事务 │
└─────────┘ └─────────────┘ └─────────────┘
3.3 代理的"灵魂三问"
Q1:代理 vs 装饰器,区别是什么?
答:
- 代理:控制访问(权限 / 远程 / 延迟加载)
- 装饰器:添加职责(增强功能 / 包装)
- 代理侧重"管控",装饰器侧重"增强"
- Spring AOP 本质是代理(不是装饰器)
Q2:JDK 动态代理 vs CGLIB,区别是什么?
答:
- JDK 动态代理:基于接口(必须实现接口),用
Proxy.newProxyInstance()创建- CGLIB 代理:基于继承(不需要接口),用字节码生成子类
- JDK 代理性能更好但有限制,CGLIB 代理更灵活但启动慢
Q3:Spring AOP 怎么"无侵入"工作?
答:
- 编译期 / 运行期生成代理对象
- 业务代码完全不知道代理存在
- 调用方注入的是代理对象(不是原对象)
- 代理对象在调用前后织入横切逻辑
3.4 代理的 4 种写法
| 写法 | 适用场景 | 缺点 |
|---|---|---|
| 静态代理 | 简单场景 | 类数量爆炸 |
| JDK 动态代理 | 有接口 | 必须有接口 |
| CGLIB 代理 | 无接口 | 不能代理 final 类 |
| Spring AOP | 企业级 | 强依赖 Spring |
大白商城主推 Spring AOP(底层是 JDK 动态代理 + CGLIB 混合)。
四、实战代码:大白商城代理完整实现
下面是大白商城生产环境在用的代理实现,全套代码可直接复制到 IDEA 跑。
4.1 项目环境与依赖
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.dabai.mall</groupId>
<artifactId>mall-design-pattern-12</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>mall-design-pattern-12</name>
<description>大白商城 - 设计模式 12 代理模式</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.2 抽象主题:OrderService 接口
package com.dabai.mall.order;
/**
* ✅ 抽象主题:订单服务接口
* <p>
* 代理和真实对象都实现这个接口
*
* @author 大白商城技术团队
*/
public interface OrderService {
/**
* 创建订单
*/
Order createOrder(OrderRequest request);
/**
* 取消订单
*/
Order cancelOrder(Long orderId);
/**
* 退款
*/
Order refundOrder(Long orderId);
}
4.3 真实主题:OrderServiceImpl
package com.dabai.mall.order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* ✅ 真实主题:订单服务实现
* <p>
* 业务代码完全干净,没有任何横切逻辑
*
* @author 大白商城技术团队
*/
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Override
public Order createOrder(OrderRequest request) {
log.info("【业务】开始创建订单, userId={}", request.getUserId());
// 业务逻辑(干净)
Order order = new Order();
order.setId(System.currentTimeMillis());
order.setOrderNo("ORDER_" + System.currentTimeMillis());
order.setUserId(request.getUserId());
order.setPayAmount(request.getAmount());
log.info("【业务】创建订单成功, orderId={}", order.getId());
return order;
}
@Override
public Order cancelOrder(Long orderId) {
log.info("【业务】取消订单, orderId={}", orderId);
Order order = new Order();
order.setId(orderId);
order.setOrderStatus("CANCELLED");
return order;
}
@Override
public Order refundOrder(Long orderId) {
log.info("【业务】退款, orderId={}", orderId);
Order order = new Order();
order.setId(orderId);
order.setOrderStatus("REFUNDED");
return order;
}
}
配套 DTO:
package com.dabai.mall.order;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderRequest {
private Long userId;
private BigDecimal amount;
private String productInfo;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class Order {
private Long id;
private String orderNo;
private Long userId;
private BigDecimal payAmount;
private String orderStatus;
}
4.4 实现一:JDK 动态代理
package com.dabai.mall.proxy.jdk;
import com.dabai.mall.order.OrderService;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* ✅ JDK 动态代理
* <p>
* 核心:基于接口 + InvocationHandler
*/
@Slf4j
public class JdkProxyFactory {
/**
* 创建 JDK 动态代理
*/
public static <T> T createProxy(T target, InvocationHandler handler) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 目标对象实现的接口
handler // 调用处理器
);
}
/**
* 默认的横切逻辑处理器(日志 + 性能监控)
*/
public static InvocationHandler defaultHandler(Object target) {
return (proxy, method, args) -> {
long start = System.currentTimeMillis();
// ✅ 横切:日志
log.info("【JDK代理-开始】{}, params={}", method.getName(), args);
try {
// ✅ 调用原方法
Object result = method.invoke(target, args);
// ✅ 横切:日志
log.info("【JDK代理-成功】{}, result={}", method.getName(), result);
return result;
} catch (Exception e) {
// ✅ 横切:错误日志
log.error("【JDK代理-失败】{}", method.getName(), e);
throw e;
} finally {
long duration = System.currentTimeMillis() - start;
log.info("【JDK代理-耗时】{}, duration={}ms", method.getName(), duration);
}
};
}
}
使用示例:
/**
* ✅ 用 JDK 动态代理包装
*/
public class JdkProxyDemo {
public static void main(String[] args) {
// 真实对象
OrderServiceImpl realService = new OrderServiceImpl();
// ✅ 创建代理
OrderService proxy = JdkProxyFactory.createProxy(
realService,
JdkProxyFactory.defaultHandler(realService)
);
// 调用代理(实际调用原方法,但带了横切)
OrderRequest request = OrderRequest.builder()
.userId(1001L)
.amount(new BigDecimal("8999.00"))
.build();
Order order = proxy.createOrder(request);
}
}
关键点解读:
| 行 | 代码 | 解读 |
|---|---|---|
Proxy.newProxyInstance(...) | JDK 反射创建 | 运行时生成代理类 |
target.getClass().getInterfaces() | 目标接口 | 代理必须实现这些接口 |
method.invoke(target, args) | 反射调用 | 真正调用原方法 |
InvocationHandler | 横切逻辑 | 织入日志/性能监控 |
4.5 实现二:CGLIB 代理
package com.dabai.mall.proxy.cglib;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* ✅ CGLIB 代理
* <p>
* 核心:基于继承(不需要接口)
*/
@Slf4j
public class CglibProxyFactory {
/**
* 创建 CGLIB 代理
*/
@SuppressWarnings("unchecked")
public static <T> T createProxy(Class<T> targetClass, MethodInterceptor interceptor) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass); // 设置父类(继承)
enhancer.setCallback(interceptor); // 设置拦截器
return (T) enhancer.create(); // 创建代理对象
}
/**
* 默认的横切逻辑拦截器
*/
public static MethodInterceptor defaultInterceptor(Class<?> targetClass) {
return (obj, method, args, proxy) -> {
long start = System.currentTimeMillis();
// ✅ 横切:日志
log.info("【CGLIB代理-开始】{}, params={}", method.getName(), args);
try {
// ✅ 调用原方法(用 proxy.invokeSuper 避免递归)
Object result = proxy.invokeSuper(obj, args);
log.info("【CGLIB代理-成功】{}, result={}", method.getName(), result);
return result;
} catch (Throwable e) {
log.error("【CGLIB代理-失败】{}", method.getName(), e);
throw e;
} finally {
long duration = System.currentTimeMillis() - start;
log.info("【CGLIB代理-耗时】{}, duration={}ms", method.getName(), duration);
}
};
}
}
使用示例:
/**
* ✅ 用 CGLIB 代理包装
*/
public class CglibProxyDemo {
public static void main(String[] args) {
// ✅ CGLIB 不需要接口,直接代理类
OrderServiceImpl proxy = CglibProxyFactory.createProxy(
OrderServiceImpl.class,
CglibProxyFactory.defaultInterceptor(OrderServiceImpl.class)
);
OrderRequest request = OrderRequest.builder()
.userId(1001L)
.amount(new BigDecimal("8999.00"))
.build();
Order order = proxy.createOrder(request);
}
}
关键点解读:
| 行 | 代码 | 解读 |
|---|---|---|
Enhancer | CGLIB 核心 | 字节码生成 |
setSuperclass(targetClass) | 设置父类 | 代理继承原类 |
proxy.invokeSuper(obj, args) | 调用父类 | 不要用 method.invoke(会递归) |
| 不需要接口 | 灵活 | 可代理普通类 |
4.6 实现三:Spring AOP
package com.dabai.mall.proxy.springaop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* ✅ Spring AOP 切面
* <p>
* 核心:@Aspect + @Around 注解
*/
@Slf4j
@Aspect
@Component
public class LogAspect {
/**
* 切点:所有 Service 类的 public 方法
*/
@Pointcut("execution(public * com.dabai.mall..*Service.*(..))")
public void servicePointcut() {}
/**
* 环绕通知:日志 + 性能监控
*/
@Around("servicePointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
// ✅ 横切:日志
log.info("【AOP-开始】{}, params={}", methodName, args);
try {
// ✅ 调用原方法
Object result = joinPoint.proceed();
log.info("【AOP-成功】{}, result={}", methodName, result);
return result;
} catch (Throwable e) {
log.error("【AOP-失败】{}", methodName, e);
throw e;
} finally {
long duration = System.currentTimeMillis() - start;
log.info("【AOP-耗时】{}, duration={}ms", methodName, duration);
}
}
}
使用示例:
/**
* ✅ Spring AOP 自动织入
* <p>
* 注意:OrderServiceImpl 完全不用改,AOP 自动织入
*/
@Service
public class OrderServiceImpl implements OrderService {
@Override
public Order createOrder(OrderRequest request) {
// 纯业务代码,没有横切
// AOP 自动在前后织入日志 + 性能监控
return new Order();
}
}
关键点解读:
| 注解 | 作用 |
|---|---|
@Aspect | 标记为切面 |
@Pointcut | 定义切点(哪些方法要拦截) |
@Around | 环绕通知(前后都织入) |
ProceedingJoinPoint | 切点对象(可获取方法名/参数/调用 proceed) |
4.7 单元测试
package com.dabai.mall.proxy;
import com.dabai.mall.order.Order;
import com.dabai.mall.order.OrderRequest;
import com.dabai.mall.order.OrderService;
import com.dabai.mall.order.OrderServiceImpl;
import com.dabai.mall.proxy.cglib.CglibProxyFactory;
import com.dabai.mall.proxy.jdk.JdkProxyFactory;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import static org.junit.jupiter.api.Assertions.*;
/**
* 代理完整单元测试
*/
class ProxyTest {
/**
* 测试 1:JDK 动态代理
*/
@Test
void testJdkDynamicProxy() {
OrderServiceImpl realService = new OrderServiceImpl();
// ✅ 创建代理
OrderService proxy = (OrderService) Proxy.newProxyInstance(
OrderServiceImpl.class.getClassLoader(),
new Class<?>[]{OrderService.class},
JdkProxyFactory.defaultHandler(realService)
);
// 验证:代理是 OrderService 类型
assertNotNull(proxy);
assertTrue(proxy instanceof OrderService);
assertFalse(proxy instanceof OrderServiceImpl, "代理不是真实对象");
// 调用
OrderRequest request = OrderRequest.builder()
.userId(1001L)
.amount(new BigDecimal("8999.00"))
.build();
Order order = proxy.createOrder(request);
assertNotNull(order);
assertNotNull(order.getId());
}
/**
* 测试 2:CGLIB 代理
*/
@Test
void testCglibProxy() {
// ✅ CGLIB 不需要接口
OrderServiceImpl proxy = CglibProxyFactory.createProxy(
OrderServiceImpl.class,
CglibProxyFactory.defaultInterceptor(OrderServiceImpl.class)
);
assertNotNull(proxy);
// 调用
OrderRequest request = OrderRequest.builder()
.userId(1002L)
.amount(new BigDecimal("100.00"))
.build();
Order order = proxy.createOrder(request);
assertNotNull(order);
}
/**
* 测试 3:JDK 代理的 getClass() 是动态生成的
*/
@Test
void testProxyClassName() {
OrderServiceImpl realService = new OrderServiceImpl();
OrderService proxy = (OrderService) Proxy.newProxyInstance(
OrderServiceImpl.class.getClassLoader(),
new Class<?>[]{OrderService.class},
JdkProxyFactory.defaultHandler(realService)
);
// 代理的 class 名是 $Proxy0、$Proxy1...
assertTrue(proxy.getClass().getName().contains("$Proxy"));
}
}
五、JDK 动态代理 vs CGLIB 代理:怎么选?
5.1 一张表看清核心区别
| 维度 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 原理 | 基于接口(反射) | 基于继承(字节码) |
| 前提条件 | 必须有接口 | 不需要接口 |
| 性能 | 较优(JDK 8+) | 启动慢,调用快 |
| final 类 | ✅ 都能代理 | ❌ 不能代理 final 类 |
| final 方法 | ✅ 都能代理 | ❌ 不能代理 final 方法 |
| 生成方式 | Proxy.newProxyInstance | Enhancer.create |
| Spring 默认 | 有接口时用 | 无接口时用 |
5.2 Spring 的默认策略
/**
* Spring AOP 代理选择逻辑(简化版)
*/
if (targetClass.getInterfaces().length > 0) {
// 有接口:用 JDK 动态代理
return new JdkDynamicAopProxy(target);
} else {
// 无接口:用 CGLIB
return new CglibAopProxy(target);
}
Spring Boot 2.0+ 默认行为:
| 情况 | 用的代理 |
|---|---|
| 类有接口 | JDK 动态代理 |
| 类无接口 | CGLIB 代理 |
proxyTargetClass=true | 强制 CGLIB(不管有没有接口) |
5.3 性能对比
| 操作 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 启动时间 | 快(10ms) | 慢(100ms,需生成字节码) |
| 单次调用 | 100ns | 50ns(CGLIB 调用更快) |
| 内存占用 | 较小 | 较大(生成新类) |
业务启示:
频繁调用的方法用 CGLIB,启动期一次性创建。调用少的方法用 JDK,简单高效。
5.4 大白商城的选型
| 场景 | 用哪个 | 原因 |
|---|---|---|
| Service 层(实现接口) | JDK 动态代理 | Spring 默认 |
| 第三方类(无接口) | CGLIB 代理 | 必须用 CGLIB |
| MyBatis Mapper | JDK 动态代理 | Mapper 是接口 |
| 工具类(static 方法) | ❌ 不能代理 | static 不能被代理 |
| final 类 | ❌ 不能代理 | CGLIB 限制 |
六、Spring AOP 怎么"无侵入"工作?
6.1 Spring AOP 的"魔法"
/**
* ✅ 业务代码完全干净
*/
@Service
public class OrderServiceImpl implements OrderService {
@Override
public Order createOrder(OrderRequest request) {
// 没有任何横切代码
return new Order();
}
}
但是调用 orderService.createOrder() 时,自动有:
- 日志
- 性能监控
- 权限校验
- 事务管理
怎么做到的?
调用方注入 OrderService
↓
Spring 容器实际注入的是代理对象
↓
代理对象内部持有 OrderServiceImpl(真实对象)
↓
代理对象在调用真实对象前后织入横切逻辑
↓
返回结果给调用方
6.2 Spring AOP 的"织入时机"
| 织入时机 | 方式 | 性能 |
|---|---|---|
| 编译期织入 | AspectJ 编译器 | 最好(运行时零开销) |
| 类加载期织入 | AspectJ 加载期织入 | 好 |
| 运行期织入 | Spring AOP(默认) | 一般(每次调用有开销) |
Spring AOP 默认是"运行期织入"——通过动态代理在运行时生成代理对象。
6.3 Spring AOP 的 5 种通知
| 通知 | 时机 | 用途 |
|---|---|---|
@Before | 方法执行前 | 权限校验 |
@After | 方法执行后(无论成功失败) | 资源清理 |
@AfterReturning | 方法成功返回后 | 正常日志 |
@AfterThrowing | 方法抛异常后 | 错误日志 |
@Around | 包裹方法(最强大) | 性能监控 / 事务 |
完整切面示例:
/**
* ✅ 完整的 Spring AOP 切面
*/
@Slf4j
@Aspect
@Component
public class CompleteAspect {
@Pointcut("execution(public * com.dabai.mall..*Service.*(..))")
public void servicePointcut() {}
@Before("servicePointcut()")
public void before() {
log.info("【@Before】方法执行前");
}
@After("servicePointcut()")
public void after() {
log.info("【@After】方法执行后");
}
@AfterReturning("servicePointcut()")
public void afterReturning() {
log.info("【@AfterReturning】方法成功返回");
}
@AfterThrowing("servicePointcut()")
public void afterThrowing() {
log.info("【@AfterThrowing】方法抛异常");
}
@Around("servicePointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
return pjp.proceed();
} finally {
log.info("【@Around】耗时: {}ms", System.currentTimeMillis() - start);
}
}
}
七、日志 + 权限 + 事务三合一切面
7.1 完整的多切面组合
/**
* ✅ 大白商城"日志 + 权限 + 事务"三合一切面
*/
@Slf4j
@Aspect
@Component
@Order(1) // 切面顺序:数字越小越先执行
public class LogAspect {
@Pointcut("execution(public * com.dabai.mall..*Service.*(..))")
public void servicePointcut() {}
@Around("servicePointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
String methodName = pjp.getSignature().getName();
log.info("【日志】开始, method={}, params={}", methodName, pjp.getArgs());
try {
Object result = pjp.proceed();
log.info("【日志】成功, method={}, result={}", methodName, result);
return result;
} catch (Throwable e) {
log.error("【日志】失败, method={}", methodName, e);
throw e;
} finally {
log.info("【日志】耗时, method={}, duration={}ms",
methodName, System.currentTimeMillis() - start);
}
}
}
@Slf4j
@Aspect
@Component
@Order(2)
public class PermissionAspect {
@Autowired
private PermissionService permissionService;
@Pointcut("execution(public * com.dabai.mall..*Service.*(..))")
public void servicePointcut() {}
@Before("servicePointcut()")
public void before(JoinPoint joinPoint) {
// 这里简化:用方法名作为权限标识
String methodName = joinPoint.getSignature().getName();
String permission = "service:" + methodName;
Long userId = SecurityContext.getUserId();
if (!permissionService.hasPermission(userId, permission)) {
throw new BusinessException("无权限: " + permission);
}
log.info("【权限】通过, userId={}, permission={}", userId, permission);
}
}
@Slf4j
@Aspect
@Component
@Order(0) // 事务最先开始
public class TransactionAspect {
@Autowired
private PlatformTransactionManager transactionManager;
@Pointcut("execution(public * com.dabai.mall..*Service.*(..))")
public void servicePointcut() {}
@Around("servicePointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
TransactionStatus status = transactionManager.getTransaction(
new DefaultTransactionDefinition());
try {
Object result = pjp.proceed();
transactionManager.commit(status);
log.info("【事务】提交成功, method={}", pjp.getSignature().getName());
return result;
} catch (Throwable e) {
transactionManager.rollback(status);
log.error("【事务】回滚, method={}", pjp.getSignature().getName(), e);
throw e;
}
}
}
7.2 切面执行顺序
调用 orderService.createOrder()
↓
【@Order(0)】TransactionAspect.around 开始
↓ 开启事务
【@Order(1)】LogAspect.around 开始
↓ 记录开始日志
【@Order(2)】PermissionAspect.before
↓ 权限校验
↓
OrderServiceImpl.createOrder() 业务执行
↓
【@Order(2)】PermissionAspect.after(无操作)
↓
【@Order(1)】LogAspect.around 结束
↓ 记录成功日志 + 耗时
【@Order(0)】TransactionAspect.around 结束
↓ 提交事务
↓
返回结果
7.3 业务代码"完全干净"
/**
* ✅ 业务代码完全干净,没有任何横切逻辑
*/
@Service
public class OrderServiceImpl implements OrderService {
@Override
public Order createOrder(OrderRequest request) {
// 纯业务逻辑
// 自动有:日志 + 权限 + 事务
Order order = new Order();
order.setId(System.currentTimeMillis());
return order;
}
}
八、工程决策 Checklist
8.1 ✅ 这 5 种情况,强烈建议用代理
| 序号 | 场景 | 原因 |
|---|---|---|
| ① | 横切关注点(日志/权限/事务) | 业务代码保持干净 |
| ② | 远程调用(RPC/HTTP) | 隐藏网络细节 |
| ③ | 延迟加载(Hibernate) | 性能优化 |
| ④ | 访问控制(敏感操作) | 统一权限管理 |
| ⑤ | 缓存(读多写少) | 自动缓存 |
8.2 ❌ 这 5 种情况,绝对不要用代理
| 序号 | 场景 | 原因 |
|---|---|---|
| ① | 业务逻辑简单 | 代理是多余的 |
| ② | 性能敏感 | 反射有性能损耗 |
| ③ | static 方法 | static 不能被代理 |
| ④ | final 类 / final 方法 | CGLIB 限制 |
| ⑤ | 构造方法中需要横切 | 代理不拦截构造方法 |
8.3 ⚠️ 代理的 6 大常见坑
| 序号 | 坑 | 表现 | 解决方案 |
|---|---|---|---|
| ① | 自调用失效 | this.method() 不走代理 | 注入自己的代理 |
| ② | final 类无法代理 | CGLIB 抛异常 | 用接口或 JDK 代理 |
| ③ | static 方法无法代理 | 代理被绕过 | 改用普通方法 |
| ④ | private 方法无法代理 | 代理不拦截 | 改用 public 方法 |
| ⑤ | 异常被吃掉 | InvocationHandler 包装异常 | 注意 getCause() |
| ⑥ | 性能损耗 | 反射调用慢 | 缓存 Method 对象 |
8.4 面试官视角:代理高频追问
Q1:JDK 动态代理 vs CGLIB,区别是什么?
答:
- JDK 动态代理:基于接口,必须有接口,用
Proxy.newProxyInstance- CGLIB:基于继承,不需要接口,但不能代理 final 类/方法
- Spring 默认:有接口用 JDK,无接口用 CGLIB
Q2:Spring AOP 是怎么"无侵入"工作的?
答:Spring 在运行时通过动态代理生成代理对象,业务代码完全不知道。调用方注入的是代理对象,代理对象在调用真实对象前后织入横切逻辑。
Q3:代理模式 vs 装饰器,区别是什么?
答:代理模式控制访问(权限/远程/延迟),装饰器模式增强功能(加职责)。Spring AOP 本质是代理,而不是装饰器。
九、与其他模式协作
9.1 代理 + 工厂方法 = 工厂创建代理
场景:用工厂方法批量创建代理对象。
/**
* 工厂方法 + 代理
*/
public class ServiceProxyFactory {
public static <T> T createProxy(T target, InvocationHandler handler) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
handler
);
}
}
9.2 代理 + 单例 = 代理本身单例
场景:代理对象可以做成单例,避免重复创建。
/**
* 代理 + 单例:全局共享一个代理
*/
public class GlobalLogProxy {
private static final InvocationHandler HANDLER = (proxy, method, args) -> {
// 统一的日志处理
return method.invoke(/* ... */);
};
public static <T> T wrap(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
HANDLER
);
}
}
9.3 代理 + 责任链 = 多个代理串联
场景:多个横切逻辑串联执行(Spring AOP 内部就是这样做的)。
/**
* 代理 + 责任链:多个拦截器串联
*/
public class ChainProxy {
public static <T> T create(T target, List<MethodInterceptor> interceptors) {
T current = target;
for (MethodInterceptor interceptor : interceptors) {
current = (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> interceptor.intercept(current, method, args)
);
}
return current;
}
}
9.4 大白商城模式协作全景图
┌──────────────┐
│ 代理 │ ← 本篇
└──────┬───────┘
│
┌───────────┬───────┼───────┬───────────┐
│ │ │ │ │
┌───▼───┐ ┌────▼───┐ ┌▼────┐ ┌▼─────┐ ┌───▼────┐
│工厂方法│ │ 单例 │ │责任链│ │策略 │ │ 装饰器 │
│(创建) │ │(共享) │ │(串联)│ │(算法)│ │(增强) │
└───────┘ └────────┘ └──────┘ └──────┘ └───────┘
04 篇 02 篇 31 篇 25 篇 13 篇
十、本篇小结 + 下篇预告
10.1 本篇小结(5 个核心要点)
- 本质:代理 = 在不动原对象的前提下加新功能,横切关注点最佳方案。
- 场景:日志/权限/事务/远程调用,任何横切都用代理。
- JDK vs CGLIB:JDK 基于接口、CGLIB 基于继承,Spring 默认混合。
- Spring AOP:运行时织入,业务代码完全干净。
- 避坑:自调用失效 / final 不能代理 / static 不能代理。
10.2 一句话总结
代理不是"换种方式调方法",是"在调用前后自动织入横切逻辑"。大白商城 100+ Service 类的统一日志/权限/事务,全靠 Spring AOP 自动代理——业务代码一行横切逻辑都不写。
10.3 知识脑图
代理模式
├── 4 大角色
│ ├── 抽象主题(OrderService 接口)
│ ├── 真实主题(OrderServiceImpl)
│ ├── 代理主题(JdkProxy / CglibProxy / SpringAOP)
│ └── 客户端(Controller)
├── 实战要点
│ ├── 完整 pom + 3 个测试
│ ├── 3 种实现对比(JDK/CGLIB/Spring AOP)
│ └── 业务代码完全干净
├── JDK vs CGLIB
│ ├── JDK:基于接口,性能优
│ ├── CGLIB:基于继承,更灵活
│ └── Spring 默认混合
├── Spring AOP
│ ├── 5 种通知
│ ├── 3 个切面组合(日志/权限/事务)
│ └── @Order 控制切面顺序
└── 模式协作
├── + 工厂方法(批量创建代理)
├── + 单例(共享代理)
└── + 责任链(多个代理串联)
10.4 下篇预告
第 13 篇【代理模式 - 源码剖析篇】:JDK / CGLIB / Spring AOP 中的代理实现
下一篇我们会深入源码,回答三个问题:
- JDK 的
Proxy.newProxyInstance()怎么生成代理类? - CGLIB 的
Enhancer.create()怎么生成子类? - Spring 的
JdkDynamicAopProxy/CglibAopProxy怎么织入横切逻辑?
并附完整的源码解读 + 流程图 + 大白商城的"抄作业"实践。
觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯
&spm=1001.2101.3001.5002&articleId=163732967&d=1&t=3&u=cfe53508a9594e62a23c67343f74dd13)
1125

被折叠的 条评论
为什么被折叠?



