关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言
开发过程中,有没有被别人的自定义注解搞得晕头转向,不知道怎么实现的,只知道那么用。其实背后使用的就是Spring
两大特性中的一个AOP
,也就是面向切面编程。
面向切面编程解耦了我们的业务逻辑,代码侵入性小。还记得我们之前分享的关于声明式事务的注解@Transactional
,就是基于AOP
实现的。
今天,我们通过自定义注解,了解其背后的艺术。
02 AOP是什么
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在解决横切关注点(Cross-Cutting Concerns)带来的代码分散和重复问题。这些关注点(如日志、事务、安全、性能监控)往往"横切"多个核心业务模块,导致代码重复、核心逻辑被污染且维护困难等。
Spring AOP
是 Spring
框架对 AOP
思想的实现。它基于动态代理(JDK Proxy
或 CGLIB
),在运行时为目标对象创建代理,将横切逻辑(通知 Advice
)织入(Weaving
)到指定的连接点(Join Point
)上。
2.1 切面
Aspect
,切面。封装横切关注点的模块,专门用来处理各个通知的实现类。注解的话,就是被@Aspect
注解修饰的类。
2.2 连接点
Join Point
,连接点。是程序代码中的一个具体体现, 执行过程中的一个点(如方法调用、异常抛出)。
2.3 切点
Pointcut
,切点。匹配连接点的表达式(@Pointcut
),定义通知在何处执行。
2.4 通知
Advice
,通知。在特定连接点执行的动作(@Before
, @After
, @Around
, @AfterReturning
, @AfterThrowing
)。
@Before
:前置通知,在方法执行之前执行@After
:后置通知,在方法执行之后执行@AfterRunning
:返回通知,在方法返回结果之后执行@AfterThrowing
:异常通知,在方法抛出异常之后执行@Around
:环绕通知,围绕着方法执行
2.5 织入
Weaving
,织入。 切面生效的关键,将切面应用到目标对象创建代理的过程。Maven的依赖如下:
xml
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
03 自定注解
我们自定义一个注解InsertLog
用来插入日志。
3.1 所需依赖
xml
<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.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
-->
spring-boot-starter-aop
里面包含了aspectjweaver
,两个只需要任选其一即可。
3.2 定义注解
java
@Target(ElementType.METHOD) // 注解用在方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
public @interface InsertLog {
/**
* 操作名称
**/
String action() default "";
/**
* 内容
**/
String content() default "";
/**
* 备注
**/
String remark() default "";
}
用来标记切点位置。
3.3 定义切面
java
@Aspect
@Component
public class LogAspect {
/**
* @Description: 切点
*
* @Author: ws
* @Date: 2025/5/29 18:30
**/
@Pointcut("@annotation(insertLog)")
public void logging(InsertLog insertLog) {}
@Around("logging(insertLog)")
public Object around(ProceedingJoinPoint joinPoint, InsertLog insertLog) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Map<String, String[]> parameterMap = request.getParameterMap();
String params = JSON.toJSONString(parameterMap);
Object result = null;
String exception = null;
try {
result = joinPoint.proceed();
}catch (Throwable e){
exception = e.getMessage();
}
// 封装日志信息
Map<String, Object> logMap = new HashMap<>();
// 请求参数
logMap.put("params", params);
// 异常信息
logMap.put("exception", exception);
// 操作
logMap.put("action", insertLog.action());
// 内容
logMap.put("content", insertLog.content());
// 备注
logMap.put("remark", insertLog.remark());
// 插入日志信息
System.out.println("日志信息:" + JSON.toJSONString(logMap));
return result;
}
}
我们这里选择了@Around
环绕通知,用来查看方法运行的结果是否有异常。
3.4 请求案例
java
@RestController
public class FooController {
@RequestMapping("/foo")
@InsertLog(action = "查询", content = "查询用户信息", remark = "测试")
public String foo(String name, Integer age) {
System.out.println("foo方法调用成功:name: " + name + ", age: " + age);
return "success";
}
}
3.5 运行结果

04 AOP的使用场景
自定注解是常用的使用方式,还可以通过Xml
标签配置AOP
。我们一起来盘一盘可以用在哪些使用场景里。
- 日志记录: 统一记录方法入参、返回值、执行时间、异常信息。
- 事务管理:
@Transactional
注解底层即基于 AOP 实现。 - 安全控制(权限校验): 在方法执行前检查用户权限。
- 性能监控: 统计方法执行耗时。
- 缓存管理: 方法执行前检查缓存,执行后更新缓存。
- 错误处理与恢复: 统一处理特定异常。
- 参数校验/数据校验: 在方法执行前校验参数合法性。
- 审计日志: 记录关键操作(如数据修改)。
- 高并发限流: 根据请求的次数、时间间隔,限制是否放行请求。
05 注意事项
Spring AOP
基于代理。调用代理对象的方法时,通知才会生效。直接调用目标对象内部方法 (this.someInternalMethod()
) 不会触发 AOP
。AOP
只能作用于 Spring IoC
容器管理的 Bean
。
Spring AOP
主要支持方法执行连接点(不支持构造器、字段访问等),切点表达式要严格按照AspectJ
切点表达式语法。 多个切面作用于同一连接点时,可通过 @Order
注解或实现 Ordered
接口指定执行顺序。
选择合适的通知方式,否则无法达到预期的效果。
06 小结
Spring AOP
是解决横切关注点、提升代码模块化和可维护性的利器。它通过声明式的方式(切面、切点、通知)将非核心业务逻辑从核心业务中剥离出来。理解其核心概念、掌握注解配置方式、熟悉常见应用场景,并注意其实现机制的限制,就能有效运用 Spring AOP 构建更清晰、更健壮、更易维护的应用程序。
自定义注解是AOP的一个完美体现,赶快定义属于自己的注解吧