Spring中的AOP思想

1 基本概念

1.1 关键概念

AOP 思想是和 Java 中的代理模式差不多,就是在不改变原有代码的情况下,对某些方法的功能进行增强。

其中关键的概念有:连接点、切入点、切面、通知;
连接点 :需要被增强功能的方法,以及通知所包围的原有代码段;
切入点 :需要被增强的方法;
切面 :描述切入点和通知的关系,具体地指描述某个切入点需要哪个通知;
通知:就是一些不变的代码段,需要给多个切入点添加的功能相同的代码;

1.2 注意事项

1)切入点一定是连接点,连接点不一定是切入点。

2)想要使用注解的形式使用 AOP,必须在配置类中声明注解@EnableAspectJAutoProxy

在学习中使用到的并不是 Spring 原生的 AOP,而是使用第三方的实现--AspectJ

java 复制代码
package cn.edu.njust.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * -- coding: UTF-8 -- *
 *
 * @author wangs
 * @description:
 *      EnableAspectJAutoProxy: 开启注解格式的AOP功能
 * @date 2023/11/15 10:07
 */
@Configuration
@ComponentScan("cn.edu.njust")
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

2 通知类型

即切入点方法和通知的代码谁先执行,谁后执行;

前置通知 **@Before**:在执行切入点方法前执行;
后置通知 **@After**:在执行切入点方法后执行;
环绕通知 **@Around**:在执行切入点方法前后执行,需要使用到 ProceedingJoinPoint 参数调用切入点方法,否则切入点方法不能执行;
返回后通知 **@AfterReturning**:正常执行切入点方法后执行;
抛出异常后通知 **@AfterThrowing**:一般用在程序 try-catch 块中捕捉到异常的时候执行;

3 切入点表达式

3.1 基本使用

切入点表达式声明在一个没有返回值类型没有形参没有方法实体的方法上,表达式会指定目标的访问权限(可省略)、返回值类型、权限类名(可按照一定规则简写)、抛出的异常类型(可省略);

java 复制代码
package cn.edu.njust.advice;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * -- coding: UTF-8 -- *
 *
 * @author wangs
 * @description: 定义通知
 *      需要将通知交给IoC容器管理
 *      需要将其声明为是一个切面
 * @date 2023/11/15 10:17
 */
@Component
@Aspect
public class MyAdvice {

    /*
    * 定义切入点
    *   切入点方法:无返回值、无参数、无方法实体
    * */
    // @Pointcut("execution(void cn.edu.njust.dao.impl.UserDaoImpl.update())")
    @Pointcut("execution(void cn.edu.njust.dao.UserDao.update())")
    public void pt(){}

    /*
    * 定义切面:即将切入点与通知关联
    *   method(): 这个方法本身是一个通知
    *
    * */
    @Before("pt()")
    public void method() {
        System.out.println(System.currentTimeMillis());
    }
}

说明:

1)@Component:定义的切面需要交给 IoC 容器管理;

2)@Aspect:使用注解的形式需要指明这是一个切面;

3) @Before("pt()"):如上所示,method()就是这个案例中的"通知",pt()是切入点,而实际的切入点在 pt()方法上使用@Pointcut("execution(void cn.edu.njust.dao.UserDao.update())")指明,直接定位到具体的方法;

3.2 切入点表达式的更多细节

1)基本使用

java 复制代码
// 方式1:描述到具体实现类具体方法名称
@Pointcut("execution(void cn.edu.njust.dao.impl.UserDaoImpl.update())")
// 方式2:描述到接口类接口方法
@Pointcut("execution(void cn.edu.njust.dao.UserDao.update())")

2)语法格式详细

标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)

2.1)动作关键词:execution,表示执行到具体的切入点;

2.2)访问修饰符:一般省略不写,自己通过测试发现,如果在这里将权限改小,是不能正常将切入点和通知关联的;

2.3)返回值:与切入点的返回值保持相同;

2.4)全类名+方法名:切入点的权限类名+具体方法名称;

