SpringBoot学习小结之AOP

25 篇文章 3 订阅
18 篇文章 0 订阅

一、概述

  • AOP,全称 Aspect Oriented Programming,中文名面向切面编程。

  • AOP是通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。

  • AOP是传统OOP的一种延续和增强,使用AOP可以降低了业务之间的耦合,提高了代码的灵活性和可拓展性。

  • AOPJava中比较有名的实现有AspectJSpring AOP

    • AspectJeclipse名下的一个子项目,可以说是AOPJava中最早也最全面的实现。AspectJ提供了很多强大的功能,详细信息可以见官网:https://www.eclipse.org/aspectj/doc/released/progguide/index.html

    • Spring AOP是后起之秀,它开发的目的不是提供完整的AOP实现,而是为了和Spring IOC集成, 帮助解决开发中的常见问题。

二、术语

  1. Aspect(切面): 散布于不同业务但功能相同的代码从业务逻辑中抽取出来,封装成独立的模块,这些独立的模块被称为切面,代表软件开发中需要关注的一个方面。例如事务,日志。
  2. Jointcut(连接点): 程序中执行的某一点。在Spring AOP特指某个方法执行,在AspectJ中还可以指定字段赋值取值、构造方法执行等。
  3. Advice(通知增强): 在连接点处采取的操作,通知增强的执行时机包含before,after ,around,after return, after throwing等。
  4. Pointcut(切点): 匹配连接点的操作(表达式)。这是AOP的核心,Spring AOP默认使用AspectJ Pointcut表达式。
  5. Introduction(引介): 在类级别上添加额外的方法或字段。Spring AOP 可以向任何Advice添加新的接口方法
  6. Target object(目标对象): 被一个或多个切面Advice的对象。Spring AOP中指的是被代理的对象
  7. Aop proxy(Aop代理对象): 指的是由Aop框架创建,用来实现切面的对象。Spring AOP中的代理对象指的是由JDK动态代理或Cglib动态代理创建的对象
  8. Weaving(织入): 连接切面和应用程序的对象用来创建Advice。Spring AOP在运行时织入。使用AspectJ可以在编译前将增强代码织入.java文件中,也可以在编译后织入.class文件中,当然,也可以在运行时织入

三、注解

序号注解名含义
1@Aspect标记此类包含各种pointcut, advice, and introduction定义
需要加上@Component被Spring检测到
2@Pointcut定义一个切点,标记的方法必须是void返回值
3@Before定义一个Before Advice,标记的方法在所匹配连接点方法执行前执行
4@After定义一个After Advice,标记的方法在所匹配连接点方法执行后执行
5@AfterReturning定义一个After returning Advice,标记的方法在所匹配连接点方法返回值后执行. 属性returning用来设置Adive方法签名中要将返回值绑定到的参数的名称
6@AfterThrowing定义一个After throwing Advice,标记的方法在所匹配连接点方法抛出异常后执行。属性throwing用来设置Adive方法签名中要将异常绑定到的参数的名称
7@Around定义一个Around Advice,标记的方法第一个参数必须是ProceedingJoinPoint,在Advice方法体内需要调用proceed()方法;方法返回值最好是Object,否则如果切点匹配的方法有返回值会拿不到。

Spring AOP支持以下在@Pointcut中使用的Aspect切点表达式

  • execution 最主要的表达式,表示方法执行

    语法规则如下,?代表可选

    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
    
    • modifiers-pattern 修饰符(public等) . 支持通配符*
    • ret-type-pattern 方法返回值类型 支持通配符*, 可以匹配任何返回值类型
    • declaring-type-pattern 方法所属类类型 ,指定类的全路径,支持通配符*
    • name-pattern 方法名字, 支持通配符*,可用来部分或全部匹配方法名字.
    • param-pattern 参数,支持通配符*.. , 其中*代表一个任意类型的参数,而..代表零个或多个任意类型的参数。例如,()匹配一个不接受任何参数的方法,而(..)匹配一个接受任意数量参数的方法,(*)匹配了一个接受一个任何类型的参数的方法,(*,String)匹配了一个接受两个参数的方法,其中第一个参数是任意类型,第二个参数必须是String类型。
    • throws-pattern 异常,支持通配符*

    示例

    序号表达式说明
    1execution( public * *(..))任何public方法的执行
    2execution(* set*(..))任何以set开头的方法
    3execution(* com.xyz.service.AccountService.*(..))AccountService这个接口的所有方法
    4execution(* com.xyz.service.*.*(..))com.xyz.service包下所有方法
    com.xyz.service.*表示包的类
    .*表示所有方法
    5execution(* com.xyz.service..*.*(..))com.xyz.service包下及子包所有方法
    com.xyz.service..*表示包及子包
    6execution(* com.xyz.service.*.*())com.xyz.service包下不带参数的方法
    7execution(* com.xyz.service.*.*(..) throws Exception)com.xyz.service包下声明带throws Exception异常的方法

    以下并不常见,具体可以查看官方文档

  • within 匹配指定包或类的方法执行

  • this 匹配当前AOP代理对象的类型,必须是类型全限定名,不能包含通配符

  • target 匹配当前目标对象类型的执行方法,必须是类型全限定名,不能包含通配符

  • args 匹配当前执行的方法传入的参数为指定类型的执行方法,注意是匹配传入的参数类型,不是匹配方法签名的参数类型;参数类型列表中的参数必须是类型全限定名,通配符不支持;args属于动态切入点,这种切入点开销非常大,非特殊情况最好不要使用

  • @target@args@within@annotation

