Spring AOP 注解概述
1、Spring 的 AOP 功能除了在配置文件中配置一大堆的配置,比如切入点、表达式、通知等等以外,使用注解的方式更为方便快捷,特别是 Spring boot 出现以后,基本不再使用原先的 beans.xml 等配置文件了,而都推荐注解编程。
@Aspect | 切面声明,标注在类、接口(包括注解类型)或枚举上。 |
@Pointcut | 切入点声明,即切入到哪些目标类的目标方法。
value 属性指定切入点表达式,默认为 “”,用于被通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式 |
@Before | 前置通知, 在目标方法(切入点)执行之前执行。
value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式 注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知。 |
@After | 后置通知, 在目标方法(切入点)执行之后执行 |
@AfterRunning | 返回通知, 在目标方法(切入点)返回结果之后执行,在 @After 的后面执行
pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 “” |
@AfterThrowing | 异常通知, 在方法抛出异常之后执行, 意味着跳过返回通知
pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 “” 注意:如果目标方法自己 try-catch 了异常,而没有继续往外抛,则不会进入此回调函数 |
@Around | 环绕通知:目标方法执行前后分别执行一些代码,发生异常的时候执行另外一些代码 |
2、上面这些 AOP 注解都是位于如下所示的 aspectjweaver 依赖中:
3、对于习惯了 Spring 全家桶编程的人来说,并不是需要直接引入 aspectjweaver 依赖,因为 spring-boot-starter-aop 组件默认已经引用了 aspectjweaver 来实现 AOP 功能。换句话说 Spring 的 AOP 功能就是依赖的 aspectjweaver !
1 2 3 4 5 6 |
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.1.4.RELEASE</version> </dependency> |
@Aspect 快速入门
1、@Aspect 常见用于记录日志、异常集中处理、权限验证、Web 参数校验、事务处理等等
2、要想把一个类变成切面类,只需3步:
1)在类上使用 @Aspect 注解使之成为切面类
2)切面类需要交由 Sprign 容器管理,所以类上还需要有 @Service、@Repository、@Controller、@Component 等注解 |
3、AOP 的含义就不再累述了,下面直接上示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.SourceLocation; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.Arrays; /** * 切面注解 Aspect 使用入门 * 1、@Aspect:声明本类为切面类 * 2、@Component:将本类交由 Spring 容器管理 * 3、@Order:指定切入执行顺序,数值越小,切面执行顺序越靠前,默认为 Integer.MAX_VALUE * * @author wangMaoXiong * @version 1.0 * @date 2020/8/20 19:22 */ @Aspect @Order(value = 999) @Component public class AspectHelloWorld { /** * @Pointcut :切入点声明,即切入到哪些目标方法。value 属性指定切入点表达式,默认为 ""。 * 用于被下面的通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式 * <p> * 切入点表达式常用格式举例如下: * - * com.wmx.aspect.EmpService.*(..)):表示 com.wmx.aspect.EmpService 类中的任意方法 * - * com.wmx.aspect.*.*(..)):表示 com.wmx.aspect 包(不含子包)下任意类中的任意方法 * - * com.wmx.aspect..*.*(..)):表示 com.wmx.aspect 包及其子包下任意类中的任意方法 * </p> */ @Pointcut(value = "execution(* com.wmx.aspect.EmpServiceImpl.*(..))") private void aspectPointcut() { } /** * 前置通知:目标方法执行之前执行以下方法体的内容。 * value:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式 * <br/> * * @param joinPoint:提供对连接点处可用状态和有关它的静态信息的反射访问<br/> <p> * Object[] getArgs():返回此连接点处(目标方法)的参数 * Signature getSignature():返回连接点处的签名。 * Object getTarget():返回目标对象 * Object getThis():返回当前正在执行的对象 * StaticPart getStaticPart():返回一个封装此连接点的静态部分的对象。 * SourceLocation getSourceLocation():返回与连接点对应的源位置 * String toLongString():返回连接点的扩展字符串表示形式。 * String toShortString():返回连接点的缩写字符串表示形式。 * String getKind():返回表示连接点类型的字符串 * </p> */ @Before(value = "aspectPointcut()") public void aspectBefore(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); Signature signature = joinPoint.getSignature(); Object target = joinPoint.getTarget(); Object aThis = joinPoint.getThis(); JoinPoint.StaticPart staticPart = joinPoint.getStaticPart(); SourceLocation sourceLocation = joinPoint.getSourceLocation(); String longString = joinPoint.toLongString(); String shortString = joinPoint.toShortString(); System.out.println("【前置通知】"); System.out.println("\targs=" + Arrays.asList(args)); System.out.println("\tsignature=" + signature); System.out.println("\ttarget=" + target); System.out.println("\taThis=" + aThis); System.out.println("\tstaticPart=" + staticPart); System.out.println("\tsourceLocation=" + sourceLocation); System.out.println("\tlongString=" + longString); System.out.println("\tshortString=" + shortString); } /** * 后置通知:目标方法执行之后执行以下方法体的内容,不管目标方法是否发生异常。 * value:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式 */ @After(value = "aspectPointcut()") public void aspectAfter(JoinPoint joinPoint) { System.out.println("【后置通知】"); System.out.println("\tkind=" + joinPoint.getKind()); } /** * 返回通知:目标方法返回后执行以下代码 * value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式 * pointcut 属性:绑定通知的切入点表达式,优先级高于 value,默认为 "" * returning 属性:通知签名中要将返回值绑定到的参数的名称,默认为 "" * * @param joinPoint :提供对连接点处可用状态和有关它的静态信息的反射访问 * @param result :目标方法返回的值,参数名称与 returning 属性值一致。无返回值时,这里 result 会为 null. */ @AfterReturning(pointcut = "aspectPointcut()", returning = "result") public void aspectAfterReturning(JoinPoint joinPoint, Object result) { System.out.println("【返回通知】"); System.out.println("\t目标方法返回值=" + result); } /** * 异常通知:目标方法发生异常的时候执行以下代码,此时返回通知不会再触发 * value 属性:绑定通知的切入点表达式。可以关联切入点声明,也可以直接设置切入点表达式 * pointcut 属性:绑定通知的切入点表达式,优先级高于 value,默认为 "" * throwing 属性:与方法中的异常参数名称一致, * * @param ex:捕获的异常对象,名称与 throwing 属性值一致 */ @AfterThrowing(pointcut = "aspectPointcut()", throwing = "ex") public void aspectAfterThrowing(JoinPoint jp, Exception ex) { String methodName = jp.getSignature().getName(); System.out.println("【异常通知】"); if (ex instanceof ArithmeticException) { System.out.println("\t【" + methodName + "】方法算术异常(ArithmeticException):" + ex.getMessage()); } else { System.out.println("\t【" + methodName + "】方法异常:" + ex.getMessage()); } } } |
如上所示在不修改原来业务层代码的基础上,就可以使用 AOP 功能,在目标方法执行前后或者异常时都能捕获然后执行。
在线演示源码:https://github.com/wangmaoxiong/h2Smil/tree/master/src/main/java/com/wmx/aspect
execution 切点表达式
1、@Pointcut 切入点声明注解,以及所有的通知注解都可以通过 value 属性或者 pointcut 属性指定切入点表达式。
2、切入点表达式通过 execution 函数匹配连接点,语法:execution([方法修饰符] 返回类型 方法路径(参数类型) [异常类型])
3、切入点表达式的写法比较灵活,比如:* 号表示任意一个,.. 表示任意多个,还可以使用 &&、||、! 进行逻辑运算,不过实际开发中通常用不到那么多花里胡哨的,掌握以下几种就基本够用了。
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(Integer)) | 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,且带有一个 Integer 类型参数。 |
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(*)) | 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,且带有一个任意类型参数。 |
execution(* com.wmx.aspect.EmpServiceImpl.findEmpById(..)) | 匹配 com.wmx.aspect.EmpService 类中的 findEmpById 方法,参数不限。 |
execution(* com.wmx.aspect.EmpService.*(..)) | 匹配 com.wmx.aspect.EmpService 类中的任意方法 |
execution(* com.wmx.aspect.*.*(..)) | 匹配 com.wmx.aspect 包(不含子包)下任意类中的任意方法 |
execution(* com.wmx.aspect..*.*(..)) | 匹配 com.wmx.aspect 包及其子包下任意类中的任意方法 |
转自:https://blog.csdn.net/wangmx1993328/article/details/108164388