目录
一.SpringAOP是什么
1.1理论
Spring Aop(Aspect-Oriented Programming,面向切面编程)是一个Spring框架中的一个核心模块,主要用于处理横切关注点。
1.2主要功能
1.2.1分离关注点
提供一种对所有目标对象的通用行为进行集中管理的方式。
如日志记录,事物管理,权限控制,性能审计等。
1.2.2增强模块化
通过创建可复用的切面,可以使应用程序的业务逻辑更具模块化。
1.2.3动态代理
Spring AOP使用动态代理技术在运行时实现切面逻辑而不是在编译时,因此可无侵入地添加功能。
1.3例子
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义切面类
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* com.example.test.controller..*.*(..))")
public void log(){
}
@Before("log()")
public void beforeController() {
System.out.println("before controller");
}
@After("log()")
public void afterController1() {
System.out.println("after controller1");
}
@Around("log()")
public Object aroundController1(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("around start controller1");
Object proceed = joinPoint.proceed();
System.out.println("around end controller1");
return proceed;
}
@AfterThrowing("log()")
public void throwController1() {
System.out.println("throw controller1");
}
@AfterReturning("log()")
public void returnController1() {
System.out.println("return controller1");
}
}
设计测试用的controller类
@RestController
@RequestMapping("/test")
public class ControllerTest {
@MyAspect1
@RequestMapping("/t1")
public String test() {
return "test";
}
@RequestMapping("/t2")
public String test2() {
throw new RuntimeException("failed");
}
}
二.SpringAOP核心概念
我们先进行分析上一段代码
@Aspect:切面类,告诉Spring,这个类是一个切面类
@Pointcut:切点,告诉Spring要针对那些方法进行切入
@Before、@Around、@AfterReturning、@After、@AfterThrowing:通知,告诉Spring针对后要做什么处理
2.1切点(Pointcut)
在Spring AOP中,Pointcut(切点)是用于定义特定的连接点(Join Points),即横切关注点应该应用到的地方。Spring通过切点表达式来指定匹配特定的类、方法、参数等,让切面能够在运行时准确地找到需要织入的点。
这是使用切点的情况下,如果不适用切点的话会使代码变的非常冗余
@Before("execution(* com.example.test.controller..*.*(..))")
public void beforeController() {
System.out.println("before controller");
}
@After("execution(* com.example.test.controller..*.*(..))")
public void afterController1() {
System.out.println("after controller1");
}
这是不适用切点的情况下,相当于每一种通知都会加上具体的目标地址,使用了切点,我们只需要加上定义切点的方法即可。
2.2通知(Advice)
Spring中AOP的通知类型有以下⼏种:
@Around: 环绕通知, 此注解标注的通知⽅法在⽬标⽅法前, 后都被执⾏
@Before: 前置通知, 此注解标注的通知⽅法在⽬标⽅法前被执⾏
@After: 后置通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, ⽆论是否有异常都会执⾏
@AfterReturning: 返回后通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, 有异常不会执⾏
@AfterThrowing: 异常后通知, 此注解标注的通知⽅法发⽣异常后执⾏
我们这里主要看环绕通知,返回后通知,异常后通知
@Around("log()")
public Object aroundController1(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("around start controller1");
Object proceed = joinPoint.proceed();
System.out.println("around end controller1");
return proceed;
}
@AfterThrowing("log()")
public void throwController1() {
System.out.println("throw controller1");
}
@AfterReturning("log()")
public void returnController1() {
System.out.println("return controller1");
}
可见,我们定义了三种通知类型,但是只是有两种通知类型,因此,异常通知类型是当异常发生后才会进行通知。
我们进行修改代码
@RestController
@RequestMapping("/test")
public class ControllerTest {
@MyAspect1
@RequestMapping("/t1")
public String test() {
int a=1/0;
return "test";
}
@RequestMapping("/t2")
public String test2() {
throw new RuntimeException("failed");
}
}
运行结果
发生了异常,但是最后环绕通知也没有完全生成。当我们进行捕获了异常,环绕通知才会完整的生成。
这是有异常后,各个通知类型是否执行以及执行的顺序。
2.3切面优先级
当我们定义了多个切面类的时候,并且这几个切面类同时匹配到了同一个方法时候,那他们的执行顺序是什么样呢
这里我创建了两个切面类,来进行测试,但是这里只是进行展示了前置通知和后置通知。
@Aspect
@Component
public class MyAspect11 {
@Pointcut("execution(* com.example.test.controller..*.*(..))")
public void log(){
}
@Before("execution(* com.example.test.controller..*.*(..))")
public void beforeController() {
System.out.println("后置通知类11");
}
@After("execution(* com.example.test.controller..*.*(..))")
public void afterController1() {
System.out.println("后置通知类11");
}
}
我们先看不定义@order注解看看顺序是什么样的?
通过对比,可以发现,存在多个切面类的时候,按照切面类的类名字母排序
- @Before 通知:字⺟排名靠前的先执⾏
- @After 通知:字⺟排名靠前的后执⾏
如果我们想要让某一个具体切面类先执行的时候,怎么办
答:Spring提供了一个全新的注解,来控制这些切面通知的执行顺序:@Order
我们在Aspect11,Aspect12分别加上@Order(1),@Order(2)
通过上述程序的运⾏结果, 得出结论:
@Order 注解标识的切⾯类, 执⾏顺序如下:
@Before 通知:数字越⼩先执⾏
@After 通知:数字越⼤先执⾏
@Order 控制切⾯的优先级, 先执⾏优先级较⾼的切⾯, 再执⾏优先级较低的切⾯, 最终执⾏⽬标⽅法
2.4切点表达式书写
2.4.1execution()表达式
execution()是最常用的切点表达式,用来匹配方法。
execution(<访问修饰符> <返回类型> <包名.类名.⽅法(⽅法参数)> <异常>)
1.*匹配任意字符,只匹配一个参数(返回类型,包,类名,方法或者是方法参数)。
2. ..匹配多个连续的任意符号,可以匹配任意级的包,或者是任意类型,任意个数的参数,
如果包下嵌套有包,可以使用这种办法。
2.4.2@annotation表达式
这个表达式主要是用来自定义注解的时候使用的,当我们要匹配两个不同类的一个方法,此时就可以使用这种方式
自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAspect1 {
}
注解解释:
一.@Target 标识了 Annotation 所修饰的对象范围, 即该注解可以⽤在什么地⽅. 常⽤取值:
ElementType.TYPE: ⽤于描述类、接⼝(包括注解类型) 或enum声明
ElementType.METHOD: 描述⽅法
ElementType.PARAMETER: 描述参数
ElementType.TYPE_USE: 可以标注任意类型
二. @Retention 指Annotation被保留的时间⻓短, 标明注解的⽣命周期,@Retention 的取值有三种:
RetentionPolicy.SOURCE:表⽰注解仅存在于源代码中, 编译成字节码后会被丢弃. 这意味着在运⾏时⽆法获取到该注解的信息, 只能在编译时使⽤. ⽐如 @SuppressWarnings , 以及 lombok提供的注解 @Data , @Slf4j
RetentionPolicy.CLASS:编译时注解. 表⽰注解存在于源代码和字节码中, 但在运⾏时会被丢弃. 这意味着在编译时和字节码中可以通过反射获取到该注解的信息, 但在实际运⾏时⽆法获 取. 通常⽤于⼀些框架和⼯具的注解.
RetentionPolicy.RUNTIME:运⾏时注解. 表⽰注解存在于源代码, 字节码和运⾏时中. 这意味着在编译时, 字节码中和实际运⾏时都可以通过反射获取到该注解的信息. 通常⽤于⼀些需要 在运⾏时处理的注解, 如Spring的 @Controller @ResponseBody
看一下切面类是如何进行书写的
@Aspect
@Component
public class MyAspect2 {
@Before("@annotation(com.example.test.aspect.MyAspect1)")
public void beforeController() {
System.out.println("before controller");
}
}
我们使用execution表达式的时候,通常里面写的是匹配方法的规则,现在写的是自定义注解的路径。这一点跟之前不同
如何在测试方法中进行使用呢
@RestController
@RequestMapping("/test")
public class ControllerTest {
@MyAspect1
@RequestMapping("/t1")
public String test() {
System.out.println("test");
return "test";
}
@RequestMapping("/t2")
public String test2() {
throw new RuntimeException("failed");
}
}
在相应的方法上加上刚才自定义的注解。
总结:
Spring AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架中的一个模块,用于实现横切关注点的模块化开发。代理是 Spring AOP 实现的一种方式。
Spring AOP 实现代理的方式有两种:
基于 JDK 动态代理: 如果目标对象实现了至少一个接口,Spring AOP 就会使用 JDK 动态代理来为目标对象创建代理。在运行时,Spring AOP 会动态生成一个实现了目标对象所有接口的代理对象,并在代理对象的方法中织入切面逻辑。
基于 CGLIB 代理: 如果目标对象没有实现任何接口,Spring AOP 就会使用 CGLIB(Code Generation Library)来为目标对象创建代理。CGLIB 使用字节码生成技术,在运行时生成目标对象的子类,并重写其中的方法来织入切面逻辑。
与其说是Spring AOP是一种方法,不如说是一种思想,是一种不改变原来的代码上,对代码达到增强的一种效果。