揭秘自定义注解,背后的面向切面编程(AOP)的艺术

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

01 引言

开发过程中,有没有被别人的自定义注解搞得晕头转向,不知道怎么实现的,只知道那么用。其实背后使用的就是Spring两大特性中的一个AOP,也就是面向切面编程。

面向切面编程解耦了我们的业务逻辑,代码侵入性小。还记得我们之前分享的关于声明式事务的注解@Transactional,就是基于AOP实现的。

今天,我们通过自定义注解,了解其背后的艺术。

02 AOP是什么

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在解决横切关注点(Cross-Cutting Concerns)带来的代码分散和重复问题。这些关注点(如日志、事务、安全、性能监控)往往"横切"多个核心业务模块,导致代码重复、核心逻辑被污染且维护困难等。

Spring AOPSpring 框架对 AOP 思想的实现。它基于动态代理(JDK ProxyCGLIB),在运行时为目标对象创建代理,将横切逻辑(通知 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()) 不会触发 AOPAOP 只能作用于 Spring IoC 容器管理的 Bean

Spring AOP 主要支持方法执行连接点(不支持构造器、字段访问等),切点表达式要严格按照AspectJ 切点表达式语法。 多个切面作用于同一连接点时,可通过 @Order 注解或实现 Ordered 接口指定执行顺序。

选择合适的通知方式,否则无法达到预期的效果。

06 小结

Spring AOP 是解决横切关注点、提升代码模块化和可维护性的利器。它通过声明式的方式(切面、切点、通知)将非核心业务逻辑从核心业务中剥离出来。理解其核心概念、掌握注解配置方式、熟悉常见应用场景,并注意其实现机制的限制,就能有效运用 Spring AOP 构建更清晰、更健壮、更易维护的应用程序。

自定义注解是AOP的一个完美体现,赶快定义属于自己的注解吧

相关推荐
菠萝013 分钟前
共识算法Raft系列(1)——什么是Raft?
c++·后端·算法·区块链·共识算法
长勺5 分钟前
Spring中@Primary注解的作用与使用
java·后端·spring
紫乾20148 分钟前
idea json生成实体类
java·json·intellij-idea
wh_xia_jun14 分钟前
在 Spring Boot 中使用 JSP
java·前端·spring boot
网安INF15 分钟前
CVE-2020-17518源码分析与漏洞复现(Flink 路径遍历)
java·web安全·网络安全·flink·漏洞
Y第五个季节19 分钟前
docker-部署Nginx以及Tomcat
java·开发语言
IT-ZXT88823 分钟前
Tomcat 线程模型详解&性能调优
java·tomcat
小奏技术1 小时前
基于 Spring AI 和 MCP:用自然语言查询 RocketMQ 消息
后端·aigc·mcp
编程轨迹1 小时前
面试官:如何在 Java 中读取和解析 JSON 文件
后端
lanfufu1 小时前
记一次诡异的线上异常赋值排查:代码没错,结果不对
java·jvm·后端