Spring AOP,从入门到进阶
Running with Spring Boot v2.4.5, Spring v5.3.6
我们常常在核心业务逻辑中看到诸如事务管理
、日志记录
或性能统计
等行为,这些行为的代码量一般也就几行,但是却分散在多个类中的多个方法内;这些四处分散的重复代码不仅不利于后期的维护工作,同时也显得核心业务逻辑混乱无章。为了解决这一问题,面向切面编程
(Aspect-Oriented Programming)应运而生。不同于面向对象编程
(Object-oriented Programming),AOP
不再以类(Class)为模块化单元,而是以切面(Aspect)作为模块化单元,也就是通过切面来封装那些四处分散的事务管理、日志记录和性能统计等行为。可能有的人会疑惑,可以将这些行为单独封装起来,并不见得一定要使用AOP啊!别杠,单独封装依然无法保持核心业务逻辑的清清爽爽啊,还是会夹杂在一起,不是吗?顺便提一句,横切关注点
(Crosscutting Concern),指的就是事务管理、日志记录和性能统计等行为。
Spring AOP
和AspectJ
是目前Java生态内最受欢迎的两款AOP框架,相较于AspectJ,Spring AOP并没有提供完整的AOP实现,而是侧重于在AOP实现与IOC容器间提供紧密的集成;事实上,Spring AOP适用于企业级开发中绝大多数应用场景,二者简略对比如下表所示:
1 从实战出发
Spring 1.2
为我们带来了第一个版本的Spring AOP框架,很遗憾,该版本的Spring AOP并没有紧跟JDK 1.5的潮流,切面的编写不支持AspectJ注解
,使用起来不是那么方便;还好Spring 2.0
知耻而后勇,一口气为Spring AOP引入了两种编写切面的方式,分别是AspectJ注解
和XML配置文件
,这两种方式均支持Aspectj切入点表达式
,其中AspectJ注解
是我们常用的风格。值得注意的是Spring 1.2引入的编程式AOP(Programmatic AOP)依然延续至今,并没有被剔除。好了,让我们从实战的角度来体验Spring 1.2和Spring 2.0中Spring AOP框架带给我们的能力吧。
CustomService
接口中定义了一个doPrint()
方法;CustomServiceImpl
类实现了CustomService接口中doPrint()方法,用于将给定内容打印到控制台。CustomService
package pers.duxiaotou.service;
public interface CustomService {
public boolean doPrint(String content);
}
CustomServiceImpl
package pers.duxiaotou.service.impl;
import org.springframework.stereotype.Service;
import pers.duxiaotou.service.CustomService;
@Service
public class CustomServiceImpl implements CustomService {
@Override
public boolean doPrint(String content) {
System.out.println(content);
return true;
}
}
需求doPrint()
方法执行前后分别打印两条日志,日志内容分别为:
*** start log ***
*** end log ***
1.1 Spring AOP In Spring 1.2
1.1.1 定制化CustomService Bean
CustomServiceBeanPostProcessor
package pers.duxiaotou.config.aop;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.Advisor;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.target.SingletonTargetSource;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import pers.duxiaotou.service.CustomService;
@Component
@Slf4j
public class CustomServiceBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (CustomService.class.isAssignableFrom(bean.getClass())
&& "customServiceImpl".equals(beanName)) {
/*
* 实现org.springframework.aop.Pointcut接口,声明一个切入点
*/
Pointcut pointcut = new Pointcut() {
/*
* 判断目标对象是否匹配
*/
@Override
public ClassFilter getClassFilter() {
return CustomService.class::isAssignableFrom;
}
/*
* 判断目标方法是否匹配
*/
@Override
public MethodMatcher getMethodMatcher() {
return new MethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return "doPrint".equals(method.getName());
}
@Override
public boolean isRuntime() {
return false;
}
/*
* 若isRuntime()返回true,则根据matches(Method, Class<?>)和matches(Method, Class<?>, Object)进行匹配度校验
* 若isRuntime()返回false,则根据matches(Method, Class<?>)进行匹配度校验
*/
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return false;
}
};
}
};
/*
* 实现org.springframework.aop.MethodBeforeAdvice接口,声明前置通知
*/
MethodBeforeAdvice beforeAdvice = new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
log.trace("*** start log ***");
}
};
/*
* 实现org.springframework.aop.AfterReturningAdvice接口,声明后置通知
*/
AfterReturningAdvice afterAdvice = new AfterReturningAdvice() {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
log.trace("*** end log ***");
}
};
DefaultPointcutAdvisor beforePointcutAdvisor = new DefaultPointcutAdvisor(pointcut, beforeAdvice);
DefaultPointcutAdvisor afterPointcutAdvisor = new DefaultPointcutAdvisor(pointcut, afterAdvice);
List<Advisor> advisors = Arrays.asList(beforePointcutAdvisor, afterPointcutAdvisor);
TargetSource targetSource = new SingletonTargetSource(bean);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setInterfaces(CustomService.class);
proxyFactory.setTargetSource(targetSource);
proxyFactory.addAdvisors(advisors);
/*
* 为目标对象CustomService生成代理对象
*/
return proxyFactory.getProxy();
}
return null;
}
}
顺便提一句,如果觉得分别定义前置通知、后置通知太麻烦,可以直接使用更强大的org.aopalliance.intercept.MethodInterceptor
接口,它可以模拟实现:
MethodBeforeAdvice
AfterReturningAdvice
ThrowsAdvice
MethodInterceptor aroundAdvice = new MethodInterceptor() {
@Override
public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
log.trace("*** start log ***");
Object result = invocation.proceed();
log.trace("*** end log ***");
return result;
}
};
1.1.2 测试
DuXiaoTouSpringAopApp
package pers.duxiaotou;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import pers.duxiaotou.service.CustomService;
@SpringBootApplication(scanBasePackages = "pers.duxiaotou")
@EnableAspectJAutoProxy
public class DuXiaoTouSpringAopApp {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(DeclarativeSpringAopApp.class, args);
CustomService customService = applicationContext.getBean(CustomService.class);
customService.doPrint("spring aop");
}
}
执行结果
2021-05-05 18:41:42.657 TRACE 12132 --- [restartedMain] p.d.c.a.CustomServiceBeanPostProcessor : *** start log ***
spring aop
2021-05-05 18:41:42.658 TRACE 12132 --- [restartedMain] p.d.c.a.CustomServiceBeanPostProcessor : *** end log ***
1.2 Spring AOP In Spring 2.0+
1.2.1 启用AspectJ注解主持
如果试图基于Aspect注解来配置Spring AOP,那么需要通过@EnableAspectJAutoProxy
注解来开启Spring对AspectJ注解的主持。
注意EnableAspectJAutoProxy
注解接口中的proxyTargetClass
属性值默认为false,这意味着Spring AOP默认采用JDK动态代理来生成代理对象;如果Spring AOP直接运行在Spring中,那么这句话没毛病;但如果运行在Spring Boot中,那就有问题了,因为Spring Boot中spring.aop.proxy-target-class
配置属性的默认值为true,即会强制优先使用CGLIB代理来生成代理对象。
1.2.2 编写AspectJ切入点表达式
1.2.3 使用AspectJ @Pointcut注解声明切入点
CommonPointcutConfig
package pers.duxiaotou.config.aop;
import org.aspectj.lang.annotation.Pointcut;
public class CommonPointcutConfig {
@Pointcut("execution(public * pers.duxiaotou.service.CustomService+.*(..))")
public void customServiceLogPointcut() {};
}
1.2.4 使用AspectJ @Aspect注解声明切面
CommonAdviceConfig
package pers.duxiaotou.config.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Slf4j
public class CommonAdviceConfig {
@Before(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
public void customServiceBeforeAdvice(JoinPoint joinPoint) {
log.trace("*** start log ***");
}
@AfterReturning(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
public void customServiceAfterReturningAdvice(JoinPoint joinPoint) {
log.trace("*** end log ***");
}
}
1.2.5 测试
DuXiaoTouSpringAopApp
package pers.duxiaotou;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import pers.duxiaotou.service.CustomService;
@SpringBootApplication(scanBasePackages = "pers.duxiaotou")
@EnableAspectJAutoProxy
public class DuXiaoTouSpringAopApp {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(DeclarativeSpringAopApp.class, args);
CustomService customService = applicationContext.getBean(CustomService.class);
customService.doPrint("spring aop");
}
}
执行结果
2021-05-05 19:03:50.212 TRACE 10984 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** start log ***
spring aop
2021-05-05 19:03:50.216 TRACE 10984 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** end log ***
2. AOP核心术语
下面所列举的AOP核心术语并非Spring AOP独有
3 AspectJ中5种通知注解在Spring AOP中的执行顺序
模拟方法很简单,只需将@Around
、@Before
、@After
、@AfterReturning
和@AfterThrowing
5种通知逻辑绑定在同一连接点即可。
注意从5.2.7版本开始,Spring官方调整了@Around
、@Before
、@After
、@AfterReturning
和@AfterThrowing
这5种通知的执行顺序。
package pers.duxiaotou.config.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Slf4j
public class CommonAdviceConfig {
@Before(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
public void customServiceBeforeAdvice(JoinPoint joinPoint) {
log.trace("*** @Before ***");
}
@After(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
public void customServiceAfterAdvice(JoinPoint joinPoint) {
log.trace("*** @After ***");
}
@AfterReturning(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
public void customServiceAfterReturningAdvice(JoinPoint joinPoint) {
log.trace("*** @AfterReturning ***");
}
@Around(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
public Object customServiceAroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
log.trace("*** @Around before ***");
Object result = joinPoint.proceed();
log.trace("*** @Around after ***");
return result;
}
@AfterThrowing(value = "pers.duxiaotou.config.aop.CommonPointcutConfig.customServiceLogPointcut()")
public void customServiceAfterThrowingAdvice(JoinPoint joinPoint) {
log.trace("*** @AfterThrowing ***");
}
}
正常执行结果
2021-05-06 17:10:27.836 TRACE 17620 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @Around before ***
2021-05-06 17:10:27.836 TRACE 17620 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @Before ***
spring aop
2021-05-06 17:10:27.862 TRACE 17620 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @AfterReturning ***
2021-05-06 17:10:27.862 TRACE 17620 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @After ***
2021-05-06 17:10:27.862 TRACE 17620 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @Around after ***
异常执行结果
2021-05-06 17:17:14.710 TRACE 27308 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @Around before ***
2021-05-06 17:17:14.710 TRACE 27308 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @Before ***
2021-05-06 17:17:14.716 TRACE 27308 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @AfterThrowing ***
2021-05-06 17:17:14.716 TRACE 27308 --- [restartedMain] p.d.c.a.CommonAdviceConfig : *** @After ***
4 Spring AOP实现原理剖析
聊到Spring AOP的底层实现原理,就不得不提到JDK动态代理
和CGLIB代理
,关于这方面的知识参见之前写的一篇文章《Java动态代理》;既然Spring AOP是基于代理来拓展目标对象的,那就很容易想到:Spring IoC容器内贮存的一定是代理对象而非目标对象,那究竟是如何替换的呢?众所周知,Spring暴露了若干IOC容器拓展点(IoC Container Extensiion Points),BeanPostProcessor
接口就是其中之一;有了BeanPostProcessor,任何人都可以在Bean初始化前后对其进行个性化改造,甚至将其替换。
首先,让我们来看一下BeanPostProcessor接口中的内容,它只有两个方法,如下:
package org.springframework.beans.factory.config;
import org.springframework.beans.BeansException;
public interface BeanPostProcessor {
/**
* Apply this BeanPostProcessor to the given new bean instance before any bean initialization.
*/
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* Apply this BeanPostProcessor to the given new bean instance after any bean initialization.
*/
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
没错,Spring AOP就是通过BeanPostProcessor来将目标对象替换为代理对象的!在Spring AOP中,这个BeanPostProcessor就是AbstractAutoProxyCreator
抽象类,其主要用于创建代理对象;Spring AOP为AbstractAutoProxyCreator
定义了两个直系子类,分别是:BeanNameAutoProxyCreator
和AbstractAdvisorAutoProxyCreator
;前者根据Bean的名称来判断是否需要为该Bean创建代理对象,后者根据Advisor探测结果来判断是否需要为该Bean创建代理对象。AbstractAutoProxyCreator
的父子关系如下图所示:
AbstractAdvisorAutoProxyCreator
是一个抽象类,它有三个子类,其中AnnotationAwareAspectJAutoProxyCreator
是我们重点关注的,其可以探测到所有@Aspect注解类,将@Aspect切面类转化为Advisor。
刚才已经提到,AbstractAutoProxyCreator充当了BeanPostProcessor的角色,那其肯定是实现了BeanPostProcessor接口的,如下所示:
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
private final Set<String> targetSourcedBeans = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
private final Map<Object, Boolean> advisedBeans = new ConcurrentHashMap<>(256);
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
// cacheKey一般指的就是beanName
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 根据需要是否为该bean生成代理对象
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 如果targetSourcedBeans包含该beanName,则说明该bean的代理对象已经生成完毕,
// 所以直接返回该bean即可;
// 在当前类中有两个代理类生成时机,第一个是在bean还没有实例化前;第二个是在
// bean已经实例化,初始化之后
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
// 如果advisedBeans已存在该cacheKey,则直接返回该bean
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
// AOP基础设施类,指的就是Advice、Pointcut、Advisor和AopInfrastructureBean
// 接口的实现类;
// shouldSkip()主要是过滤掉由<aop:config/>标签解析出的AspectJPointcutAdvisor
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
// 将该bean标记为已处理且不需要为其生成代理对象,然后返回该bean
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 核心逻辑:获取与当前Bean匹配的Advisor列表
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
// 将该Bean标记为已处理且需要为其生成代理对象
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 生成代理对象
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
}
无疑,AbstractAutoProxyCreator中wrapIfNecessary()
方法就是我们进行源码分析的入口了。wrapIfNecessary()方法首先获取与当前Bean匹配的Advisor列表,然后根据所获取的Advisor列表来判断是否需要为该Bean创建代理对象。本文后续重点分析Advisor列表的获取逻辑,而关于代理对象的创建其实很简单,限于篇幅这里就不做分析了。
首先简单介绍下Advisor的概念。Advisor
有两个分支,分别是PointcutAdvisor
和IntroductionAdvisor
,本文所指的Advisor列表即PointcutAdvisor列表;Advisor是Spring AOP中独有的术语,在AspectJ中并没有等效的术语与其匹配;但其与切面还是有一定相似之处的,或者大家干脆将其视为一个特殊的切面,该切面只能包含一个通知和一个切入点而已。
public interface Advisor {
Advice getAdvice();
}
public interface PointcutAdvisor extends Advisor {
Pointcut getPointcut();
}
介绍完了Advisor的概念后,下面就要开始探索Spring AOP究竟是如何获取与当前Bean匹配的Advisor列表了。wrapIfNecessary()方法内通过getAdvicesAndAdvisorsForBean()
方法来获取与当前Bean匹配的Advisor列表,其实现如下:
public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {
@Override
protected Object[] getAdvicesAndAdvisorsForBean(
Class<?> beanClass, String beanName, TargetSource targetSource) {
// Eligible译为合格的,即获取合格的Advisor实例列表
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return DO_NOT_PROXY;
}
return advisors.toArray();
}
}
getAdvicesAndAdvisorsForBean()方法并没有几行代码,那核心逻辑只能落在findEligibleAdvisors()
方法中,其实现如下:
public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
List<Advisor> candidateAdvisors = findCandidateAdvisors();
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
}
findEligibleAdvisors()方法极为重要,它主要有三个核心逻辑:
- 从当前BeanFactory中获取所有Advisor列表;
- 从所获取的Advisor列表中进一步判断哪些Advisor是与当前Bean匹配的,剔除不匹配的;
- 在Advisor列表头部追加一个DefaultPointcutAdvisor;
下面分别针对上述三个核心逻辑逐个分析!
4.1 findCandidateAdvisors()
注意子类AnnotationAwareAspectJAutoProxyCreator覆盖了父类AbstractAdvisorAutoProxyCreator的findCandidateAdvisors()方法逻辑。
AnnotationAwareAspectJAutoProxyCreator
protected List<Advisor> findCandidateAdvisors() {
List<Advisor> advisors = super.findCandidateAdvisors();
if (this.aspectJAdvisorsBuilder != null) {
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}
注意其实上述findCandidateAdvisors()方法第一次执行是由AbstractAutoProxyCreator中postProcessBeforeInstantiation()
方法内的shouldSkip()
方法触发,即在此时所有Advisor已经获取到了;后期虽然会多次执行findCandidateAdvisors(),但Advisor列表已经被缓存!
super.findCandidateAdvisors()核心实现
// 首先,从当前BeanFactory中获取所有Advisor类的名称,生成advisorNames数组
String[] advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class);
List<Advisor> advisors = new ArrayList<>();
for (String name : advisorNames) {
// 然后,根据advisor名称从当前BeanFactory中获取Advisor实例,将其添加到List中
advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
aspectJAdvisorsBuilder.buildAspectJAdvisors()核心实现
// 首先,从当前BeanFactory中获取所有Bean的名称,最终获取到的beanNames数组数量达100+
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class);
List<Advisor> advisors = new ArrayList<>();
for (String beanName : beanNames) {
Class<?> beanType = this.beanFactory.getType(beanName, false);
if (beanType == null) {
continue;
}
// 然后,判断当前Bean是否有@Aspect注解
if (this.advisorFactory.isAspect(beanType)) {
MetadataAwareAspectInstanceFactory factory =
new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
// 最后,将当前Bean内由@Before、@After、@AfterReturning、@Around
// 和@AfterThrowing注解的方法解析出来,并生成一个Method列表;
// 再逐个将Method封装为InstantiationModelAwarePointcutAdvisorImpl实例。
// 另外,InstantiationModelAwarePointcutAdvisorImpl构造方法中有一个方法,即
// instantiateAdvice(this.declaredPointcut),其主要负责根据切面行为的注解类型
// 转换为具体的Advice实例,具体的转换规则如下:
// @Around ---> AspectJAroundAdvice
// @Before ---> AspectJMethodBeforeAdvice
// @After ---> AspectJAfterAdvice
// @AfterReturning ---> AspectJAfterReturningAdvice
// @AfterThrowing ---> AspectJAfterThrowingAdvice
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
advisors.addAll(classAdvisors);
}
}
小节总结
findCandidateAdvisors()逻辑主要有两点:
- 从当前BeanFactory中根据Advisor.class获取Advisor列表,主要是一些自定义Advisor,比如:
@Configuration
public class PerformanceAdvisorConfig {
@Bean
public PerformanceMonitorInterceptor performanceMonitorInterceptor() {
return new PerformanceMonitorInterceptor(true);
}
@Bean
public Advisor performanceMonitorAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(public * pers.duxiaotou.service.CustomService+.*(..))");
return new DefaultPointcutAdvisor(pointcut, performanceMonitorInterceptor());
}
}
- 从当前BeanFactory中获取由@Aspect注解的切面,然后解析出切面行为和切入点表达式,最后为每个切面行为生成一个InstantiationModelAwarePointcutAdvisorImpl实例。
4.2 findAdvisorsThatCanApply
现在,我们已经获取到了所有的Advisor列表,但还不知道Advisor可以与哪些Bean匹配。继续分析···
protected List<Advisor> findAdvisorsThatCanApply(
List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
}
接下来,进入AopUtils
中一探究竟。查阅下面源码后依然没有找到感兴趣的代码。
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List<Advisor> eligibleAdvisors = new ArrayList<>();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
}
}
boolean hasIntroductions = !eligibleAdvisors.isEmpty();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
// 执行到这里,应该就是PointcutAdvisor了
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
}
return eligibleAdvisors;
}
接下来,进入AopUtils中canApply()
寻找答案。好家伙,进来一看,还有一个canApply()的重载方法。
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
// 无需关注IntroductionAdvisor,一般需要重点关注PointcutAdvisor
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}
再次进入AopUtils中的canApply()
重载方法内一探究竟。
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "Pointcut must not be null");
// Pointcut接口实现类需要实现ClassFilter接口和MethodMather接口
// 首先,根据ClassFilter判断当前切入点是否与目标对象匹配
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
// 然后,根据ClassFilter判断当前切入点是否与目标对象所有目标方法匹配
MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
return true;
}
// 执行到这里,说明当前切入点不会与目标对象中所有目标方法匹配
// 下面就要分析究竟是哪些方法匹配了
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
Set<Class<?>> classes = new LinkedHashSet<>();
// 如果目标对象不是JDK动态代理类
if (!Proxy.isProxyClass(targetClass)) {
// 根据targetClass获取用户所定义的原生Class,如CustomServiceImpl Class实例
classes.add(ClassUtils.getUserClass(targetClass));
}
// 获取targetClass所实现的接口,如CustomService Class实例
classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
for (Class<?> clazz : classes) {
// 通过反射获取方法列表,包括从父类中继承的方法
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
for (Method method : methods) {
// 核心逻辑来了,最终通过methodMatcher.matched()方法
// 来断定切入点与目标对象中哪些目标方法匹配
if (introductionAwareMethodMatcher != null ?
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
小节总结findAdvisorsThatCanApply()
方法的内部核心逻辑主要依托于切入点中ClassFilter
和MethodMatcher
来实现Advisor与Bean的匹配度检测。
4.3 extendAdvisors
extendAdvisors()
方法逻辑比较简单,如下:
protected void extendAdvisors(List<Advisor> candidateAdvisors) {
AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(candidateAdvisors);
}
继续跟进makeAdvisorChainAspectJCapableIfNecessary()方法,如下:
public static boolean makeAdvisorChainAspectJCapableIfNecessary(List<Advisor> advisors) {
if (!advisors.isEmpty()) {
boolean foundAspectJAdvice = false;
for (Advisor advisor : advisors) {
// 判断当前Advisor是否包含AspectJ通知
// 具体地:
// advisor instanceof InstantiationModelAwarePointcutAdvisor
// advisor.getAdvice() instanceof AbstractAspectJAdvice
// advisor instanceof PointcutAdvisor && ((PointcutAdvisor) advisor).getPointcut() instanceof AspectJExpressionPointcut)
if (isAspectJAdvice(advisor)) {
foundAspectJAdvice = true;
break;
}
}
if (foundAspectJAdvice && !advisors.contains(ExposeInvocationInterceptor.ADVISOR)) {
advisors.add(0, ExposeInvocationInterceptor.ADVISOR);
return true;
}
}
return false;
}
从源码来看,makeAdvisorChainAspectJCapableIfNecessary()方法只做了一件事,即在当前Advisor列表头部追加一个ExposeInvocationInterceptor.ADVISOR
;至于这个ADVISOR的信息只能通过阅读ExposeInvocationInterceptor的源码来获取了,如下:
public final class ExposeInvocationInterceptor implements MethodInterceptor {
public static final ExposeInvocationInterceptor INSTANCE = new ExposeInvocationInterceptor();
public static final Advisor ADVISOR = new DefaultPointcutAdvisor(INSTANCE) {
@Override
public String toString() {
return org.springframework.aop.interceptor.ExposeInvocationInterceptor.class.getName() +".ADVISOR";
}
};
private static final ThreadLocal<MethodInvocation> invocation =
new NamedThreadLocal<>("Current AOP method invocation");
private ExposeInvocationInterceptor() {
}
@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
MethodInvocation oldInvocation = invocation.get();
invocation.set(mi);
try {
return mi.proceed();
} finally {
invocation.set(oldInvocation);
}
}
}
原来如此,这个ADVISOR指的是DefaultPointcutAdvisor
,其所持有的通知就是ExposeInvocationInterceptor;另外,invoke()方法内将MethodInvocation保存在ThreadLocal中,直到整个拦截器链处理完毕才恢复原有的MethodInvocation。
疑问当通过代理对象调用目标方法时,首先会依次执行拦截器链中的拦截器逻辑,然后才是执行目标方法;ExposeInvocationInterceptor有什么特别之处使得它可以坐在拦截器链的第一把交椅呢?
4.4 代理对象中目标方法的执行流程
要想搞清楚代理对象中目标方法的执行流程,首先要找到执行入口。下面分别针对JDK动态代理和CGLIB代理的执行入口进行分析。
4.4.1 JDK动态代理
JDK动态代理是基于接口的实现
机制来生成代理对象的,代理对象最终委托java.lang.reflect.InvocationHandler
接口中的invoke()
方法来进行拓展处理的,换句话说,invoke()方法才是真正的拓展逻辑所在。在Spring AOP中扮演InvocationHandler角色的就是JdkDynamicAopProxy
,即JdkDynamicAopProxy实现了InvocationHandler接口,并重写了invoke()方法,具体如下:
final class JdkDynamicAopProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object retVal = null;
// 获取TargetSource,其持有需要被代理对象增强的目标对象
TargetSource targetSource = this.advised.targetSource;
// 通过TargetSource获取目标对象
Object target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 获取拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
if (chain.isEmpty()) {
// 如果拦截器链为空,则直接调用目标方法
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
} else {
// 如果拦截器链非空,则直接执行拦截器链中的逻辑
MethodInvocation invocation =
new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();
}
return retVal;
}
}
4.4.2 CGLIB代理
CGLIB代理是基于子类的继承
机制来生成代理对象的,代理对象最终委托org.springframework.cglib.proxy.MethodInterceptor
接口中的intercept()
方法来进行拓展处理的,换句话说,intercept()方法才是真正的拓展逻辑所在。在Spring AOP中扮演MethodInterceptor角色的就是DynamicAdvisedInterceptor
,即DynamicAdvisedInterceptor实现了MethodInterceptor接口,并重写了intercept()方法,具体如下:
class CglibAopProxy implements AopProxy {
// CglibAopProxy的静态内部类,DynamicAdvisedInterceptor是最常用的拦截器
// CglibAopProxy内部还定义了若干其他类型的拦截器
private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
// AOP代理配置管理器,AdvisedSupport实现了ProxyConfig接口
private final AdvisedSupport advised;
public DynamicAdvisedInterceptor(AdvisedSupport advised) {
this.advised = advised;
}
@Override
public Object intercept(
Object proxy,
Method method,
Object[] args,
MethodProxy methodProxy) throws Throwable {
TargetSource targetSource = this.advised.getTargetSource();
Object target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 获取拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal = null;
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// 如果拦截器链为空,则直接调用目标方法
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
} else {
// 如果拦截器链非空,则直接执行拦截器链中的逻辑
MethodInvocation invocation =
new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy);
// CglibMethodInvocation继承自ReflectiveMethodInvocation
// CglibMethodInvocation中的proceed()方法实际依然是通过super.proceed()
// 调用父类ReflectiveMethodInvocation中的proceed()方法
retVal = invocation.proceed();
}
return retVal;
}
}
}
JDK动态代理的执行入口与CGLIB代理的执行入口虽然不同,但核心逻辑无疑是一致的:
- 获取拦截器链
- 依次执行拦截器链中的拦截器逻辑
首先,来看看拦截器链是如何获取到的,一步一步DEBUG,最终发现以下转换规则:
- ExposeInvocationInterceptor.ADVISOR ---> ExposeInvocationInterceptor
- DefaultPointcutAdvisor ---> 直接通过Advisor的getAdvice()即可获取到拦截器
- InstantiationModelAwarePointcutAdvisorImpl ---> 借助AdvisorAdapter将其转换为MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor和ThrowsAdviceInterceptor。
然后,再来看看拦截器链的执行原理。还记得吗?前面在介绍连接点概念的时候,提过一句:包括Spring AOP在内的许多AOP框架都将通知建模为拦截器,并在连接点周围维护了拦截器链。关于“在连接点周围维护了拦截器链”这一点Spring AOP并没有说谎,可以从代码层面来验证;ReflectiveMethodInvocation
实现了MethodInvocation接口,而MethodInvocation接口继承自Joinpoint
接口,可以说ReflectiveMethodInvocation就是最常用的连接点,查阅其源代码,我们可以发现其维护了一个拦截器链成员变量,这也验证了Spring AOP确实在连接点周围维护了拦截器链。
public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable {
protected final List<?> interceptorsAndDynamicMethodMatchers;
@Override
public Object proceed() throws Throwable {
// 拦截器链处理完毕,则执行目标方法;invokeJoinpoint()内部是通过反射来调用目标方法的
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
// 在将要处理该拦截器链时,会将currentInterceptorIndex自增一
// 责任链一般都是这种玩法
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
// 拦截器链中的拦截器都是MethodInteceptor接口的实现类,那肯定自行实现了invoke()方法;
// 在invoke()方法内部均会有invoke.proceed()的身影,invoke.proceed()方法才是拦截器链不断流转的关键;
// 无论是JDK动态代理还是CGLIB代理,invoke()方法的参数肯定是同一个MethodInvocation实例;
// 如果是JDK动态代理,那么这个MethodInvocation实例就是ReflectiveMethodInvocation实例;
// 如果是CGLIB代理,那么这个MethodInvocation实例就是CglibMethodInvocation实例
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
5 总结
总结的话就不多说了,要不然感觉又能水个三四百字。最后只想分享一个关于切面编写的建议:Pointcut、Advice和Advisor的定义分别放置在独立的配置类中,方便维护。
6 参考文档
- https://docs.spring.io/spring-framework/docs/5.3.6/reference/html/core.html#aop
- https://docs.spring.io/spring-framework/docs/5.3.6/reference/html/core.html#aop-api
- https://www.javachinna.com/logging-performance-monitoring-security-and-transaction-management-with-spring-aop/