SpringBoot AOP
1. 认识AOP
1.1 AOP概述
AOP即Aspect Oriented Programming,意为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中非常重要的一种思想,也是Spring框架中的一个重要内容,是函数式编程的一种衍生模型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
在平时Java开发中,Spring AOP主要用于日志记录、性能统计、安全控制、事务管理及异常处理等多种场景。
1.2 AOP相关术语
- 目标对象:target
指的是需要被增强的对象,由于Spring aop是通过代理模式实现,从而这个对象永远是被代理对象。 - 连接点:join point
指的是那些被拦截到的点,在Spring中这些点指的是方法,因为Spring只支持方法类型的连接点。 - 切入点:pointcut
表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。 - 通知:advice
指拦截到连接点之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,返回通知,环绕通知等,其中定义了具体要做的操作。 - 引介:introduction
引介是一种特殊的通知,在不修改类代码的前提下,可以在运行期为类动态地添加一些方法或属性。 - 切面:aspect
是切入点和通知的结合。 - 织入:weaving
织入是一个过程,是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期,类装载期,运行期进行。 - 代理:proxy
一个类被AOP织入增强后,就产生一个结果代理类。
1.3 通知类型
1)before:前置通知,在一个方法执行前被调用;
2)after:最终通知,在方法执行完成之后调用的通知,无论方法执行是否成功;
3)after-returning:后置通知,仅当方法成功完成之后的通知;
4)after-throwing:异常抛出通知,在方法抛出异常退出时执行的通知;
5)around:环绕通知,集大成者,可包含上述4种通知类型。
1.4 AOP底层实现
AOP也分为静态AOP与动态AOP。
静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。
动态AOP是指将切面代码进行动态织入实现的AOP。Spring的AOP为动态AOP,实现的技术为JDK提供的动态代理技术 和 CGLIB动态字节码增强技术。
CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
JDK动态代理是在运行期间,在JVM内部动态生成Class对象。更多内容可参考:
https://blog.csdn.net/qq_35006663/article/details/102648760。
2. SpringBoot AOP
前面了解了AOP的一些基本概念,本节就先通过简单的代码,实现SpringBoot中AOP的开发,更清晰的认识一下AOP。
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
测试
1)创建Controller、Service等,实现接口的调用。Service接口简化代码:
@Service
public class UserService {
public void getUser(){
System.out.println("执行方法getUser()");
}
public void deleteUser(){
System.out.println("执行方法deleteUser()");
}
}
2)写一个Aspect,封装横切关注点(日志、监控等),需要配置通知(前置、后置、返回、异常、环绕通知等)、添加切入点(在哪些包的哪些方法执行)。
注意execution语法,以下示例中表示范围是service包及其子包下所有类的所有方法。
@Component
@Aspect
public class LogComponent {
//配置拦截规则
@Pointcut("execution(* org.haoj.aop.service.*.*(..))")
public void pc1() {
}
//前置通知
@Before(value = "pc1()")
public void before(JoinPoint jp) {
String name = jp.getSignature().getName();
System.out.println("前置通知拦截>>" + name);
}
//后置通知
@After(value = "pc1()")
public void after(JoinPoint jp) {
String name = jp.getSignature().getName();
System.out.println("后置通知拦截>>" + name);
}
//返回通知
@AfterReturning(value = "pc1()", returning = "result")
public void AfterReturning(JoinPoint jp, Object result) {
String name = jp.getSignature().getName();
System.out.println("返回通知拦截>>" + name + ">>返回值>>" + result);
}
//异常通知
@AfterThrowing(value = "pc1()", throwing = "e")
public void AfterThrowing(JoinPoint jp, Exception e) {
String name = jp.getSignature().getName();
System.out.println("异常通知拦截>>" + name + ">>>>" + e.getMessage());
}
//环绕通知
@Around(value = "pc1()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("方法调用前>>");
Object proceed = pjp.proceed(); //方法调用
System.out.println("方法调用后>>");
return proceed;
}
}
3)访问接口,查看控制台输出。http://localhost:8080/getUser
方法调用前>>
前置通知拦截>>getUser
执行方法getUser()
方法调用后>>
后置通知拦截>>getUser
返回通知拦截>>getUser>>返回值>>null
参考文档:https://blog.csdn.net/qq_35006663/article/details/102650735