1.Aop是什么
我们都知道Spring中最关键的两大特性:IOC和AOP两者相辅相成,成就现在的Spring框架;
AOP叫面向切面编程,帮助我们在很多切入点进行增强、扩充操作,如果是传统我们就需要在所有类开头加上日志开始,末尾加上日志结束,这样不仅编程效率低下,浪费时间,代码冗余,维护也麻烦,万一有需求换方式记录日志,可不得改死;
AOP我们可以使用在很多地方,如日志模块,权限控制,事务处理等;
AOP的具体实现有两种方式
- JDK动态代理 (需要被代理类实现一个接口,本质是创建一个代理类也实现该接口,然后对该方法进行增强,扩展)
- CGLib字节码处理 (直接对编译好的字节码进行修改,修改对应的方法,本质直接继承该被代理类,所以该类不能设置为final)
2.Aop使用方式
在Spring中要实现aop通常我们会定义一个Aspect类,他使用的是org.aspectj.lang包下的类来帮助我们引入aop模块;
具体使用方式: 1.定义一个Aspect类 添加@Aspect注解、添加@Component注解将该类纳入spring容器; 2.定义切点 使用@PointCut注解定义在方法上,并标明想切入的点(方法) 3.对该切点进行增强 使用@Before、@Around等注解对该方法(@PointCut注解的方法名!!)
具体用法如下
@Aspect
@Component
public class logAspect {
// 扫描所有controller类的方法
@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
public void log(){
}
@Before(value = "log()")
public void before(JoinPoint joinPoint){
System.out.println("[Aspect] before log method!");
}
@Around(value = "log()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("[Aspect] around log before method!");
proceedingJoinPoint.proceed();
System.out.println("[Aspect] around log after method!");
}
@After(value = "log()")
public void after(){
System.out.println("[Aspect] after log method!");
}
@AfterReturning(value = "log()")
public void afterReturning(){
System.out.println("[Aspect] afterReturning log method!");
}
@AfterThrowing(value = "log()")
public void afterThrowing(){
System.out.println("[Aspect] afterThrowing log method!");
}
}
3.具体注解作用
1.@Pointcut
使用参数value可以定义切点 @Pointcut(value = "execution(* com.exam.exam.controller..(..))") 扫描所有controller包下所有类中所有返回值的方法
@Pointcut("@within(com.exam.exam.logaspect.LogAnnotation)") 扫描所有LogAnnotation注解标识的类,这个类以及它的子孙类都能被增强。
within()与execution()的功能类似,两者的区别是,within()定义的连接点的最小范围是类级别的(不是接口),而execution()定义的连接点的最小范围可以精确到方法的入参,因此可以认为execution()涵盖了within()的功能。
@Pointcut("within(com.exam.exam.entity.User)") 扫描该类及其子类,当注入使用的时候对它进行增强
target() 匹配目标类以及子类,目标类以及其子类的所有方法执行都会匹配到 @target:若当前类有注解,则对该类继承、自有、重载的方法有效。若子类无注解,则无效果。 @Annotation 匹配使用了该注解的类
@target和@within区别:两者都用于注解类 1.我们对被@target、@within注解的类中的方法会进行增强,但只有@within子类重写的方法会生效; 2.对于被@target注解的子类,若子类调用父类的方法,而没有重写父类的方法那么就会被增强,但@within不会 3.对于被@within注解的父类,若父类方法子类没有重写,那么子类调用父类的方法,就会被增强,但@target不会 (2,3区别在于一个是@target子类调用父类的方法,一个是子类调用@within父类的方法) 4.@target、@within对子类新增方法都不起作用
2.@Before
用于切面前置动作,在执行方法前增强动作 一般两种用法
- 定义@Pointcut方法,对该方法进行前置动作
@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
public void log(){
}
@Before(value = "log()")
public void before(JoinPoint joinPoint){
System.out.println("[Aspect] before log method!");
}
- 直接使用@within、@target等对注解进行切入
@Before("@target(com.exam.exam.logaspect.LogAnnotation)")
public void before1(){
}
3.@After
用于切面后置动作,在执行方法后增强动作
- 定义@Pointcut方法,对该方法进行后置动作
@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
public void log(){
}
@After(value = "log()")
public void After(JoinPoint joinPoint){
System.out.println("[Aspect] After log method!");
}
- 直接使用@within、@target等对注解进行切入
@After("@within(com.exam.exam.logaspect.LogAnnotation)")
public void After(){
}
4.@Around
用于切面环绕动作,对方法进行环绕操作
- 对切面进行环绕,前后操作;
@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
public void log(){
}
@Around(value = "log()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("[Aspect] around log before method!");
proceedingJoinPoint.proceed();
System.out.println("[Aspect] around log after method!");
}
- 直接使用@within、@target等对注解进行切入
@Around(value = "@within(com.exam.exam.logaspect.LogAnnotation)")
public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("[Aspect] around log before method!");
proceedingJoinPoint.proceed();
System.out.println("[Aspect] around log after method!");
}
5.@afterReturning
在方法返回后进行的后置处理
- 定义@Pointcut方法,对该方法进行返回后置动作
@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
public void log(){
}
@AfterReturning(value = "log()")
public void afterReturning(){
System.out.println("[Aspect] afterReturning log method!");
}
- 直接使用@within、@target等对注解进行切入
@AfterReturning(value = "@within(com.exam.exam.logaspect.LogAnnotation)")
public void afterReturning(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("[Aspect] afterReturning log method!");
}
6.@AfterThrowing
在方法抛出异常后进行的后置处理
- 定义@Pointcut方法,对该方法进行返回后置动作
@Pointcut(value = "execution(* com.exam.exam.controller.*.*(..))")
public void log(){
}
@AfterThrowing(value = "log()")
public void AfterThrowing(){
System.out.println("[Aspect] AfterThrowing log method!");
}
- 直接使用@within、@target等对注解进行切入
@AfterThrowing(value = "@within(com.exam.exam.logaspect.LogAnnotation)")
public void AfterThrowing(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("[Aspect] AfterThrowing log method!");
}
4.参数ProceedingJoinPoint和JoinPoint
环绕通知(around) ProceedingJoinPoint 执行proceed方法的作用是让目标方法执行,这也是环绕通知和前置、后置通知方法的一个最大区别。
Proceedingjoinpoint 继承了 JoinPoint 。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。
5.调用顺序
执行流程图:
1.当一个Aspect的时候
# 执行顺序
[Aspect] around log before method!
[Aspect] before log method!
doing test aspect!
[Aspect] afterReturning log method!
[Aspect] after log method!
[Aspect] around log after method!
2.当两个Aspect的时候
# 执行顺序
[Aspect] around log before method!
[Aspect] before log method!
[Aspect2] around log before method!
[Aspect2] before log method!
doing test aspect!
[Aspect2] afterReturning log method!
[Aspect2] after log method!
[Aspect2] around log after method!
[Aspect] afterReturning log method!
[Aspect] after log method!
[Aspect] around log after method!
3.当抛出异常的时候
[Aspect] around log before method!
[Aspect] before log method!
[Aspect] afterThrowing log method!
[Aspect] after log method!
2021-03-13 20:52:26.730 ERROR 21632 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: error] with root cause
java.lang.RuntimeException: error
我们可以看到afterReturning没有执行,around-after没有执行 after执行了!
参考:https://www.jianshu.com/p/fb109e03edec
评论