AOP(面向切面编程),或多或少都听过一点。名字比较怪,切面,不容易理解,但其中真正含义,无非就是旁路控制,非侵入式编码之类。比如我想加个操作日志功能,利用AOP,无须每个操作都加一个记录功能,只需写一个,就惠泽全部。
这个是怎么做到的呢?也没有太玄妙的东西,原理类似于过滤器、拦截器,在底层和全局性的地方做了处理,各个业务功能都流经这些关卡。
一、过滤器、拦截器与AOP
据说,过滤器、拦截器、AOP三者功能类似,但各有优势,从过滤器 》拦截器 》切面,拦截规则越来越细致,执行顺序依次是过滤器、拦截器、切面。一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是AOP。
AOP使用的主要是动态代理 , 过滤器使用的主要是函数回调;拦截器使用是反射机制 。一个请求过来,先进行过滤器处理,看程序是否受理该请求 。 过滤器放过后 , 程序中的拦截器进行处理 ,处理完后进入 被 AOP动态代理重新编译过的主要业务类进行处理 。
Filter:和框架无关,过滤器拦截的是URL,可以控制最初的http请求,但是更细一点的类和方法控制不了。
Interceptor:拦截器拦截的也是URL,拦截器有三个方法,相对于过滤器更加细致,有被拦截逻辑执行前、后等。
AOP:面向切面拦截的是类的元数据(包、类、方法名、参数等) 相对于拦截器更加细致,而且非常灵活,拦截器只能针对URL做拦截,而AOP针对具体的代码,能够实现更加复杂的业务逻辑。
二、Spring Boot中应用AOP
1、pom.xml
引入依赖包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 1
- 2
- 3
- 4
2、面向切面类
1)写一个方法用于测试
为方便测试,写一个控制器。再简单不过的控制器。
TestController.java
package com.chenqu.bullshit.modules.work.controller;
@RestController
@RequestMapping("test1")
public class TestController {
@ResponseBody
@RequestMapping("/hello")
public String sayHello(){
System.out.println("hello");
return "hello";
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
2)面向切面的类
然后写一个面向切面的类。就像配置、过滤器、拦截器一样,写出来之后,系统(准确来说,应该是容器?)会自动解释它,于是面向切面开始生效。
TestAdvice.java
@Aspect
@Component
public class TestAdvice {
//指示哪些方法将会受到影响。Pointcut,切入点
@Pointcut("execution (* com.chenqu.bullshit.modules.work.controller.TestController.*(..))")
public void test1() {
}
@Before("test1()")
public void beforeAdvice() {
System.out.println("beforeAdvice...");
}
@After("test1()")
public void afterAdvice() {
System.out.println("afterAdvice...");
}
@Around("test1()")
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("before");
try {
proceedingJoinPoint.proceed();
} catch (Throwable t) {
t.printStackTrace();
}
System.out.println("after");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
上述代码中,首先类使用了标注“@Aspect”,表明这是一个面向切面的类。
类中void test1()使用了标注“@Pointcut”。Pointcut者,切入点也。它里面的内容,指示了哪些方法将会受到影响。在这里,指向了我们用于测试的控制器。如果是想指向所有的控制器,可以这样写:
@Pointcut("execution (* com.chenqu.bullshit.modules.work.controller.*.*(..))")
- 1
至于其他的什么@Before、@After、@Arround,无非就是切入前执行、切入后执行、切入时执行,顾名思义。
运行代码,用浏览器访问
http://localhost:8090/test1/hello
- 1
控制台输出如下:
3、execution 与 annotation
execution与annotation都是Pointcut的2种常用执行方式,除此而外,还有什么within、this、target之类乱七八糟的,一大群,让人头大。常用的就是execution与annotation。execution如上例,就是指明会影响哪些方法;而annotation是注解的意思,指明会影响带哪些注解的方法,或者说,如果方法带上指定的注解,就会受到影响。
1)添加一个名为NeedTest的注解
NeedTest.java
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedTest {
String value() default "";
}
- 1
- 2
- 3
- 4
- 5
- 6
2)然后将上面例子中TestAdvice.java,Pointcut的执行方式由execution 改为 annotation,其余保持不变。
@Aspect
@Component
public class TestAdvice {
@Pointcut("@annotation(com.chenqu.bullshit.modules.annotation.NeedTest)")
public void test1() {
}
@Before("test1()")
public void beforeAdvice() {
System.out.println("beforeAdvice...");
}
@After("test1()")
public void afterAdvice() {
System.out.println("afterAdvice...");
}
@Around("test1()")
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("before");
try {
proceedingJoinPoint.proceed();
} catch (Throwable t) {
t.printStackTrace();
}
System.out.println("after");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
3)修改用于测试的控制器TestController.java,方法带上@NeedTest注解
package com.chenqu.bullshit.modules.work.controller;
@RestController
@RequestMapping("test1")
public class TestController {
@NeedTest
@ResponseBody
@RequestMapping("/hello")
public String sayHello(){
System.out.println("hello");
return "hello";
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
运行结果跟上面一样,效果一致。
三、小结
从网上搜索出来的,有关spring boot aop的使用资料,绝大部分都很难懂。说了一大堆,根本不知道在说什么,也许作为手册供查阅是极好的。本文从我个人学习的角度出发,记录一下心得。
参考文章:
Spring Boot使用AOP的正确姿势