1.AOP介绍
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
1.1 AOP通俗解释
我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。
按照正常的逻辑,我们可以这么做。
这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。
同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。
1.2 AOP中的相关概念
- Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
- Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
- Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
- Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
- Target(目标对象):织入 Advice 的目标对象.。
- Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
1.3 过滤器、拦截器、AOP功能区别
1.3.1 过滤器和拦截器的区别
从一个请求到返回数据的整个过程看,首先是请求进入Servlet,在Servlet中存在过滤器。对url进行拦截。后经过分发器后,会进入到spring容器,在请求尚未到达具体的controller时,会先经过拦截器的preHandle方法。方法通过后,进入业务层处理,返回实体,在进入postHandle方法。经过JSP后会进入afterCompletion方法,返回页面ModleAndView.
1.3.2 AOP
面向切面拦截的是类的元数据(包、类、方法名、参数等),拦截是在controller层的类,接口,或者service层的类,方法的执行前后进行拦截。
相对于拦截器更加细致,而且非常灵活,拦截器只能针对URL做拦截,而AOP针对具体的代码,能够实现更加复杂的业务逻辑。
1.3.3 过滤器、拦截器、AOP使用场景
三者功能类似,但各有优势,从过滤器–>拦截器–>切面,拦截规则越来越细致,执行顺序依次是过滤器、拦截器、切面。一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。
比如权限校验,一般情况下,所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验;日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此使用过滤器不能细致地划分模块,此时应该考虑拦截器,然而拦截器也是依据URL做规则匹配,因此相对来说不够细致,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能.
1.3.4 AfterReturning
AfterReturning源码:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface AfterReturning { String value() default ""; String pointcut() default ""; String returning() default ""; String argNames() default ""; }
returning:自定义的变量,标识目标方法的返回值
pointcut/value:这两个属性的作用是一样的,它们都属于指定切入点对应的切入表达式。一样既可以是已有的切入点,也可直接定义切入点表达式。当指定了pointcut属性值后,value属性值将会被覆盖。
2.使用@Before, @AfterReturning自定义注解的AOP实际使用场景
2.1 引入Pom依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2.2 自定义注解
/** * @Author lucky * @Date 2022/1/26 15:33 * 统计耗时 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ExecuteTime { }
2.3 AOP绑定自定义注解。并在aop中实现处理逻辑
package com.ttbank.flep.core.aspect; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.NamedThreadLocal; import org.springframework.stereotype.Component; /** * @Author lucky * @Date 2022/1/26 15:35 */ @Aspect @Component @Slf4j public class ExecuteTimeAspect { private static final ThreadLocal<Long> beginTimeThreadLocal = new NamedThreadLocal<>("ThreadLocal beginTime"); //切点:决定用注解方式的方法切还是针对某个路径下的所有类和方法进行切,方法必须是返回void类型 @Pointcut("@annotation(com.ttbank.flep.core.aspect.ExecuteTime)") private void logTimeCalculateCut(){ } /** * 前置通知 (在方法执行之前返回)用于拦截Controller层记录用户的操作的开始时间 * @param joinPoint 切点 */ @Before("logTimeCalculateCut()") public void doBefore(JoinPoint joinPoint) throws InterruptedException{ beginTimeThreadLocal.set(System.currentTimeMillis()); } /** * 后置通知(在方法执行之后并返回数据) 用于拦截Controller层无异常的操作 */ @AfterReturning(pointcut = "logTimeCalculateCut()",returning = "ret") public void after(Object ret){ Long executeTime=System.currentTimeMillis()-beginTimeThreadLocal.get(); log.info("执行时间为:"+executeTime); log.info("sleepTime:"+ret); } }
2.4 AOP的使用
用在接口层校验登陆的角色是否有权限使用该接口
/** * <p> * 前端控制器 * </p> * * @author lucky * @since 2021-11-25 */ @RestController @RequestMapping("/student") @Slf4j public class StudentController { @Autowired private IStudentService studentService; @PostMapping("/getStudentName") @ExecuteTime public int getStudentName(){ log.info("studentName:lucky"); Random random=new Random(); int sleepTime = random.nextInt(10000); try { Thread.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } return sleepTime; } }
postman测试:
控制台输出:
3.@Around简单使用示例——SpringAOP增强处理
3.1 @Around的作用
- 既可以在目标方法之前织入增强动作,也可以在执行目标方法之后织入增强动作;
- 可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止目标目标方法的执行;
- 可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值; 当需要改变目标方法的返回值时,只能使用Around方法;
虽然Around功能强大,但通常需要在线程安全的环境下使用。因此,如果使用普通的Before、AfterReturing增强方法就可以解决的事情,就没有必要使用Around增强处理了。
3.2 注解方式
如果需要对某一方法进行增强,只需要在相应的方法上添加上自定义注解即可
(1)定义注解类
package com.ttbank.flep.aspect; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Author lucky * @Date 2022/6/27 9:47 */ @Retention(RetentionPolicy.RUNTIME)//运行时有效 @Target(ElementType.METHOD)//作用于方法 public @interface MyAnnotation { String methodName () default ""; }
(2) 定义增强处理类(切面类)
package com.ttbank.flep.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; /** * @Author lucky * @Date 2022/6/27 9:44 */ @Aspect //标注增强处理类(切面类) @Component //交由Spring容器管理 public class AnnotationAspect { /* 可自定义切点位置,针对不同切点,方法上的@Around()可以这样写ex:@Around(value = "methodPointcut() && args(..)") @Pointcut(value = "@annotation(com.rq.aop.common.annotation.MyAnnotation)") public void methodPointcut(){} @Pointcut(value = "@annotation(com.rq.aop.common.annotation.MyAnnotation2)") public void methodPointcut2(){} */ //定义增强,pointcut连接点使用@annotation(xxx)进行定义 @Around(value = "@annotation(around)") //around 与 下面参数名around对应 public Object processAuthority(ProceedingJoinPoint point, MyAnnotation around) throws Throwable{ System.out.println("ANNOTATION welcome"); System.out.println("ANNOTATION 调用方法:"+ around.methodName()); System.out.println("ANNOTATION 调用类:" + point.getSignature().getDeclaringTypeName()); System.out.println("ANNOTATION 调用类名" + point.getSignature().getDeclaringType().getSimpleName()); Object result = point.proceed();//调用目标方法 System.out.println("ANNOTATION login success"); return result; } }
(3)Controller
package com.ttbank.flep.controller; import com.ttbank.flep.aspect.MyAnnotation; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; /** * @Author lucky * @Date 2022/6/27 9:49 */ @RestController @RequestMapping("/hello") public class HelloController { @PostMapping("/login") @MyAnnotation(methodName = "login") public void login(String name){ System.out.println("hello!"+name); } }
@RestController可以使得postman能够接收返回值,回显在postman页面中;而@Controller并不能;
postman测试:
控制台输出:
3.3 匹配方法执行连接点方式
@Aspect @Component @Order(0) //设置优先级,值越低优先级越高 public class ExecutionAspect { @Around(value = "execution(* com.ttbank.flep.controller..*.*(..))") public Object processAuthority (ProceedingJoinPoint point)throws Throwable{ System.out.println("EXECUTION welcome"); System.out.println("EXECUTION 调用方法:" + point.getSignature().getName()); System.out.println("EXECUTION 目标对象:" + point.getTarget()); //System.out.println("EXECUTION 首个参数:" + point.getArgs()[0]); Object proceed = point.proceed(); System.out.println("EXECUTION success"); return proceed; } }
注意:切面需要返回执行结果,否则会导致返回结果丢失;
eg.
任意公共方法的执行:execution(public * *(..))
任何一个以“set”开始的方法的执行:execution(* set*(..))
AccountService 接口的任意方法的执行:execution(* com.xyz.service.AccountService.*(..))
定义在service包里的任意方法的执行: execution(* com.xyz.service.*.*(..))
定义在service包和所有子包里的任意类的任意方法的执行:execution(* com.xyz.service..*.*(..))
控制台输出:
切面执行顺序:
参考文献:
https://blog.csdn.net/zhanglf02/article/details/89787937
https://blog.csdn.net/qq_41981107/article/details/85260765 (经典)
https://www.cnblogs.com/niceyoo/p/10907203.html