2.5)参数:如果切入点有参数,则需将参数的类型列举出来,如 int、String 等等;

2.6)异常名:指明抛出的异常类型,一般省略不写;

3)切入点表达式的通配符

java 复制代码
// * :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
// 匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
execution(public * com.itheima.*.UserService.find*(*))


// ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
// 匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
execution(public User com..UserService.findById(..))

// +:专用于匹配子类类型
// 这个使用率较低,描述子类的,咱们做JavaEE开发,继承机会就一次,使用都很慎重,所以很少用它。*Service+,表示所有以Service结尾的接口的子类。
execution(* *..*Service+.*(..))

4 AOP 中如何获取数据

4.1 获取参数

1)环绕通知获取参数:调用 ProceedingJoinPoint 中的 getArgs() 方法即可获取一次参数的数组;

java 复制代码
@Around("pt()")
public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("---------- 环绕通知 ----------");
    System.out.println("切入点前执行");
    // 获取参数
    proceedingJoinPoint.getArgs();

    System.out.println("切入点后执行");
}

2)非环绕通知获取参数:在通知方法上使用参数 JoinPoint,调用该对象的 getArgs() 方法也可以获取对应的参数;

使用方式和环绕行类似,不在演示代码

4.2 获取返回值

能获取返回值的通知就两种,一种是环绕通知,另一种是返回后通知。

1)环绕通知:和其他正常方法一样,调用 ProceedingJoinPoint 中的 proceed() 方法,如果切入点方法有返回值,就会有返回值,直接正常返回该参数即可;

java 复制代码
@Around("pt()")
public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("---------- 环绕通知 ----------");
    System.out.println("切入点前执行");
    // 获取返回值
    proceedingJoinPoint.proceed();
    System.out.println("切入点后执行");
}

2)返回后通知:使用一个参数来接受,如下:

java 复制代码
@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(Object ret) {
	System.out.println("afterReturning advice ..."+ret);
}

4.3 获取异常

只有抛出异常后通知有获取异常的能力,也是使用参数接收的,具体格式如下:

java 复制代码
@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
	System.out.println("afterThrowing advice ..."+t);
}

5 知识汇总

5.1 注解

** 注解** 说明
@EnableAspectJAutoProxy 声明在配置类上,开启注解 AOP 功能
@Aspect 声明在切面类上,表示这是一个切面
@Pointcut 声明一个切入点
@Before 说明在通知上面,Before 表示这个通知在切入点执行前执行
@After 通知在切入点原始方法执行完之后执行
@AfterReturning 通知在切入点原始方法正常执行完毕后执行
@AfterThrowing 通知在切入点原始方法抛出异常后执行
@Around 通知在切入点原始方法执行前后执行,需要调用方法执行切入点方法

5.2 方法

** 方法原型** 说明
Object proceed() throws Throwable; 调用切入点的方法
Object[] getArgs() 获取一个切入点方法参数,封装成一个数组形式
相关推荐
唐 城19 分钟前
Solon v3.0.5 发布!(Spring 可以退休了吗?)
java·spring·log4j
Mr.朱鹏44 分钟前
操作002:HelloWorld
java·后端·spring·rabbitmq·maven·intellij-idea·java-rabbitmq
小小药2 小时前
009-spring-bean的实例化流程
java·数据库·spring
herogus丶4 小时前
【Spring AI】Spring AI Alibaba的简单使用
java·人工智能·spring·ai
奋斗的老史8 小时前
Spring Retry + Redis Watch实现高并发乐观锁
java·redis·spring
00Allen009 小时前
mybatis/mybatisplus
java·spring·mybatis
编码浪子10 小时前
Springboot3国际化
java·spring·mybatis
HUNAG-DA-PAO10 小时前
Spring AOP是什么
java·jvm·spring
带刺的坐椅10 小时前
Solon v3.0.5 发布!(Spring 生态可以退休了吗?)
java·spring·solon
小林想被监督学习12 小时前
Spring Boot 整合 RabbitMQ(在Spring项目中使用RabbitMQ)
spring boot·spring·java-rabbitmq