Spring AOP作为Spring框架的核心模块,为我们提供了一种优雅的方式来处理横切关注点。本篇博客将从组成、实现和实现原理三个方面深入剖析Spring AOP,并结合具体的例子进行说明。
在Spring 切⾯类中,可以在⽅法上使⽤以下注解,会设置⽅法为通知⽅法,在满⾜条件后会通知本⽅法进⾏调⽤:
@Before
:通知⽅法会在⽬标⽅法调⽤之前执⾏。 @After
:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。 @AfterReturning
:通知⽅法会在⽬标⽅法返回后调⽤。 @AfterThrowing
:通知⽅法会在⽬标⽅法抛出异常后调⽤。@Around
:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执⾏⾃定义的⾏为。下面通过一个生活中的例子,我们可以更直观地理解Spring AOP的组成部分及其运作方式。
在这个例子中,我们可以将Spring AOP的组成部分与咖啡馆的场景联系起来:
切面(Aspect):切面就好像是咖啡馆提供的不同附加服务,比如打包、送货等。每种附加服务都是一个切面,它定义了一个特定的横切逻辑。
切点(Pointcut):切点就像是决定在哪些情况下应该提供附加服务。例如,每次有人点了咖啡,那就是一个切点,表示一个可以应用附加服务的地方。
通知(Advice):通知就是具体的附加服务的实现。在咖啡馆中,这可以是将咖啡打包的过程,或者是送货员将咖啡送到指定地点的过程。
连接点(Join Point):连接点就像是咖啡的准备过程,比如磨咖啡豆、冲泡咖啡等。这些过程都是咖啡制作的特定点,也就是连接点。
当你在咖啡馆点一杯咖啡时,Spring AOP的运作方式如下:
切面定义了附加服务,比如打包和送货。
切点决定了在哪些情况下应该提供附加服务,比如在每次点咖啡时。
通知实现了附加服务的具体逻辑,比如将咖啡打包或送到指定地点。
连接点是咖啡的制作过程,每个步骤都是一个连接点,而在某些连接点上(即切点匹配的地方),会触发附加服务的通知。
这样,当你点了一杯咖啡时,Spring AOP就会在制作咖啡的过程中,根据切点的匹配情况触发相应的附加服务通知,比如将咖啡打包好或送到指定地点。
Spring AOP的实现主要依赖于JDK动态代理和CGLIB动态代理两种方式。默认情况下,Spring AOP使用JDK动态代理,但也可以通过配置切换到CGLIB动态代理。
在 pom.xml 中添加如下配置:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
切点指的是具体要处理的某⼀类问题,Spring AOP 切点的定义如下,在切点中我们要定义拦截的规则,具体实现如下:
@Aspect //表明这个类为一个切面
@Component
public class UserAspect {
//定义切点,使⽤ AspectJ 表达式语法
@Pointcut("execution(* com.example.springaop.Controller.UserController.* (..))")
public void pointcut(){ }
}
@Pointcut 注解标注的方法是一个空方法,它不需要具体的方法体。它的作用是为下面的通知方法提供一个标识,用来指定具体指向哪个切点。
通知定义的是被拦截的⽅法具体要执⾏的业务,比如统一异常处理等待。
依旧是在刚才的切面类UserAspect中书写代码:
// 前置通知
@Before("pointcut()")
public void doBefore(){
System.out.println("执⾏ Before ⽅法");
}
我们在UserController中写一个测试方法:
@RestController
@ResponseBody
@RequestMapping("/con")
public class UserController {
@RequestMapping("/tsst")
public Object test() {
System.out.println("测试方法执行");
return "测试";
}
}
我们在浏览器进行访问测试方法:
通过控制台信息发现前置通知方法的实现在测试方法之前,至于其他通知方法我们也补全于切面类当中
// 后置通知
@After("pointcut()")
public void doAfter() {
System.out.println("执⾏ After ⽅法");
}
@AfterReturning("pointcut()")
public void doAfterReturning() {
System.out.println("执⾏ return 之前通知⽅法");
}
@AfterThrowing
public void doAfterThrowing() {
System.out.println("执行异常抛出之前的通知方法");
}
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) {
Object obj = new Object();
System.out.println("开始执行环绕通知方法");
try {
//执行目标方法
joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("环绕通知方法结束");
return obj;
}
可以将环绕通知方法理解为包裹目标方法的一层“包装”,它可以在目标方法执行前后进行一些额外的操作和控制。通过环绕通知,我们可以在不改变目标方法原有逻辑的同时,添加一些横切关注点的功能,例如日志记录、性能监控、事务管理等。
AspectJ ⽀持三种通配符
*
:匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)..
:匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤+
:表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如com.cad.Car+ ,表示继承该类的所有⼦类包括本身切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法为:execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)
其中修饰符与异常可以省略。
示例:
execution(* com.cad.demo.User+.*(..))
匹配该类的⼦类包括该类的所有⽅法。
execution(* addUser(String, int))
匹配 addUser ⽅法,且第⼀个参数类型是 String,第⼆个参数类型是 int。
Spring AOP的实现原理主要依赖于动态代理机制。当使用Spring AOP时,它会在运行时为目标对象生成一个代理对象,并将切面逻辑织入到目标对象的方法调用中。
Spring AOP所使用的代理方式可以分为两种:JDK动态代理和CGLIB动态代理。
当目标对象实现了至少一个接口时,Spring AOP会使用JDK动态代理来生成代理对象。JDK动态代理是通过java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口实现的。
当目标对象没有实现任何接口时,Spring AOP会使用CGLIB动态代理来生成代理对象。CGLIB动态代理是通过继承目标类并重写目标方法的方式实现的。
总结起来,Spring AOP的实现原理是通过动态代理机制,在运行时为目标对象生成代理对象,并将切面逻辑织入到目标对象的方法中。具体使用JDK动态代理还是CGLIB动态代理取决于目标对象是否实现了接口。