四、执行顺序

以下顺序指增强一个方法Method的各个Advice顺序

单个切点

单个切点匹配到Method

Around Before Method After AfterReturning 执行proceed() 之前的代码 正常执行 执行proceed() 之后的代码 Around Before Method After AfterReturning Method正常运行切面Advice顺序



Around Before Method After AfterThrowing 执行proceed() 之前的代码 抛出异常 Around Before Method After AfterThrowing Method运行异常切面Advice顺序

多个切点

多个切点匹配到Method, 还是以Method执行为核心, 正常执行如下,异常执行类似。多个切点可以加Order注解配置顺序,值越低优先级越高,越先执行。相同优先级就是包加类字符串排序的顺序。

在这里插入图片描述

五、流程及源码分析

SpringAOPIOC紧密相连,当我们配置好AOP后启动,IOC容器生成Bean,如果这个Bean中的方法被AOP匹配到,那么IOC实际会生成一个Bean代理对象。下面以一个例子来简单分析一下流程

@Aspect
@Component
public class OneAop {

	private final Logger log = LoggerFactory.getLogger(this.getClass());
	@Pointcut("execution(* net.guides.springboot2.springboot2jpacrudexample.controller.UserController.*(..))")
	public void pointCut() {
	}
    
	/**
	 * Run before the method execution.
	 * @param joinPoint
	 */
	@Before("pointCut()")
	public void logBefore(JoinPoint joinPoint) {
		log.debug("logBefore running .....");
	}

	/**
	 * Run after the method returned a result.
	 * @param joinPoint
	 */
	@After("pointCut()")
	public void logAfter(JoinPoint joinPoint) {
		log.debug("logAfter running .....");
	}

	/**
	 * Run after the method returned a result, intercept the returned result as well.
	 * @param joinPoint
	 * @param result
	 */
	@AfterReturning(pointcut = "pointCut()", returning = "result")
	public void logAfterReturning(JoinPoint joinPoint, Object result) {
		log.debug("logAfterReturning running .....");
	}

	/**
	 * Run around the method execution.
	 * @param joinPoint
	 * @return
	 * @throws Throwable
	 */
	@Around("pointCut()")
	public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
		log.debug("logAround before running .....");
		Object result = joinPoint.proceed();
		log.debug("logAround after running .....");
		return result;
	}

	/**
	 * Advice that logs methods throwing exceptions.
	 *
	 * @param joinPoint join point for advice
	 * @param error         exception
	 */
	@AfterThrowing(pointcut = "pointCut()", throwing = "error")
	public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
		log.debug("logAfterThrowing running .....");
	}
}

UserController

@RestController
public class UserController {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);
  
    @GetMapping("/exception")
    public Object exception() throws Exception{
        logger.info("controller exception invoke");
        throw new Exception("异常");
    }
    
    @GetMapping("/test")
    public void test() {
        logger.info("controller invoke");
    }

}

  1. 最重要的一个类AbstractAutoProxyCreator, 这是BeanPostProcess的实现类,实现了postProcessAfterInitialization方法,会通过wrapIfNecessary判断IOC容器中的Bean是不是需要代理

    在这里插入图片描述

  2. wrapIfNecessary中会为Bean获取Advice, 获取不到就是DO_NOT_PROXY

    在这里插入图片描述

  3. 先获取全部Advice,再查找能够匹配Bean的Advice,还会进行排序. 如下图,Advisor是Advice基本接口

    在这里插入图片描述
    在这里插入图片描述

  4. 在获取Advice时,会从带有@Aspect注解的Bean中查找

    在这里插入图片描述

  5. 从Aspect中拿Advice,会先从缓存拿,拿不到从工厂创建

    在这里插入图片描述

  6. 通过工厂创建,会遍历Aspect中方法,查找Advice相关的生成

    在这里插入图片描述

  7. 拿到所有Advice,会找出匹配Bean的Advice

    在这里插入图片描述

  8. 有Advice的Bean就可以为它生成代理对象了

    在这里插入图片描述

  9. 用Cglib代理,还是JDK代理

    在这里插入图片描述

参考

  1. https://blog.csdn.net/qq_26323323/article/details/81012855
  2. https://docs.spring.io/spring/docs/5.2.8.RELEASE/spring-framework-reference/core.html#aop
  3. https://blog.csdn.net/qq_23167527/article/details/78623639
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aabond

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值