SpringAop的快速入门
SpringAop的快速入门
AOP
AOP:Aspect Oriented Programming(面向切面编程),是一种编程范式,将通用功能(如日志、事务、权限)从核心业务逻辑中剥离,用于实现“横向切割”的代码复用。 此外AOP对原有代码是没有任何侵入性的,不需要修改任何的业务代码。
AOP的核心概念
-
**连接点(JoinPoint):**连接点指的是可以被aop控制的方法。
-
**通知(Advice):**对重复逻辑抽取形成的通用方法简称为通知。
-
**切入点(PointCut):**匹配特定条件的连接点,通知仅会在切入点方法执行时被应用;切入点就是实际被aop控制的方法。
-
切面:Aspect,描述通知与切入点的对应关系,简单来说切面就是通知+切入点的组合,而切面所在的类我们常称之为切面类。
-
目标对象:Target,通知所作用的对象被称之为目标对象。
SpringAop的快速入门
SpringAOP是Spring框架中对AOP思想的实现。
1). 引入相关依赖
<!-- 引入SpringAop相关依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2). 编写AOP切面类
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Slf4j
@Component
@Aspect//标识这是一个切面类
public class DemoAspect {//前置通知@Before("execution(* com.ryuki.springaopquickstart.service.imp.*.say*(..))")public void before() {log.info(" before... ");}//环绕通知@Around("execution(* com.ryuki.springaopquickstart.service.imp.*.say*(..))")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info(" around... before ... ");//SpringAop会将连接点信息都封装到joinPoint中,通过jointPoint调用目标对象的原始方法执行Object result = joinPoint.proceed();log.info(" around... after ... ");return result;}//目标方法正确返回后通知@AfterReturning("execution(* com.ryuki.springaopquickstart.service.imp.*.say*(..))")public void afterReturning() {log.info(" afterReturning... ");}//目标方法抛出异常后通知@AfterThrowing("execution(* com.ryuki.springaopquickstart.service.imp.*.say*(..))")public void afterThrowing() {log.info(" afterThrowing... ");}//后置通知@After("execution(* com.ryuki.springaopquickstart.service.imp.*.say*(..))")public void after() {log.info(" after... ");}}
在切面类上添加@Aspect
注解标识这是一个切面类,同时将给切面类交由IOC容器管理,只有交由IOC管理这个切面类才能够生效。
启动服务器,访问相关资源查看控制台日志信息如下:
- 程序正常执行情况下:目标方法执行前后插入对应通知中的逻辑。
- 程序异常运行:
aop核心概念的再认识
- 连接点(JointPoint):
- 切入点(PointCut):
- 通知(Advice):
- 切面(Aspect):
通知类型
在通知方法前加上相应的注解指定通知类型:
通知类型 | 解释 |
---|---|
@Around | 环绕通知,此注解标注的通知方法在目标方法前、后都被执行 |
@Before | 前置通知,此注解标注的通知方法在目标方法前被执行 |
@After | 后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常抛出 |
@AfterReturning | 目标方法正确返回后通知,出现异常不会执行 |
@AfterThrowing | 目标方法抛出异常后通知,只有目标方法抛出异常才会之执行 |
观察上面程序控制台输出日志,我们可以发现通知类型的不同控制着执行时机的不同,同时@AfterReturning 和@AfterThrowing这两个注解是互斥的,不会同时执行。
注意:
SpringAop会将对应的连接点信息封装到ProceedingJoinPoint中,且@Around环绕通知会在目标方法执行前后执行。
因此@Around环绕通知需要自己调用 ProceedingJoinPoint类提供的proceed() 来让原始方法执行,且环绕通知方法必须指定返回值类型为Object来接受原始方法的返回值。
切入点表达式
描述切入点方法的表达式叫做切入点表达式,切入点表达式是用来决定SpringAOP实际控制那些方法。
-
常见的形式:
-
- @annotation(……) :基于注解匹配切入点
- execution(…):根据方法签名匹配切入点
@annotation(…)
@Component
@Aspect
@Slf4j
public class Aspect1 {@Before("@annotation(com.ryuki.springaopquickstart.anno.Operation)")public void before() {log.info("annotation.....before... ");}@After("@annotation(com.ryuki.springaopquickstart.anno.Operation)")public void after() {log.info("annotation....after... ");}
}
execution(…)
execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法如下:
execution([访问修饰符] 返回值 [包名.类名.]方法名(方法参数) [throws 异常?])
特别说明:带[]的部分可省略不写
我们可以配合通配符来描述切入点:
*
:匹配单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分..
:匹配多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
@Component
@Aspect
@Slf4j
public class Aspect2 {@Before("execution(public String com.ryuki.springaopquickstart.service.imp.*.*(..))")public void before() {log.info("execution.....before... ");}@After("execution(* com.ryuki.springaopquickstart.service.imp.*.*(..))")public void after() {log.info("execution....after... ");}
}
前面编写的AOP入门切面类中,每一个注解的切点表达式都相同,此时我们可以通过@PointCut
注解将共性的切点表达式抽取出来;定义一个切入点方法,在该方法上通过@PointCut注解抽取公共的切点表达式,需要时直接引入即可:
package com.ryuki.springaopquickstart.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Slf4j
@Component
@Aspect
public class DemoAspect {//切入点方法@Pointcut("execution(* com.ryuki.springaopquickstart.service.imp.*.*(..))")public void pointcut() {}//后续直接引入该切入点即可//前置通知@Before("pointcut()")public void before() {log.info(" before... ");}//环绕通知@Around("pointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info(" around... before ... ");//SpringAop会将连接点信息都封装到joinPoint中,通过jointPoint调用目标对象的原始方法执行Object result = joinPoint.proceed();log.info(" around... after ... ");return result;}//目标方法正确返回后通知@AfterReturning("pointcut()")public void afterReturning() {log.info(" afterReturning... ");}//目标方法抛出异常后通知@AfterThrowing("pointcut()")public void afterThrowing() {log.info(" afterThrowing... ");}//后置通知@After("pointcut()")public void after() {log.info(" after... ");}}
多个切面类同类型通知的执行顺序
多个切面类中,相同类型的通知,默认是按照切面类的类名字母排序:
目标方法前的通知方法:字母排名靠前的先执行
目标方法后的通知方法:字母排名靠前的后执行
一般情况下,切面类的类名都是具有特殊意义的,我们无法通过类名来控制通知的执行顺序,因此我们可以使用Spring提供的@Order
注解来控制通知的执行顺序。
@Component
@Aspect
@Slf4j
@Order(3)
public class Aspect3 {@Before("execution(* com.ryuki.springaopquickstart.service.imp.*.*(..))")public void before() {log.info("Aspect3.....before... ");}@After("execution(* com.ryuki.springaopquickstart.service.imp.*.*(..))")public void after() {log.info("Aspect3....after... ");}
}====================================================@Component
@Aspect
@Slf4j
@Order(2)
public class Aspect4 {@Before("execution(* com.ryuki.springaopquickstart.service.imp.*.*(..))")public void before() {log.info("Aspect4.....before... ");}@After("execution(* com.ryuki.springaopquickstart.service.imp.*.*(..))")public void after() {log.info("Aspect4....after... ");}
}====================================================@Component
@Aspect
@Slf4j
@Order(1)
public class Aspect5 {@Before("execution(* com.ryuki.springaopquickstart.service.imp.*.*(..))")public void before() {log.info("Aspect5.....before... ");}@After("execution(* com.ryuki.springaopquickstart.service.imp.*.*(..))")public void after() {log.info("Aspect5....after... ");}
}
对于前置通知而言,@Order中数字越小越先执行;后置通知中@Order数字越小越后执行。
小结
SpringAOP底层是基于动态代理技术来实现的,当程序运行时会自动基于动态代理技术为目标对象生成一个相应的代理对象,代理对象就会基于切面类中的逻辑对目标对象当中的原始方法进行功能上的增强。
此外,在我们注入相应的bean对象时注入的是SpringAOP创建的代理对象: