N

Norlcayn的博客

A text-focused Halo theme

  • 首页
  • 文章
  • 默认分类
  • 关于
主页 AOP面向切面编程
文章

AOP面向切面编程

发表于 2026-01-15 更新于 昨天
作者 Administrator
22~29 分钟 阅读

AOP介绍

  • AOP:Aspect Oriented Programming(面向切面编程),可以简单理解为面向特定方法编程
  • 场景: 1. 统计业务方法的执行耗时 2. 记录系统的日志操作 3. 事务管理(底层就是AOP,Spring对其封装成了@Transactional) 4. 权限控制
  • 优势: 1. 较少重复代码 2. 代码无侵入 3. 提高开发效率 4. 维护方便

AOP基础

  • 需求:统计所有业务层方法的执行耗时
  1. 导入依赖:在pom.xml中引入AOP的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<dependency>
  1. 编写AOP程序:针对特定的方法根据业务需要进行编程
@Aspect
@Component
@Slf4j
public class RecordTimeAspect {
    @Around("execution(* com.norlcyan.service.impl.*.*(..))")
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        long beginTime = System.currentTimeMillis();

        Object result = pjp.proceed();

        long endTime = System.currentTimeMillis();
        log.info("方法 {} 执行耗时: {}ms", pjp.getSignature() ,(endTime - beginTime));

        return result;
    }
}
  1. @Aspect:标识该类为 Aspect(切面类)
  2. @Around:生效的范围(环绕通知注解),可以在目标方法执行前后进行拦截和处理
  • execution:匹配方法执行连接点
  • *:返回值类型(任意)
  • com.norlcyan.service.impl.*:包路径下的所有类
  • .*:所有方法
  • (..):任意参数列表 3. pjp.proceed():可以看作是执行原始方法

  • 可以控制是否执行目标方法

  • 可以修改方法参数或返回值 4. pjp.getSignature():获取方法的签名(方便定位具体哪个方法)

AOP核心概念

  • 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
  • 可以被控制不代表已经被控制了
  • 切入点一定是连接点,连接点不一定是切入点
  • 通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)
  • 在基础案例中,recordTime就是通知方法
  • 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
  • 实际被控制的方法
  • 简单理解为就是@Around注解中的execution参数指定的方法
  • 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
  • 目标对象:Target,通知所应用的对象
切面(Aspect)
    ├── 通知(Advice)     ←→  具体的增强逻辑方法
    └── 切入点(PointCut) ←→  匹配连接点的表达式

连接点(JoinPoint)       ←→  程序中可以被增强的点
    ↑ 包含 ↑
切入点(实际匹配的连接点)  ←→  被AOP实际控制的方法

目标对象(Target)        ←→  被代理和增强的原始对象

后端Web进阶(AOP)

代理对象在Spring容器初始化时创建一次,而不是调用一次方法就创建一次代理对象

AOP进阶

通知类型

  • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都会被执行
  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  • @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  • @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  • @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行

注意:

  1. @Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
  2. @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值

示例:

@Slf4j
@Aspect
@Component
public class MyAspect1 {

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt(){}

    //前置通知 - 目标方法运行之前运行
    @Before("pt()")
    public void before(){
        log.info("before ....");
    }

    //环绕通知 - 目标方法运行之前、后运行
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("around ... before ....");

        Object result = pjp.proceed();

        log.info("around ... after ....");
        return result;
    }

    //后置通知 - 目标方法运行之后运行, 无论是否出现异常都会执行
    @After("pt()")
    public void after(){
        log.info("after ....");
    }

    //返回后通知 - 目标方法运行之后运行, 如果出现异常不会运行
    @AfterReturning("pt()")
    public void afterReturning(){
        log.info("afterReturning ....");
    }

    //异常后通知 - 目标方法运行之后运行, 只有出现异常才会运行
    @AfterThrowing("pt()")
    public void afterThrowing(){
        log.info("afterThrowing ....");
    }

}

@PointCut:该注解的作用是将公共的切入点表达式抽取出来,许需要用到时引用该切入点表达式即可

