回顾
前面已经讲述了大致的AOP流程,从解析配置文件里面的AOP标签到创建AOP代理时去获取所有的增强方法,但对于增强方法的获取还没有看到具体实现的底层,下面就来看看具体的底层实现
获取增强器
回到ReflectiveAspectJAdvisorFactory中的getAdvisors方法
上一篇我们就是讲到这
- 对方法上的注解处理:即@Around,@Before,@After等。。。
- 使用getAdvisor方法将Method转化为Advisor
- 对字段上的注解处理:即@DeclaredParents
- 使用getDeclareParentsAdvisor对字段进行转化成Advisor
下面就对这两个方法进行分析
getAdvisor方法
这个方法是获取普通增强器的
源码如下
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
int declarationOrderInAspect, String aspectName) {
//校验操作
validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
//获取切入点信息,也就是切面里面的@Pointcut注解,或者@Befor等注解里面的一些表达式
//这个注解标识了切入点而已,切入点就是给切入方法使用的,表明该切入方法在什么时候被切入
//获取切入点
//参数有候选方法,切面工厂获取的切面Class
AspectJExpressionPointcut expressionPointcut = getPointcut(
candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
//判断切入点是否为空
if (expressionPointcut == null) {
//如果没有切入点,直接返回null
//代表没有需要增强的方法
//没有切入点还增强什么咧
return null;
}
//如果有切入点,就根据切点信息生成增强器
return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}
总体的步骤如下
- 校验
- 获取切入点信息
- 判断切断是否为空
- 如果切入点为空,直接return null
- 如果切入点不为空,根据切入点信息去生成增强方法
拓展:可能这里有人对于切入点不太熟悉,切入点其实就是指你切入的方法在哪里去执行,一般在AOP类里有两种方法添加
- 注解式拦截:使用@PointCut注解去指定哪个注解会代表类被切入
- 方法规则式拦截:在切入方法的@Before等上面添加表达式,表达式里面的内容就是标识对于哪些类进行切入
使用注解式拦截
使用方法式规则拦截
下面就蓝看看是如何获取这两种切入点的
获取切入点
对应的方法为getPointCut
方法的源码如下
private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
//要知道这里传进来的就是切面里面的方法
//从候选方法里面寻找有没有对应的注解
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
//判断有没有匹配的注解
if (aspectJAnnotation == null) {
//没有匹配的注解直接返回Null,
return null;
}
//使用AspectJExpressionPointCut来封装数据
AspectJExpressionPointcut ajexp =
new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
//注入切入点的表达式,也就是规则式拦截的表达式
ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
if (this.beanFactory != null) {
//注入beanFactory
ajexp.setBeanFactory(this.beanFactory);
}
return ajexp;
}
这里大概就两个流程
- 从候选方法上取出匹配的注解
- 使用AspectJExpressionPointCut来封装数据,比如切面对应的Class
- 注入切入点的表达式,也就是规则式拦截的表达式
- 注入beanFactroy
取出匹配的注解
从代码上可以看到,取出匹配的注解是交由AbstractAspectJAdvisorFactory去执行的,对应的方法是findAspectJAnnotationOnMethod
下面就来看看这个方法的源码
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
//遍历要匹配的注解
for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
//调用findAnnotation方法从方法中看能不能找出当前的注解
//并且封装在AspectJAnnotation里面
AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz);
//如果不为null,就直接返回
//从这里可以看到,如果给方法加上了多个注解还会有一个优先级顺序。。。。
if (foundAnnotation != null) {
return foundAnnotation;
}
}
return null;
}
可以看到这里遍历的注解,是不是十分熟悉咧。。
就是我们用于给Aop定义切入点和切入方法的6个注解,所以也是找这6个注解
同时还有一个顺序问题,先找PointCut、Around、Before、After、AfterReturing、AfterThrowing(后面再进行验证一下。。。。。),优先级也是从大往小,如果一个方法里面有PointCut又有Around,则会优先取PointCut
后面的封装进AspectJAnnotation里面就不详细说了,比较复杂,看不太懂。。。。。。。
还是直接分析下一个流程
封装数据
封装数据也没什么好讲的,就是如果存在匹配的注解,就代表要生成一个切入点了,Spring使用的是AspectJExpressionPointCut来保存这一部分信息的,最终将AspectJExpressionPointCut来返回结果即可
根据切入点信息生成增强方法
对应的代码如下
可以看到增强方法其实对应的是一个InstantiationModelAwarePointcutAdvisorImpl对象
接下来我们就看看其是怎么创建的,源码如下
public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut,
Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory,
MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
this.declaredPointcut = declaredPointcut;
this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
this.methodName = aspectJAdviceMethod.getName();
this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
this.aspectJAdviceMethod = aspectJAdviceMethod;
this.aspectJAdvisorFactory = aspectJAdvisorFactory;
this.aspectInstanceFactory = aspectInstanceFactory;
this.declarationOrder = declarationOrder;
this.aspectName = aspectName;
if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
// Static part of the pointcut is a lazy type.
Pointcut preInstantiationPointcut = Pointcuts.union(
aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);
// Make it dynamic: must mutate from pre-instantiation to post-instantiation state.
// If it's not a dynamic pointcut, it may be optimized out
// by the Spring AOP infrastructure after the first evaluation.
this.pointcut = new PerTargetInstantiationModelPointcut(
this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);
this.lazy = true;
}
else {
// A singleton aspect.
this.pointcut = this.declaredPointcut;
this.lazy = false;
//对于建言进行初始化
//建言就是指切入方法的注解,比如@Before,@After那些2
//但那些不同类型的建言,对方法的增强类型也是不同的
//@Before是方法执行前,@After是方法执行后,@Around则是一个包围
this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
}
}
可以看到构造方法,emmmmm,很长,大部分的都是一些简单的赋值操作,但这里要注意的是对于建言的初始化,也就是对于增强器的初始化,不同类型的建言对应不同的增强器
而初始化增强器是在instantiateAdvice里面初始化的
初始化增强器
源码如下
private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) {
//调用aspectAdvisorFactory来获取增强器
Advice advice = this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pointcut,
this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
return (advice != null ? advice : EMPTY_ADVICE);
}
可以看到,增强器的初始化是交由AspectInstanceFactory接口来完成的,并且其只有一个实现类,就是ReflectiveAspectJAdvisorFactory
在ReflectiveAspectJAdvisorFactory的getAdvice方法的源码如下
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
//获取切面的class
Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
//校验
validate(candidateAspectClass);
//获取方法上的注解
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
if (aspectJAnnotation == null) {
return null;
}
// If we get here, we know we have an AspectJ method.
// Check that it's an AspectJ-annotated class
if (!isAspect(candidateAspectClass)) {
throw new AopConfigException("Advice must be declared inside an aspect type: " +
"Offending method '" + candidateAdviceMethod + "' in class [" +
candidateAspectClass.getName() + "]");
}
if (logger.isDebugEnabled()) {
logger.debug("Found AspectJ method: " + candidateAdviceMethod);
}
//AbstractAspectJAdvice其实就是建言(增强器)的一个抽象模板
AbstractAspectJAdvice springAdvice;
//这是最关键的一步
//对注解的类型进行判断
switch (aspectJAnnotation.getAnnotationType()) {
//如果是@PointCut类型
case AtPointcut:
if (logger.isDebugEnabled()) {
logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
}
//@PointCut类型没有增强器,其代表的仅仅只是一个切入点
return null;
//如果是@Around类型
case AtAround:
//返回一个AspectJAroundAdvice
springAdvice = new AspectJAroundAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
//如果是@Before类型
case AtBefore:
//返回一个AspectJMethodBeforeAdvice
springAdvice = new AspectJMethodBeforeAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
//如果是@After类型
case AtAfter:
//返回一个AspectJAfterAdvice
springAdvice = new AspectJAfterAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
break;
//如果是一个@AfterReturning注解
case AtAfterReturning:
//返回一个AspectJAfterReturingAdvice
springAdvice = new AspectJAfterReturningAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
//获取@AfterReturing的属性
AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
//判断是否为空,不为空就添加进springAdvice中
//这里对应的属性是returningName
if (StringUtils.hasText(afterReturningAnnotation.returning())) {
springAdvice.setReturningName(afterReturningAnnotation.returning());
}
break;
//判断是不是@AfterThrowing注解
case AtAfterThrowing:
//返回一个AspectJAfterThrowingAdvice
springAdvice = new AspectJAfterThrowingAdvice(
candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
//获取@AfterThrowing注解上的属性
AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
//判断是否为空,不为空就添加进springAdvice中
//这里对应的属性是throwingName,
if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
}
break;
//如果不是上面的5种类型,抛错处理
default:
throw new UnsupportedOperationException(
"Unsupported advice type on method: " + candidateAdviceMethod);
}
// Now to configure the advice...
//接下来给建言添加一些配置
//注入切入的名称
springAdvice.setAspectName(aspectName);
//
springAdvice.setDeclarationOrder(declarationOrder);
String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
if (argNames != null) {
springAdvice.setArgumentNamesFromStringArray(argNames);
}
springAdvice.calculateArgumentBindings();
return springAdvice;
}
枚举类如下,可见与对应的注解是一一对应的
可以看到,初始化增强器是使用一个简单工厂模式来根据不同的注解来初始化的,增强器整体抽象成一个模板AbstractAspectAdvice
使用switch来根据注解类型选择哪种AbstractAspectAdvice的实现类
-
@PointCut:并不是一个建言,直接return null
-
@Around:返回一个AspectJAroundAdvice
-
@Before:返回一个AspectJMethodBeforeAdvice
-
@AfterReturing:返回一个AspectJAfterReturing,并且根据注解里面的returing属性来给AspectJAfterReturing设置returing属性
-
@AfterThrowing:返回一个AspectJAfterThrowingAdavice,并且根据注解里面的annotation属性去设置annotation属性
-
仅仅支持这5种类型,对于其他类型都会报错
-
最后就是一些简单配置信息注入进AbstractAspectAdvice上去,比如切面名字等。。。。。。
-
最终将AbstractAspectAdvice返回,返回到增强方法的构造方法
-
并且这里要注意,对于每个建言的构造方法里面都是传进了候选方法的Method的,同时也给上了切面的实例工厂,也就是说建言可以通过切面实例工厂来获取实例然后使用候选方法的Method来进行执行建言的
下面我们简单分析一下几个常用的增强器
AspectJMethodBeforeAdvice
AspectJMethodBeforeAdvice对应的就是@Before注解生成的建言(增强器)
可以看到,拦截对应的是一个before方法,里面执行了invokeAdviceMethod
而invokeAdviceMethod是AbstractAspectJAdivice里面去做的,执行了invokeAdviceMethodWithGivenArgs方法
源码如下
protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
//对参数处理
Object[] actualArgs = args;
if (this.aspectJAdviceMethod.getParameterCount() == 0) {
actualArgs = null;
}
try {
//下面就是通过反射来调用方法了
//先让方法变得可以访问
ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
//使用切面实例共产获取切面实例,然后执行建言方法
return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
}
catch (IllegalArgumentException ex) {
throw new AopInvocationException("Mismatch on arguments to advice method [" +
this.aspectJAdviceMethod + "]; pointcut expression [" +
this.pointcut.getPointcutExpression() + "]", ex);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
看到这,有点懵逼,@Before的特性呢?你这直接执行就完事了???
下面再看一看@After的
AspectJAfterAdvice
对应的invoke方法如下
可以看到,AspectJAfterAdvice是不一样的,其是使用invoke方法来进行执行的,并且还会先执行mi.proceed,最终才会进行invokeAdviceMethod(这个方法与AspectJMethodBeforeAdvice调用的before一样,这里不再赘述)
这里可以估计mi.proceed是执行原来方法的,后面的invokeAdviceMethod是执行@After的建言方法的,那么对于@Before而言是不是也应该有个mi.proceed方法的呢?只不过顺序会与@After的相反
后面看书发现,原来@Before对应的增强器其实是MethodBeforeAdviceInterceptor
里面就很清晰的说明了顺序,而且对应的也是invoke方法,先执行MethodBeforeAdvice的before方法,后面再执行mi.proceed继续被切方法的执行
但还是疑惑,明明返回的是AspectJMethodBeforeAdvice,是在哪里进行替换的呢?????
大致的结构是拦截器链中去放置这些AspectJxxxAdvice进行拦截,而对于前置增强就比较特殊点,放置的是MethodBeforeAdviceInterceptor
现在我们也完成根据切入点信息生成增强方法了
让我们返回上一层去,返回我们之前的ReflectiveAspectJAdvisorFactory的getAdvisors方法
接下来就应该到增加同步实例增强器了
增加同步实例增强器
首先会先判断,如果寻找到的增强器(建言)不为空并且又配置了增强延迟初始化,那么就需要在首位加入同步实例化增强器
咳咳,这里在首位添加是没问题的,上一篇不小心搞混了,add方法是会将原来的元素进行右移的,腾出一个位置之后进行添加新元素
源码如下
// If it's a per target aspect, emit the dummy instantiating aspect.
if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
//去实例化一个同步实例增强器
Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
//添加进advisors结果集中
advisors.add(0, instantiationAdvisor);
}
步骤很简单,就是判断完之后去实例化一个同步实例增强器,然后添加进advisors结果集中,关键还是实例化
可以看到,这个是所谓的同步实力增强器仅仅只是ReflectiveAspectJAdvisorFactory的一个静态内部类
可以看到这仅仅是构建了父类而已,接下来看看父类的构造方法
可以看到,参数是一个PointCut和一个Advice,这下大概明白了,其实所谓的同步实例增强器仅仅也只是一个增强器,也是跟前面的各种建言增强器一样的,都是放在拦截器链上的
回到同步实力增强器的构建方法上,看看其给的增强器做了什么
可以看到,这个肯定是实例化了一个MethodBeforeAdvice接口
并且跟AspectMethodBeforeAdvice类似都是重写的是before方法,而before方法的内容是使用切面实例工厂去创建一个切面。。。。。
Emmmm,有点懵逼,并且这个before方法在前面的AspectJMethodBeforeAdvice上看到的是由另外一个拦截器来进行的,那么对于这个同步实例增强器是不是应该也是由另外一个拦截器来进行的呢?????
获取DeclareParents注解
接下来就是最后的对DeclareParents注解进行解析了
可以看到是使用getDeclareParentsAdvisor方法进行的
源码如下
@Nullable
private Advisor getDeclareParentsAdvisor(Field introductionField) {
//从传进来的字段里面去找注解
DeclareParents declareParents = introductionField.getAnnotation(DeclareParents.class);
//判断是否找到
if (declareParents == null) {
// Not an introduction field
return null;
}
//判断DeclareParents的defaultImpl属性是不是与DeclareParents的类一样
//defaultImpl属性是用来定义待添加方法所在的类的,这个类要被bean托管
//所以这个判断就代表不支持添加DeclareParents的方法
if (DeclareParents.class == declareParents.defaultImpl()) {
throw new IllegalStateException("'defaultImpl' attribute must be set on DeclareParents");
}
//返回一个DeclareParentsAdvisor增强器
return new DeclareParentsAdvisor(
introductionField.getType(), declareParents.value(), declareParents.defaultImpl());
}
可以看到,步骤非常简单,因为这仅仅只是针对一个注解,而是形成的加强器也只有一个,不像建言注解一样,5个增强器提取出来
- 从传进来的字段里面寻找@DeclareParents注解
- 如果没找到,直接返回
- 找到了之后,校验@DeclareParents注解里面的defaultImpl属性,规定了defaultImpl属性对应的实现类的Class类型不能是DeclareParents属性的
- 创建DeclareParentsAdvisor增强器
至此,寻找所有的增强方法、增强器就结束了