AOP是Spring框架的核心之一,AOP是一种思想,它的实现方法有很多,有Spring AOP,也有AspectJ、CGLIB等。我们熟知的拦截器其实就是AOP思想的一种实现方式。
AOP是一种思想,是对某一类事情的集中处理。
Spring AOP的实现方式:
想要实现Spring Aop需要先引入以下依赖。
org.springframework.boot spring-boot-starter-aop
例如:我们此时想要优化一个接口的执行效率
此时有一个接口如下:
@RequestMapping("/aop") @RestController public class Main { @Autowired private ForService fs; @RequestMapping("/fun1") public void fun1() { fs.fun(3); for (int i = 0; i < 1000; i++) {}; } }
@Service public class ForService { public int fun(int i) { for (int j = 0; j < 3000; j++) { i++; } return i; } }
我们首先需要知道这个接口在执行过程中调用的各个方法的执行时间,然后再对每个方法进行针对性优化。用AOP思想来实现:
@Slf4j @Component @Aspect public class WritTime { @Around("execution(* com.example.Spring_demo.aop.*.*(..)))") public Object time(ProceedingJoinPoint pjp) throws Throwable { //记录开始时间 long start = System.currentTimeMillis(); //执行目标方法 Object a = pjp.proceed(); //打印方法执行时间。pjp.toShortString()方法会返回方法签名的简写 log.info(pjp.toShortString()+":"+(System.currentTimeMillis()-start)+"ms"); return a; } }
切点:也称之为"切入点",提供⼀组规则(切点表达式)告诉程序对哪些方法来进行功能增强;
连接点:满足切点表达式规则的方法,就是连接点。也就是可以被AOP控制的方法;
通知:就是具体要做的工作,指哪些重复的逻辑,也就是共性功能(最终体现为⼀个方法);
切面:切点+通知。
切面类:切面所在的类,一个切面类中可以包含多个切面。
常见的切点表达式有两种表达方式:
访问修饰限定符和异常可以被省略。
* :匹配任意字符,只匹配一个元素(返回类型,包,类名,方法或者方法参数)
.. :匹配多个连续的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数
+:表示匹配当前类和其子类
现有如下接口:
@Slf4j @RequestMapping("/aop") @RestController public class Main { @RequestMapping("/fun1") public void fun1() { log.info("执行fun1方法"); } }
定义如下切面类,里面有多个切面。
@Slf4j @Component @Aspect public class WritTime { @Around("execution(* com.example.Spring_demo.aop.*.*(..)))") public Object time(ProceedingJoinPoint pjp) throws Throwable { log.info("@Around 方法前"); //执行目标方法 Object a = pjp.proceed(); log.info("@Around 方法后"); return a; } @Before("execution(* com.example.Spring_demo.aop.*.*(..)))") public void before() { log.info("@Before 前置通知"); } @After("execution(* com.example.Spring_demo.aop.*.*(..)))") public void After() { log.info("@After 后置通知"); } @AfterReturning("execution(* com.example.Spring_demo.aop.*.*(..)))") public void AfterReturning() { log.info("@AfterReturning 返回后通知"); } @AfterThrowing("execution(* com.example.Spring_demo.aop.*.*(..)))") public void AfterThrowing() { log.info("@AfterThrowing 异常后通知"); } }
如果发生异常:
异常是可以再切面中被捕获的,但需要通知类型为@Around
@Pointcut注解可以把公共的切点表达式提取出来,需要用到时引用该切入点表达式即可。
@Slf4j @Component @Aspect public class WritTime { @Pointcut("execution(* com.example.Spring_demo.aop.*.*(..)))") public void pc(){} @Around("pc()") public Object time(ProceedingJoinPoint pjp) { log.info("@Around 方法前"); //执行目标方法 Object a = null; try { a = pjp.proceed(); } catch (Throwable e) { } log.info("@Around 方法后"); return a; } @Before("pc()") public void before() { log.info("@Before 前置通知"); } @After("pc()") public void After() { log.info("@After 后置通知"); } @AfterReturning("pc()") public void AfterReturning() { log.info("@AfterReturning 返回后通知"); } @AfterThrowing("pc()") public void AfterThrowing() { log.info("@AfterThrowing 异常后通知"); } }
声明的切面表达式也可以再其他切面类中使用但需要提前声明
@Slf4j @Component @Aspect public class WritTime1 { //()里引用的切面表达式前必须加上全限定类名 @Before("com.example.Spring_demo.aop.WritTime.pc()") public void before() { log.info("WritTime1通知"); } }
@Slf4j @RequestMapping("/aop") @RestController public class Main { @RequestMapping("/fun1") public void fun1() { log.info("执行fun1方法"); } }
定义以下三个切面类,每个类里面只有一个切面:
//第一个 @Slf4j @Component @Aspect public class WritTime1 { @Before("execution(* com.example.Spring_demo.aop.*.*(..)))") public void before() { log.info("WritTime1通知"); } } //第二个 @Slf4j @Component @Aspect public class WritTime2 { @Before("execution(* com.example.Spring_demo.aop.*.*(..)))") public void before() { log.info("WritTime2通知"); } } //第三个 @Slf4j @Component @Aspect public class WritTime3 { @Before("execution(* com.example.Spring_demo.aop.*.*(..)))") public void before() { log.info("WritTime3通知"); } }
如果存在多个切面类执行顺序默认是按照类名进行排序。
当存在多个切面类时可以通过@Order注解来定义各个切面类的优先级。
对上述切面类进行细微修改加上注解
//第一个 @Slf4j @Component @Aspect @Order(3) public class WritTime1 { …… } //第二个 @Slf4j @Component @Aspect @Order(2) public class WritTime2 { …… } //第三个 @Slf4j @Component @Aspect @Order(1) public class WritTime3 { …… }
使用自定义注解来计算方法的执行时间。
声明一个自定义注解:
//表示该接口只能作用于方法 @Target({ElementType.METHOD}) //该接口的生命周期 @Retention(RetentionPolicy.RUNTIME) public @interface Time { }
使用切面类实现该注解的功能:
@Slf4j @Component @Aspect public class WritTime { //@annotation里面的值为要实现的目标注解的全限定类名 @Around("@annotation(com.example.Spring_demo.aop.Time)") public Object time(ProceedingJoinPoint pjp) throws Throwable { //记录开始时间 long start = System.currentTimeMillis(); //执行目标方法 Object a = pjp.proceed(); //打印方法执行时间。pjp.toShortString()方法会返回方法签名的简写 log.info(pjp.toShortString()+":"+(System.currentTimeMillis()-start)+"ms(注解实现)"); return a; } }
给待测方法添加注解
@Slf4j @RequestMapping("/aop") @RestController public class Main { @Autowired private ForService forService; @Time @RequestMapping("/fun1") public void fun1() { forService.fun(1); for (int i = 0; i < 10; i++) {} } }
还可以使用上面的方式给已有的注解进行功能的增强(给@RequestMapping注解添加可以打印接口执行时间的功能):
@Slf4j @Component @Aspect public class WritTime { //@annotation里面的值为@RequestMapping注解的全限定类名 @Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)") public Object time(ProceedingJoinPoint pjp) throws Throwable { //记录开始时间 long start = System.currentTimeMillis(); //执行目标方法 Object a = pjp.proceed(); //打印方法执行时间。pjp.toShortString()方法会返回方法签名的简写 log.info(pjp.toShortString()+":"+(System.currentTimeMillis()-start)+"ms(注解实现)"); return a; } }