作者:逆境不可逃
你的支持是我的动力,谢谢!!!
目录
[二、代理模式的核心分类(实战中最常用的 3 类)](#二、代理模式的核心分类(实战中最常用的 3 类))
[三、实战案例 1:静态代理(入门必懂)](#三、实战案例 1:静态代理(入门必懂))
[1. 抽象主题:订单服务接口](#1. 抽象主题:订单服务接口)
[2. 真实主题:订单服务实现类(目标对象)](#2. 真实主题:订单服务实现类(目标对象))
[3. 代理类:订单服务静态代理](#3. 代理类:订单服务静态代理)
[4. 客户端调用](#4. 客户端调用)
[四、实战案例 2:JDK 动态代理(Spring AOP 核心)](#四、实战案例 2:JDK 动态代理(Spring AOP 核心))
[1. JDK 动态代理核心类:InvocationHandler](#1. JDK 动态代理核心类:InvocationHandler)
[2. 客户端调用(可代理任意接口)](#2. 客户端调用(可代理任意接口))
[五、实战案例 3:CGLIB 动态代理(无接口场景)](#五、实战案例 3:CGLIB 动态代理(无接口场景))
[1. 依赖准备](#1. 依赖准备)
[2. CGLIB 代理核心类:MethodInterceptor](#2. CGLIB 代理核心类:MethodInterceptor)
[3. 客户端调用(代理无接口类)](#3. 客户端调用(代理无接口类))
[1. Spring AOP(核心场景)](#1. Spring AOP(核心场景))
[2. 远程代理(RPC 框架)](#2. 远程代理(RPC 框架))
[3. 延迟加载(虚拟代理)](#3. 延迟加载(虚拟代理))
[4. 保护代理(权限控制)](#4. 保护代理(权限控制))
[5. 缓存代理(结果复用)](#5. 缓存代理(结果复用))
[三、完整代码实现(Spring Boot 3.x)](#三、完整代码实现(Spring Boot 3.x))
[1. 依赖准备(Maven)](#1. 依赖准备(Maven))
[2. 步骤 1:定义自定义注解@ApiLog](#2. 步骤 1:定义自定义注解@ApiLog)
[3. 步骤 2:定义切面类(核心代理逻辑)](#3. 步骤 2:定义切面类(核心代理逻辑))
[4. 步骤 3:定义业务接口和实现类](#4. 步骤 3:定义业务接口和实现类)
[5. 步骤 4:启动类(开启 AOP)](#5. 步骤 4:启动类(开启 AOP))
[四、测试验证(Postman / 浏览器调用)](#四、测试验证(Postman / 浏览器调用))
[1. 测试查询用户接口(GET)](#1. 测试查询用户接口(GET))
[2. 测试创建用户接口(POST)](#2. 测试创建用户接口(POST))
[3. 测试异常接口(GET)](#3. 测试异常接口(GET))
[1. Spring AOP 与动态代理的对应关系](#1. Spring AOP 与动态代理的对应关系)
[2. 关键注解说明](#2. 关键注解说明)
[3. JDK vs CGLIB 代理选择](#3. JDK vs CGLIB 代理选择)
[1. 性能优化](#1. 性能优化)
[2. 功能扩展](#2. 功能扩展)
[3. 异常处理](#3. 异常处理)
一、代理模式核心定义
代理模式是结构型设计模式的一种,核心目的是:
为其他对象提供一种代理以控制对这个对象的访问,代理对象在客户端和目标对象之间起到中介作用,可附加额外逻辑(如权限校验、日志、缓存、延迟加载)。
简单来说,就是 "中间人":你想访问目标对象,但不直接接触它,而是通过代理对象间接访问,代理可以帮你做 "前置处理(如鉴权)→ 调用目标 → 后置处理(如日志)"。
核心角色(所有代理模式通用)
| 角色 | 作用 |
|---|---|
| 抽象主题(Subject) | 定义目标对象和代理对象的统一接口(如 "订单服务接口") |
| 真实主题(RealSubject) | 目标对象(被代理的核心对象),实现 Subject 接口(如 "订单服务实现类") |
| 代理(Proxy) | 实现 Subject 接口,持有 RealSubject 引用,控制对目标对象的访问,可附加额外逻辑 |
生活类比(最易理解)
- 场景 1 :租房
- 抽象主题:租房服务(找房源、签合同);
- 真实主题:房东(有房源,能签合同);
- 代理:中介(帮房东带看、收中介费、做背景核验,控制租客对房东的直接访问);
- 场景 2 :明星演出
- 抽象主题:演出服务(接演出、表演);
- 真实主题:明星(核心表演);
- 代理:经纪人(帮明星接演出、谈价格、安排行程,过滤无效请求)。
二、代理模式的核心分类(实战中最常用的 3 类)
代理模式的核心分类依据是代理对象的创建时机 ,不同分类适配不同场景:
| 分类 | 创建时机 | 实现方式 | 核心场景 | 优点 | 缺点 |
|---|---|---|---|---|---|
| 静态代理 | 编译期 | 手动编写代理类 | 简单场景、固定逻辑 | 简单直观、易调试 | 代码冗余、扩展差 |
| 动态代理(JDK) | 运行期 | 反射 + 接口 | 接口化场景(如 Spring AOP) | 无代码冗余、灵活 | 只能代理接口 |
| 动态代理(CGLIB) | 运行期 | 字节码生成 + 继承 | 无接口场景 | 可代理任意类 | 不能代理 final 类 / 方法 |
三、实战案例 1:静态代理(入门必懂)
以 "订单服务代理" 为例,代理附加权限校验 + 日志记录逻辑:
1. 抽象主题:订单服务接口
/**
* 抽象主题:订单服务接口(目标+代理的统一接口)
*/
public interface OrderService {
/**
* 创建订单
* @param userId 用户ID
* @param productId 商品ID
* @return 订单ID
*/
String createOrder(String userId, String productId);
}
2. 真实主题:订单服务实现类(目标对象)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
/**
* 真实主题:订单服务核心实现(被代理的目标对象)
*/
@Service
public class OrderServiceImpl implements OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);
@Override
public String createOrder(String userId, String productId) {
// 核心业务逻辑:创建订单
String orderId = "ORDER_" + System.currentTimeMillis() + "_" + userId;
log.info("【核心业务】创建订单成功 | 订单ID:{} | 用户ID:{} | 商品ID:{}", orderId, userId, productId);
return orderId;
}
}
3. 代理类:订单服务静态代理
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 静态代理:订单服务代理(附加权限校验+日志)
*/
public class OrderServiceStaticProxy implements OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderServiceStaticProxy.class);
// 持有目标对象引用
private final OrderService target;
public OrderServiceStaticProxy(OrderService target) {
this.target = target;
}
@Override
public String createOrder(String userId, String productId) {
// 1. 前置处理:权限校验
if (!checkPermission(userId)) {
log.error("【权限校验】用户ID:{} 无创建订单权限", userId);
throw new RuntimeException("无权限创建订单");
}
// 2. 前置处理:日志记录(入参)
long startTime = System.currentTimeMillis();
log.info("【代理日志】开始创建订单 | 用户ID:{} | 商品ID:{}", userId, productId);
// 3. 调用目标对象核心方法
String orderId;
try {
orderId = target.createOrder(userId, productId);
} catch (Exception e) {
// 异常处理
log.error("【代理日志】创建订单异常 | 用户ID:{} | 商品ID:{}", userId, productId, e);
throw e;
}
// 4. 后置处理:日志记录(耗时)
long costTime = System.currentTimeMillis() - startTime;
log.info("【代理日志】订单创建完成 | 订单ID:{} | 耗时:{}ms", orderId, costTime);
return orderId;
}
/**
* 权限校验逻辑(代理附加功能)
*/
private boolean checkPermission(String userId) {
// 模拟:仅白名单用户可创建订单
return "U10086".equals(userId) || "U10010".equals(userId);
}
}
4. 客户端调用
public class StaticProxyClient {
public static void main(String[] args) {
// 1. 创建目标对象
OrderService target = new OrderServiceImpl();
// 2. 创建代理对象(关联目标)
OrderService proxy = new OrderServiceStaticProxy(target);
// 3. 调用代理方法(间接访问目标)
try {
// 有权限用户
String orderId1 = proxy.createOrder("U10086", "P8888");
System.out.println("有权限用户创建订单结果:" + orderId1);
// 无权限用户
String orderId2 = proxy.createOrder("U9999", "P8888");
System.out.println("无权限用户创建订单结果:" + orderId2);
} catch (Exception e) {
System.out.println("调用失败:" + e.getMessage());
}
}
}
输出结果
【代理日志】开始创建订单 | 用户ID:U10086 | 商品ID:P8888
【核心业务】创建订单成功 | 订单ID:ORDER_1740400000000_U10086 | 用户ID:U10086 | 商品ID:P8888
【代理日志】订单创建完成 | 订单ID:ORDER_1740400000000_U10086 | 耗时:10ms
有权限用户创建订单结果:ORDER_1740400000000_U10086
【权限校验】用户ID:U9999 无创建订单权限
调用失败:无权限创建订单
四、实战案例 2:JDK 动态代理(Spring AOP 核心)
静态代理的问题:每加一个接口就要写一个代理类,代码冗余。动态代理可在运行期动态生成代理类,无需手动编写,是 Spring AOP 的底层实现。
1. JDK 动态代理核心类:InvocationHandler
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* JDK动态代理处理器(通用代理逻辑,可代理任意接口)
*/
public class JdkDynamicProxyHandler implements InvocationHandler {
private static final Logger log = LoggerFactory.getLogger(JdkDynamicProxyHandler.class);
// 目标对象(任意接口实现类)
private final Object target;
public JdkDynamicProxyHandler(Object target) {
this.target = target;
}
/**
* 代理核心方法:所有目标方法调用都会走这里
* @param proxy 代理对象
* @param method 目标方法
* @param args 方法入参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 前置处理:通用权限校验(适配任意接口的userId参数)
String userId = args != null && args.length > 0 ? args[0].toString() : "";
if (!checkPermission(userId)) {
log.error("【动态代理-权限校验】用户ID:{} 无权限调用方法:{}", userId, method.getName());
throw new RuntimeException("无权限调用方法");
}
// 2. 前置处理:通用日志
long startTime = System.currentTimeMillis();
log.info("【动态代理-日志】开始调用方法:{} | 入参:{}", method.getName(), args);
// 3. 调用目标方法
Object result;
try {
result = method.invoke(target, args); // 反射调用目标方法
} catch (Exception e) {
log.error("【动态代理-异常】方法调用失败:{}", method.getName(), e);
throw e;
}
// 4. 后置处理:通用耗时统计
long costTime = System.currentTimeMillis() - startTime;
log.info("【动态代理-日志】方法调用完成:{} | 结果:{} | 耗时:{}ms", method.getName(), result, costTime);
return result;
}
/**
* 生成代理对象(通用方法)
*/
@SuppressWarnings("unchecked")
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 类加载器
target.getClass().getInterfaces(), // 目标对象实现的接口(JDK代理必须有接口)
this // 代理处理器
);
}
/**
* 通用权限校验
*/
private boolean checkPermission(String userId) {
return "U10086".equals(userId) || "U10010".equals(userId);
}
}
2. 客户端调用(可代理任意接口)
public class JdkDynamicProxyClient {
public static void main(String[] args) {
// 1. 目标对象1:订单服务
OrderService orderTarget = new OrderServiceImpl();
// 2. 创建动态代理处理器
JdkDynamicProxyHandler orderProxyHandler = new JdkDynamicProxyHandler(orderTarget);
// 3. 生成代理对象
OrderService orderProxy = orderProxyHandler.getProxy();
// 4. 调用代理方法(订单服务)
try {
String orderId = orderProxy.createOrder("U10086", "P8888");
System.out.println("订单服务调用结果:" + orderId);
} catch (Exception e) {
System.out.println("订单服务调用失败:" + e.getMessage());
}
// ------------------------------
// 扩展:代理其他接口(如用户服务)
// ------------------------------
// 目标对象2:用户服务
UserService userTarget = new UserServiceImpl();
JdkDynamicProxyHandler userProxyHandler = new JdkDynamicProxyHandler(userTarget);
UserService userProxy = userProxyHandler.getProxy();
// 调用代理方法(用户服务)
try {
String userInfo = userProxy.getUserInfo("U10010");
System.out.println("用户服务调用结果:" + userInfo);
} catch (Exception e) {
System.out.println("用户服务调用失败:" + e.getMessage());
}
}
// 新增:用户服务接口(演示通用代理)
interface UserService {
String getUserInfo(String userId);
}
// 新增:用户服务实现
static class UserServiceImpl implements UserService {
@Override
public String getUserInfo(String userId) {
return "用户ID:" + userId + ",姓名:张三";
}
}
}
输出结果
【动态代理-日志】开始调用方法:createOrder | 入参:[U10086, P8888]
【核心业务】创建订单成功 | 订单ID:ORDER_1740400000001_U10086 | 用户ID:U10086 | 商品ID:P8888
【动态代理-日志】方法调用完成:createOrder | 结果:ORDER_1740400000001_U10086 | 耗时:12ms
订单服务调用结果:ORDER_1740400000001_U10086
【动态代理-日志】开始调用方法:getUserInfo | 入参:[U10010]
【动态代理-日志】方法调用完成:getUserInfo | 结果:用户ID:U10010,姓名:张三 | 耗时:1ms
用户服务调用结果:用户ID:U10010,姓名:张三
五、实战案例 3:CGLIB 动态代理(无接口场景)
JDK 动态代理只能代理接口,CGLIB 可代理任意类(包括无接口类),通过生成目标类的子类实现代理(Spring 默认优先用 JDK,无接口则用 CGLIB)。
1. 依赖准备
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2. CGLIB 代理核心类:MethodInterceptor
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
/**
* CGLIB动态代理处理器(可代理任意类,无需接口)
*/
public class CglibDynamicProxyHandler implements MethodInterceptor {
private static final Logger log = LoggerFactory.getLogger(CglibDynamicProxyHandler.class);
// 目标对象(任意类)
private final Object target;
public CglibDynamicProxyHandler(Object target) {
this.target = target;
}
/**
* 生成代理对象(子类)
*/
@SuppressWarnings("unchecked")
public <T> T getProxy() {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 设置父类(目标类)
enhancer.setCallback(this); // 设置回调(代理逻辑)
return (T) enhancer.create(); // 生成子类(代理对象)
}
/**
* 代理核心方法:目标方法调用都会走这里
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 1. 前置处理:通用日志
long startTime = System.currentTimeMillis();
log.info("【CGLIB代理-日志】开始调用方法:{} | 入参:{}", method.getName(), args);
// 2. 调用目标方法(通过MethodProxy,比反射更快)
Object result;
try {
result = proxy.invokeSuper(obj, args); // 调用父类(目标类)方法
} catch (Exception e) {
log.error("【CGLIB代理-异常】方法调用失败:{}", method.getName(), e);
throw e;
}
// 3. 后置处理:耗时统计
long costTime = System.currentTimeMillis() - startTime;
log.info("【CGLIB代理-日志】方法调用完成:{} | 结果:{} | 耗时:{}ms", method.getName(), result, costTime);
return result;
}
}
3. 客户端调用(代理无接口类)
public class CglibDynamicProxyClient {
public static void main(String[] args) {
// 目标对象:无接口的商品服务类
ProductService productTarget = new ProductService();
// 创建CGLIB代理处理器
CglibDynamicProxyHandler proxyHandler = new CglibDynamicProxyHandler(productTarget);
// 生成代理对象(子类)
ProductService productProxy = proxyHandler.getProxy();
// 调用代理方法
String productInfo = productProxy.getProductInfo("P8888");
System.out.println("CGLIB代理调用结果:" + productInfo);
}
// 无接口的商品服务类(演示CGLIB代理)
static class ProductService {
public String getProductInfo(String productId) {
// 核心业务逻辑
return "商品ID:" + productId + ",名称:小米14 Pro,价格:4999元";
}
}
}
输出结果
【CGLIB代理-日志】开始调用方法:getProductInfo | 入参:[P8888]
【CGLIB代理-日志】方法调用完成:getProductInfo | 结果:商品ID:P8888,名称:小米14 Pro,价格:4999元 | 耗时:1ms
CGLIB代理调用结果:商品ID:P8888,名称:小米14 Pro,价格:4999元
六、代理模式的典型实战场景
1. Spring AOP(核心场景)
- Spring AOP 的底层是JDK/CGLIB 动态代理 ;
- 代理附加逻辑:事务管理(@Transactional)、权限校验(@PreAuthorize)、日志(@Slf4j)、缓存(@Cacheable);
- 示例:@Transactional注解本质是代理在方法执行前开启事务,执行后提交 / 回滚。
2. 远程代理(RPC 框架)
- 如 Dubbo、Feign 的远程调用代理:
- 本地代理对象:Feign 接口的动态代理;
- 目标对象:远程服务器的服务;
- 代理逻辑:封装网络请求、序列化 / 反序列化、负载均衡。
3. 延迟加载(虚拟代理)
- 如 MyBatis 的 Mapper 接口代理:
- 代理对象:Mapper 接口的动态代理;
- 目标对象:SQL 执行逻辑(延迟加载);
- 核心:调用 Mapper 方法时才执行 SQL,而非初始化时。
4. 保护代理(权限控制)
- 如 Spring Security 的权限校验:
- 代理对象:受保护的接口;
- 代理逻辑:调用前校验用户权限,无权限则拒绝访问。
5. 缓存代理(结果复用)
- 如 MyBatis 的一级 / 二级缓存:
- 代理逻辑:调用方法前查缓存,有则直接返回,无则调用目标方法并缓存结果。
七、代理模式与装饰器模式的区别(易混淆点)
很多人会混淆代理模式和装饰器模式,核心区别如下:
| 特性 | 代理模式 | 装饰器模式 |
|---|---|---|
| 核心目的 | 控制访问(如鉴权、限流) | 增强功能(如加日志、缓存) |
| 对目标对象的感知 | 代理知道目标对象,客户端不知道 | 客户端知道目标对象,主动装饰 |
| 附加逻辑的位置 | 前置 / 后置(如先鉴权再调用) | 环绕(增强核心逻辑) |
| 组合方式 | 代理包裹目标对象 | 装饰器层层包裹 |
| 典型场景 | AOP、RPC、权限控制 | 动态加功能、IO 流 |
总结
- 代理模式的核心是通过代理对象控制对目标对象的访问 ,可附加前置 / 后置逻辑(鉴权、日志、缓存等);
- 静态代理简单但冗余,JDK 动态代理(接口)和 CGLIB 动态代理(任意类)是实战主流,也是 Spring AOP 的底层;
- 代理模式是开发中最核心的设计模式之一,广泛应用于 AOP、RPC、权限控制、缓存等场景,掌握它能理解框架的核心设计思想。
扩展
基于代理模式实现一个Spring AOP + 自定义注解 + 切面的完整实战案例,我会贴合真实开发场景(接口日志 + 耗时统计 + 异常捕获),清晰拆解 Spring AOP 如何通过动态代理(JDK/CGLIB)实现无侵入的功能增强,代码可直接集成到 Spring Boot 项目中。
一、核心原理回顾
Spring AOP 的底层是动态代理模式:
- 抽象主题 :被代理的业务接口 / 类(如UserService);
- 真实主题 :业务接口的实现类(如UserServiceImpl);
- 代理对象 :Spring 动态生成的 JDK/CGLIB 代理类;
- 代理逻辑 :切面(Aspect)中定义的额外功能(日志、耗时、异常),由 Spring 自动织入到目标方法的执行流程中。
二、实战场景定义
实现一个通用的接口增强注解@ApiLog,功能包括:
- 记录接口入参、出参;
- 统计接口执行耗时;
- 捕获接口异常并记录;
- 支持通过注解参数开关功能(如是否记录出参)。
三、完整代码实现(Spring Boot 3.x)
1. 依赖准备(Maven)
<dependencies>
<!-- Spring Boot核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.2.3</version>
</dependency>
<!-- Spring AOP核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>3.2.3</version>
</dependency>
<!-- JSON格式化 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.43</version>
</dependency>
<!-- Lombok(简化代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2. 步骤 1:定义自定义注解@ApiLog
import java.lang.annotation.*;
/**
* 自定义注解:接口日志增强
* 标注在方法/类上,触发AOP代理增强
*/
@Target({ElementType.METHOD, ElementType.TYPE}) // 可标注在方法/类上
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,允许反射获取
@Documented // 生成文档
public @interface ApiLog {
/**
* 是否记录入参(默认true)
*/
boolean recordInput() default true;
/**
* 是否记录出参(默认true)
*/
boolean recordOutput() default true;
/**
* 是否统计耗时(默认true)
*/
boolean recordCostTime() default true;
/**
* 接口描述(默认取方法名)
*/
String desc() default "";
}
3. 步骤 2:定义切面类(核心代理逻辑)
import com.alibaba.fastjson2.JSON;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.UUID;
/**
* 切面类:实现@ApiLog注解的代理逻辑
* 核心:通过Around通知环绕目标方法,添加日志/耗时/异常处理(动态代理的前置/后置逻辑)
*/
@Slf4j
@Aspect // 标记为切面类
@Component // 交给Spring管理
public class ApiLogAspect {
/**
* 定义切入点:匹配所有标注@ApiLog的方法/类
*/
@Pointcut("@annotation(com.example.demo.annotation.ApiLog) || @within(com.example.demo.annotation.ApiLog)")
public void apiLogPointcut() {}
/**
* 环绕通知:核心代理逻辑(对应动态代理的invoke方法)
* ProceedingJoinPoint:连接点,包含目标方法、入参等信息
*/
@Around("apiLogPointcut()")
public Object aroundApiLog(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 获取注解配置和请求上下文
ApiLog apiLog = getApiLogAnnotation(joinPoint);
String requestId = "REQ_" + UUID.randomUUID().toString().substring(0, 16);
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 2. 解析目标方法信息
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
String desc = apiLog.desc().isEmpty() ? methodName : apiLog.desc();
try {
// 3. 前置处理:记录入参(代理的前置逻辑)
if (apiLog.recordInput()) {
String params = JSON.toJSONString(joinPoint.getArgs());
log.info("【API日志-开始】requestId={} | 接口描述={} | 类名={} | 方法名={} | 请求URL={} | 请求方式={} | 入参={}",
requestId, desc, className, methodName, request.getRequestURL(), request.getMethod(), params);
}
// 4. 调用目标方法(对应动态代理的method.invoke)
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 执行目标方法
long costTime = System.currentTimeMillis() - startTime;
// 5. 后置处理:记录出参+耗时(代理的后置逻辑)
if (apiLog.recordOutput()) {
String output = JSON.toJSONString(result);
log.info("【API日志-成功】requestId={} | 接口描述={} | 耗时={}ms | 出参={}",
requestId, desc, costTime, output);
} else if (apiLog.recordCostTime()) {
log.info("【API日志-成功】requestId={} | 接口描述={} | 耗时={}ms",
requestId, desc, costTime);
}
return result;
} catch (Exception e) {
// 6. 异常处理:记录异常信息
log.error("【API日志-异常】requestId={} | 接口描述={} | 类名={} | 方法名={} | 异常信息={}",
requestId, desc, className, methodName, e.getMessage(), e);
throw e; // 抛出异常,不影响上层全局异常处理
}
}
/**
* 获取方法/类上的@ApiLog注解
*/
private ApiLog getApiLogAnnotation(ProceedingJoinPoint joinPoint) {
// 先获取方法上的注解
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
ApiLog methodAnnotation = method.getAnnotation(ApiLog.class);
if (methodAnnotation != null) {
return methodAnnotation;
}
// 方法上没有则获取类上的注解
Class<?> targetClass = joinPoint.getTarget().getClass();
return targetClass.getAnnotation(ApiLog.class);
}
}
4. 步骤 3:定义业务接口和实现类
import com.example.demo.annotation.ApiLog;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
* 业务控制器:演示@ApiLog注解的使用
*/
@RestController
// 类上标注:该类所有方法都启用日志(可被方法注解覆盖)
@ApiLog(desc = "用户管理接口", recordOutput = true)
public class UserController {
/**
* 示例1:查询用户(使用类上的注解配置)
*/
@GetMapping("/user/query")
public UserVO queryUser(String userId) {
// 模拟业务逻辑:查询数据库
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new UserVO(userId, "张三", "13800138000", 25);
}
/**
* 示例2:创建用户(方法注解覆盖类注解)
*/
@PostMapping("/user/create")
@ApiLog(desc = "创建用户接口", recordInput = true, recordOutput = false) // 不记录出参,仅记录入参+耗时
public String createUser(@RequestBody UserDTO userDTO) {
// 模拟业务逻辑:插入数据库
return "SUCCESS: 用户" + userDTO.getUserName() + "创建成功";
}
/**
* 示例3:异常场景测试
*/
@GetMapping("/user/exception")
@ApiLog(desc = "异常测试接口")
public String testException() {
throw new RuntimeException("模拟接口异常:用户ID不存在");
}
// 数据模型
@Data
@NoArgsConstructor
@AllArgsConstructor
static class UserDTO {
private String userName;
private String phone;
private Integer age;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class UserVO {
private String userId;
private String userName;
private String phone;
private Integer age;
}
}
5. 步骤 4:启动类(开启 AOP)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* 启动类:开启AOP代理(Spring Boot 2.x+默认开启,显式声明更清晰)
*/
@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass = true) // proxyTargetClass=true:强制使用CGLIB代理(无接口也能代理)
public class AopProxyDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AopProxyDemoApplication.class, args);
}
}
四、测试验证(Postman / 浏览器调用)
1. 测试查询用户接口(GET)
请求:http://localhost:8080/user/query?userId=U1001
日志输出:
【API日志-开始】requestId=REQ_1234567890abcdef | 接口描述=用户管理接口 | 类名=com.example.demo.controller.UserController | 方法名=queryUser | 请求URL=http://localhost:8080/user/query | 请求方式=GET | 入参=["U1001"]
【API日志-成功】requestId=REQ_1234567890abcdef | 接口描述=用户管理接口 | 耗时=51ms | 出参={"age":25,"phone":"13800138000","userId":"U1001","userName":"张三"}
2. 测试创建用户接口(POST)
请求:
POST http://localhost:8080/user/create
Body: {"userName":"李四","phone":"13800138001","age":30}
日志输出:
【API日志-开始】requestId=REQ_abcdef1234567890 | 接口描述=创建用户接口 | 类名=com.example.demo.controller.UserController | 方法名=createUser | 请求URL=http://localhost:8080/user/create | 请求方式=POST | 入参=[{"age":30,"phone":"13800138001","userName":"李四"}]
【API日志-成功】requestId=REQ_abcdef1234567890 | 接口描述=创建用户接口 | 耗时=2ms
3. 测试异常接口(GET)
请求:http://localhost:8080/user/exception
日志输出:
【API日志-开始】requestId=REQ_9876543210fedcba | 接口描述=异常测试接口 | 类名=com.example.demo.controller.UserController | 方法名=testException | 请求URL=http://localhost:8080/user/exception | 请求方式=GET | 入参=[]
【API日志-异常】requestId=REQ_9876543210fedcba | 接口描述=异常测试接口 | 类名=com.example.demo.controller.UserController | 方法名=testException | 异常信息=模拟接口异常:用户ID不存在
五、核心知识点拆解(代理模式视角)
1. Spring AOP 与动态代理的对应关系
| 动态代理组件 | Spring AOP 组件 | 作用 |
|---|---|---|
| InvocationHandler | Aspect(切面) | 封装代理逻辑(日志、耗时、异常) |
| invoke 方法 | Around 通知 | 环绕目标方法,执行前置 / 核心 / 后置逻辑 |
| method.invoke | joinPoint.proceed() | 调用目标方法(真实业务逻辑) |
| Proxy.newProxyInstance | Spring 自动生成代理对象 | 根据目标类是否有接口,选择 JDK/CGLIB 生成代理对象 |
2. 关键注解说明
- @Aspect:标记类为切面类(代理逻辑的容器);
- @Pointcut:定义切入点(哪些方法需要被代理);
- @Around:环绕通知(最灵活的通知类型,对应动态代理的 invoke 方法);
- @EnableAspectJAutoProxy:开启 AOP 代理(Spring Boot 2.x + 可省略,默认开启)。
3. JDK vs CGLIB 代理选择
- proxyTargetClass = false(默认):目标类有接口则用 JDK 代理,无则用 CGLIB;
- proxyTargetClass = true:强制使用 CGLIB 代理(即使有接口);
- 实战建议:设置proxyTargetClass = true,避免因接口问题导致代理失效(如 Controller 无接口)。
六、生产级扩展建议
1. 性能优化
- 缓存 Method 对象:避免每次反射获取注解,提升性能;
- 异步日志:日志写入异步执行,避免阻塞接口响应;
- 过滤敏感参数:如密码、身份证号,日志中脱敏处理。
2. 功能扩展
- 添加限流逻辑:在切面中集成 Guava RateLimiter,实现接口限流;
- 添加权限校验:结合 Spring Security,校验用户权限;
- 添加链路追踪:集成 SkyWalking/Zipkin,记录 traceId。
3. 异常处理
- 自定义业务异常:捕获后返回友好提示;
- 全局异常拦截:配合@ControllerAdvice,统一返回格式。
总结
- Spring AOP 的核心是动态代理模式 ,通过切面(Aspect)封装代理逻辑,无需修改业务代码即可实现功能增强;
- 自定义注解 + 切面是 Spring AOP 的经典用法,核心步骤:定义注解→定义切面(切入点 + 环绕通知)→业务方法标注注解;
- 环绕通知(@Around)对应动态代理的 invoke 方法,可灵活实现前置(入参日志)、核心(执行目标方法)、后置(出参 / 耗时)、异常处理逻辑。
