作者:墨镜猛女班长 | 来源:互联网 | 2023-09-18 15:28
1、什么是AOP
AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(两外两个:IOC-控制反转、DI-依赖注入)
个人理解:Aop是在业务流程中增加新的通用流程时,如添加日志,统计方法运行时间等时,做一个拦截,在方法执行前后,都可以做额外的动作,可以做一些无关业务流程的工作如添加日志,也可以对业务流程进行干涉,如对参数进行修改,返回值进行修改等。类似python的装饰器的作用。
2、Aop工作流程
1、定义一个切点Pointcut,切点主要是用来定义在哪里切入,比如对所有的get请求进行请求前添加日志,那么就需要针对所有@GetMapping注解的方法进行拦截
2.定义一个针对切点的处理方法Advice,这个主要来定义什么时候进行处理,方法执行前,方法执行后(专业术语:前置和后置处理)
3.代码实现
1、引入依赖
org.springframework.bootspring-boot-starter-aop
2、创建AOP类
使用@Aspect进行注解,来标记是一个切面类
3、定义切点方法
使用@Pointcut进行方法注解,注解参数表明是对什么方法或类等进行拦截
该注解有两种类型的参数:一个是使用 execution()
,另一个是使用 annotation()
execution表达式可以详细参考
execution表达式详解
execution (* com.sample.service.impl..*.*(..))
annotation()
方式是针对某个注解来定义切点
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
4、定义处理方法
有5个注解
1.@Before 标注的方法在切面切入目标方法之前执行
2.@After 标注的方法在切面切入目标方法之后执行
3.@Arround 可以自由选择增强动作与目标方法的执行顺序,也就是说可以在增强动作前后,甚至过程中执行目标方法,因为被@Arround标注的方法第一个形参必须是ProceedingJoinPoint
类型,调用ProceedingJoinPoint
参数的procedd()
方法才会执行目标方法,这个方法的调用时机自己可以进行控制,没有调用ProceedingJoinPoint
的proceed
方法,则目标方法不会执行。
@Around
可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值
调用ProceedingJoinPoint
的proceed
方法时,还可以传入一个Object[ ]
对象,该数组中的值将被传入目标方法作为实参,所以可以改变执行目标方法的参数。传入的Object[ ]
数组长度与目标方法所需要的参数必须相等。
4、@AfterReturning
注解和 @After
有些类似,区别在于 @AfterReturning
注解可以用来捕获切入方法执行完之后的返回值,对返回值进行业务逻辑上的增强处理
5、当被切方法执行过程中抛出异常时,会进入 @AfterThrowing
注解的方法中执行,在该方法中可以做一些异常的处理逻辑
6、多个
5、示例
package com.ljx.splearn.AopDemo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.util.HashMap;
import java.util.Map;/*** @author lijianxi* @date 2022年11月09日 10:58 上午*/
@Order(0)
//标注该类是切面类
@Aspect
//交给spring容器
@Component
public class LogAdvice {//@Pointcut 注解定义什么时机进行拦截,要拦截的是什么东西,一个是使用 execution(),另一个是使用 annotation()。//annotation() 方式是针对某个注解来定义切点,execution指明哪些类或包或方法被执行的表达式//表明什么时机进行切入,本例中是被getmapping注解到的方法被调用时(参数是一个注解,表示被该注解标注的方法调用时进行切入)@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")private void pointCut(){}public long t1 ;public long t2;//指定的方法在切面切入目标方法之前执行,注入后做什么动作,参数是切点方法@Before("pointCut()")public void logAdvice(JoinPoint joinPoint){// 获取签名Signature signature = joinPoint.getSignature();// 获取切入的包名String declaringTypeName = signature.getDeclaringTypeName();// 获取即将执行的方法名String funcName = signature.getName();System.out.println("执行前开始记录");t1 = System.currentTimeMillis();}//指定的方法在切面切入目标方法之后执行@After("pointCut()")public void logAdvice1(JoinPoint joinPoint) throws Throwable {Object[] args = joinPoint.getArgs();String name =(String) args[0];System.out.println("结束记录1"+name);t2=System.currentTimeMillis();System.out.println("执行时间"+(t2-t1));}//自由选择增强动作与目标方法的执行顺序@Around("pointCut()")//方法参数必须是ProceedingJoinPoint,而不能是JoinPointpublic Object logAdvice2(ProceedingJoinPoint joinPoint) throws Throwable {//获取请求参数Object[] args = joinPoint.getArgs();String name =(String) args[0];System.out.println("结束记录2"+name);t2=System.currentTimeMillis();System.out.println("执行时间1"+(t2-t1));//修改参数args[0]="王五";joinPoint.proceed(args);//修改返回值return "hello ,zhangsan";}/*** 在上面定义的切面方法返回后执行该方法,可以捕获返回对象或者对返回对象进行增强* @param joinPoint joinPoint* @param result result* 属性 returning 的值必须要和参数保持一致*/@AfterReturning(pointcut = "pointCut()", returning = "result")public void doAfterReturning(JoinPoint joinPoint, Object result) {Signature signature = joinPoint.getSignature();String classMethod = signature.getName();System.out.println((String.format("方法%s执行完毕",classMethod)));System.out.println(result);// 实际项目中可以根据业务做具体的返回值增强System.out.println("对返回参数进行业务上的增强:{}"+result + "增强版");}/*** 在上面定义的切面方法执行抛异常时,执行该方法* @param joinPoint jointPoint* @param ex ex*/@AfterThrowing(pointcut = "pointCut()", throwing = "ex")public void afterThrowing(JoinPoint joinPoint, Throwable ex) {Signature signature = joinPoint.getSignature();String method = signature.getName();// 处理异常的逻辑System.out.println((String)(String.format("执行方法{}出错,异常为:{}", method, ex)));}}
package com.ljx.splearn.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.sql.Time;/*** @author lijianxi* @date 2022年11月09日 10:55 上午*/
@RestController
@RequestMapping("/demo")
public class DemoController {@GetMapping("/hello")String sayHello(String name) throws InterruptedException {System.out.println("传递过来参数是"+name);Thread.sleep(2000);return "hello"+name;}
}
发起请求,参数是lisi,中间对参数修改传递到controller层时是王五,最终结果被@Around修改
最终返回是zhangsan
日志打印