切面通知应用增强
通知类型
在基于Spring AOP编程的过程中,基于AspectJ框架标准,spring中定义了五种类型的通知(通知-Advice描述的是一种扩展业务),它们分别是:
-
@Before。
-
@AfterReturning。目标方法执行结束没有出现异常时
-
@AfterThrowing。目标方法出现异常执行结束时
-
@After。不管目标方法有没有出现异常,只要目标方法结束
-
@Around.重点掌握(优先级最高)
说明:在切面类中使用什么通知,由业务决定,并不是说,在切面中要把所有通知都写上。
假如说我们现在有一种业务,这种业务要以某一种方式织入到我们原有的对象上去。那我们可以通过通知,去把那个业务封装起来,然后由代理对象去调用我们切面的通知方法,来为目标方法做功能扩展。而这种方式恰巧是oop的一种补充。
通知执行顺序
假如这些通知全部写到一个切面对象中,其执行顺序及过程,如图所示(但是这个顺序现在在springboot工程的不同版本中可能会有一些不同,在2.3.0之前是下面这张图,如果项目上有严谨的顺序要求时,建议使用@Around):
说明:实际项目中可能不会在切面中定义所有的通知,具体定义哪些通知要结合业务进行实现。
通知实践过程分析
这里的测试可以用我们之前写的项目进行测试(看我的基础及原理那篇)
代码如下
package com.cy.pj.common.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class SysTimeAspect {
@Pointcut("bean(mailServiceImpl)")
public void doTime(){}
@Before("doTime()")
public void doBefore(){
System.out.println("time doBefore()");
}
@After("doTime()")
public void doAfter(){
System.out.println("time doAfter()");
}
/**核心业务正常结束时执行* 说明:假如有after,先执行after,再执行returning*/
@AfterReturning("doTime()")
public void doAfterReturning(){
System.out.println("time doAfterReturning");
}
/**核心业务出现异常时执行说明:假如有after,先执行after,再执行Throwing*/
@AfterThrowing("doTime()")
public void doAfterThrowing(){
System.out.println("time doAfterThrowing");
}
@Around("doTime()")
public Object doAround(ProceedingJoinPoint jp)
throws Throwable{
System.out.println("doAround.before");
try{
Object obj=jp.proceed();
System.out.println("doAround.after");
return obj;
}catch(Throwable e){
System.out.println(e.getMessage());
throw e;
}
}
}
然后进行测试,得到的结果如下
我们发现,AfterReturning和After和我之前那张图,顺序不一样。这就是因为版本的问题。刚才我们测得是springboot2.3.4版本。
说明:对于@AfterThrowing通知只有在出现异常时才会执行,所以当做一些异常监控时可在此方法中进行代码实现。
切入点表达式增强
Spring中通过切入点表达式定义具体切入点,其常用AOP切入点表达式定义及说明:
bean表达式(重点)
bean表达式一般应用于类级别,实现粗粒度的切入点定义,案例分析:
(1)bean("userServiceImpl")指定一个userServiceImpl类中所有方法。
(2)bean("*ServiceImpl")指定所有后缀为ServiceImpl的类中所有方法。
说明:bean表达式内部的对象是由spring容器管理的一个bean对象,表达式内部的名字应该是spring容器中某个bean的name。
within表达式(了解)
within表达式应用于类级别,实现粗粒度的切入点表达式定义,案例分析:
(1)within("aop.service.UserServiceImpl")指定当前包中这个类内部的所有方法。
(2)within("aop.service.*") 指定当前目录下的所有类的所有方法。
(3)within("aop.service..*") 指定当前目录以及子目录中类的所有方法。
within表达式应用场景分析:
(1)对所有业务bean都要进行功能增强,但是bean名字又没有规则。
(2)按业务模块(不同包下的业务)对bean对象进行业务功能增强。
execution表达式(了解)
execution表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析:
语法:execution(返回值类型 包名.类名.方法名(参数列表))。
(1)execution(void aop.service.UserServiceImpl.addUser())匹配addUser方法。
(2)execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为String的addUser方法。
(3)execution(* aop.service..*.*(..)) 万能配置。aop.service包及子包下所有类中的所有方法
@annotation表达式(重点)
@annotaion表达式应用于方法级别,实现细粒度的切入点表达式定义,案例分析
@annotation(anno.RequiredLog) 匹配有此注解描述的方法。
@annotation(anno.RequiredCache) 匹配有此注解描述的方法。
其中:RequiredLog为我们自己定义的注解,当我们使用@RequiredLog注解修饰业务层方法时,系统底层会在执行此方法时进行日扩展操作。
推荐细粒度的切入点表达式使用@annotation表达式,更加灵活。