该注解修饰的方法,它的权限修饰符根据如下规则即可:

  • private:仅能在当前切面类中引用该表达式
  • public:在其他外部的切面类中也可以引用该表达式

通知顺序

  • 当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行
  • 执行顺序:
  • 不同切面类中,默认按照切面类的类名字母排序:
    • 目标方法前的通知方法:字母排名靠前的先执行
    • 目标方法后的通知方法:字母排名考前的后执行
  • 用@Order(数字)加在切面类上来控制顺序
    • 目标方法前的通知方法:数字小的先执行
    • 目标方法后的通知方法:数字小的后执行

切入点表达式

  • 介绍:描述切入点方法的一种表达式
  • 作用:用来决定项目中的哪些方法需要加入通知
  • 常见形式: 1. execution(...):根据方法的签名来匹配 2. @annotation(...):根据注解匹配

execution

  • execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配语法为:
execution(访问修饰符? 返回值? 包名.类名.?方法名(方法参数) throws 异常?)
  • 其中带?的表示可以省略的部分 1. 访问修饰符:可以省略(比如public、protected) 2. 包名.类名:可以省略(不建议省略) 3. throws异常:可以省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
  • *:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
  • 比如:execution(* com.*.service.*.update*(*))
  • ..:多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
  • 比如:execution(* com.norlcyan..DeptService.*(..))

示例:

@Before("execution(public void com.norlcyan.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
public void before(JoinPoint joinPoint) {...};

另外,execution表达式还支持逻辑运算符:

@Before("execution(* com.norlcyan.service.impl.DeptServiceImpl.list(..)) ||" +
        "execution(* com.norlcyan.service.impl.DeptServiceImpl.delete(..))"
)
public void before(JoinPoint joinPoint) {...};

上面表达式的意思是目标方法为list方法或delete方法

书写建议:

  1. 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:findXxx,updateXxx
  2. 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性
  3. 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名尽量不使用..,使用*匹配单个包

@annotation

  • @annotation切入点表达式,用于匹配标识有特定注解的方法
  • 当execution表达式过于复杂,就可以考虑这种方式
@Before("@annotation(com.norlcyan.anno.LogOperation)")
public void before() {...};
@LogOperation// 匹配到该注解
@DeleteMapping
public Result delete(Integer id) {...}

特定注解可以是自定义注解,也可以是其他依赖提供的注解

连接点

  • 在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等
  • 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint
  • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类

@Around:

@Around("execution(* com.norlcyan.service.DeptService.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
   String className = joinPoint.getTarget().getClass().getName(); //获取目标类名  
   Signature signature = joinPoint.getSignature(); //获取目标方法签名
   String methodName = joinPoint.getSignature().getName(); //获取目标方法名  
   Object[] args = joinPoint.getArgs(); //获取目标方法运行参数
   Object res = joinPoint.proceed(); //执行原始方法,获取返回值(环绕通知)
   return res;
}

其他通知:

@Before("execution(* com.itheima.service.DeptService.*(..))")
public void before(JoinPoint joinPoint) {
   String className = joinPoint.getTarget().getClass().getName(); //获取目标类名
   Signature signature = joinPoint.getSignature(); //获取目标方法签名
   String methodName = joinPoint.getSignature().getName(); //获取目标方法名
   Object[] args = joinPoint.getArgs(); //获取目标方法运行参数 
}

案例

  • 需求:将Tlias系统中的员工接口下的增、删、改操作以日志的形式记录到数据库表中
  • 日志信息包括:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值,方法执行时长
后端 Java Web入门 SpringBoot 面向切面编程
许可协议:  CC BY 4.0
分享

相关文章

下一篇

Spring MyBatis

上一篇

Linux项目部署

最近更新

  • Hello Halo
  • 状态机
  • 数据表设计规范
  • 缓存优化
  • 事务失效

热门标签

Halo 异常处理 Redisson 并行 Vue 并发 递归 请求限流 SpringAMQP 类加载器

目录

©2026 Norlcayn的博客. 保留部分权利。

使用 Halo 主题 Chirpy