Spring AOP原理 收集切面和实例化增强器

以下源代码基于Spring aop 5.2.0

首先根据以往的知识,当一个类被 @Controller @Service @Configuration @Component 等注解加入IOC容器的时候,我们再次获取到的时候它们就有可能是经过动态代理增强后的代理对象。

这点想必很多人都知道,AOP只是一个概念、思想,实现AOP靠的是工具和方法,这种工具就是JDK动态代理和CGLIB (Class Generation Libary)。

参见:

JDK动态代理学习_Alan CGH的博客-CSDN博客

Cglib代理如何生成的及工作原理_Alan CGH的博客-CSDN博客

而现在SpringBoot 2.0以后,对于没有在配置文件中配置 aop.proxy-target-class 属性的,默认会采用Cglib代理。这涉及到SpringBoot AOP自动配置原理。请看 SpringBoot 2.2.6 AOP自动配置原理

AOP重要对象:

AnnotationAwareAspectJAutoProxyCreator切面自动代理创建器(处理所有符合切点的bean)
ProxyFactory代理工厂对象
DefaultAopProxyFactory 默认的AOP代理工厂
AdvisedSupport被代理对象的通知规则配置
JdkDynamicAopProxyJdk代理的 invocationHandler,会在这里调用Target的对应方法
CglibAopProxyCglib代理的 MethodInterceptor,代理对象中每个目标方法都会调用拦截器 intercept(),在其中调用Target对应方法

Spring中要开启aop:在xml中定义 <aop:aspectj-autoproxy> 标签,SpringBoot中对应的配置为 aop.auto=true。没有显式看到这个标签对么?其实在自动装配中已经配置好了,aop.auto默认是true,然后SpringBoot会加上 EnableAspectJAutoProxy注解,等效于<aop:aspectj-autoproxy>

基于对注解的认识,我们知道每一个注解都要有对应的解析器去处理,所以我们配置了aop,框架底层是如何去搜集到所有的切面类,解析其中的切点规则,再去寻找所有匹配的bean,然后如何运用通知的?

所以带着问题来学习会更有明确目标:

1、框架如何搜集到所有切面类

2、如何解析切面类中的切点规则

3、如何去匹配所有被任意切点命中的bean

4、如何运用通知增强目标方法

全局搜索<aop:aspectj-autoproxy>,AopNamespaceHandler中注册了 AutoProxyBeanDefinitionParser 自动代理解析器。

所有的解析器都实现 BeanDefinitionParser 接口,实现parser()。在parser()中又会去注册 AspectJAutoProxyCreator 切面自动代理创建器。

注册切面自动代理创建器

在AopNamespaceUtils中把AutoProxyCreator包装成一个BeanDefinition后,注册成一个组件,存在于解析器上下文中。

到这里Spring是注册了一个创建器,这个创建器是干什么的?

这个创建器是处理当前Spring上下文中所有的切面类和增强器的,所有带@AspectJ的类都会被自动识别并且如果Aop代理模式开启的话,这些切面类中的通知方法就会被运用起来。

现在我们知道是这个创建器里面有某些逻辑可以去处理切面类,并且把切面中的通知给运用起来,但是不知道是如何让通知生效的?也不知道它是怎么去找这些切面类的?看名字应该是自动代理对象的一个创建器。

慢慢解析,首先看到它的类图,实现了BeanPostProcessor接口,意味着这个自动代理创建器是对bean做处理的处理器,这个接口中有个关键方法

default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

   return bean;

}

按照注释,这个方法会在新创建的bean对象的所有初始化回调方法之后调用,什么是初始化回调方法?

例如 afterProperties() init-method 这些方法。在回调这个方法时,这个bean的所有成员变量已经被赋值了,最后方法返回时可以是原始对象或被包装后的对象。

一直向上追到父类 AbstractAutoProxyCreator 它实现了这个方法,并且注释说明了会把 bean 新创建的实例用拦截器包装成代理对象返回如果这个bean是符合被代理规则的话。

重点:实现了BeanPostProcessor接口的对象,提供处理方法。这个处理方法会在Spring容器中每个托管对象在被实例化后被调用,会调用这个 postProcessAfterInitialization(@Nullable Object bean, String beanName) 函数,托管对象被传进去进行必要的后置处理!!!

而这个 AbstractAutoProxyCreator 对象的后置处理方法就是专门用来处理当前这个托管对象是否被任何切面的切点命中,如果被切点命中就会包装成一个代理对象返回!而这个函数内部第一次被调用时也会去实例化Spring中所有的切面对象和通知方法!

所以这就是项目启动后,我们在代码里面注入的对象可能是一个代理对象的真正的奥秘所在!

/**
 * Create a proxy with the configured interceptors if the bean is
 * identified as one to proxy by the subclass.
 * @see #getAdvicesAndAdvisorsForBean
 */
@Override
public ob<x>ject postProcessAfterInitialization(@Nullable ob<x>ject bean String beanName) {
   if (bean != null) {
      // 用目标bean的Class对象和名字做成缓存key
      ob<x>ject cacheKey = getCacheKey(bean.getClass() beanName);
      if (this.earlyProxyReferences.remove(cacheKey) != bean) {
         // 进一步判断有无必要针对这个对象创建代理
         return wrapIfNecessary(bean beanName cacheKey);
      }
   }
   return bean;
}

生成代理对象的入口

protected ob<x>ject wrapIfNecessary(ob<x>ject bean String beanName ob<x>ject cacheKey) {
   if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
      return bean;
   }
     // 这个对象已经被处理过了,并且被判断为是不需要代理的。
      // advisedBeans 是ConcurrentHashMap<CacheKey Boolean>,作为一个缓存存储项目中所有bean的代理情况信息
   if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
   }
   // 如果是基础设施的对象或被配置了规则应该跳过的,就不代理,并且将结果缓存起来
   if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass() beanName)) {
      this.advisedBeans.put(cacheKey Boolean.FALSE);
      return bean;
   }
   // 这里是去找这个目标对象是否有拦截器(就是通知方法),以此为判断依据是否要代理
   ob<x>ject[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass() beanName null);
   if (specificInterceptors != DO_NOT_PROXY) {
      // 标记一下这个bean是一个增强bean
      this.advisedBeans.put(cacheKey Boolean.TRUE);
       // 有拦截器就去创建代理对象,这里的createProxy()实际上就是衔接 CglibAopProxy 和 JdkAopProxy 的入口
      ob<x>ject proxy = createProxy(
            bean.getClass() beanName specificInterceptors new SingletonTargetSource(bean));
      // 拿到代理对象,缓存起来,proxyTypes就是专门存储代理对象Class对象的容器
      this.proxyTypes.put(cacheKey proxy.getClass());
      return proxy;
   }
   this.advisedBeans.put(cacheKey Boolean.FALSE);
   return bean;
}

从上面代码得知,一切的入口就是从获取增强器开始的,这个方法很重要,因为增强器就是代理对象工作的核心。向上追踪到 AbstractAdvisorAutoProxyCreator,看名字就知道找到目标对象的所有可以运用在它身上的增强器。

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass String beanName) {
    // 这一步是找到当前spring中所有的增强器
   List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // 从所有增强器中筛选出能运用在目标对象上的增强器,返回
   List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors beanClass beanName);
   extendAdvisors(eligibleAdvisors);
   if (!eligibleAdvisors.isEmpty()) {
      eligibleAdvisors = sortAdvisors(eligibleAdvisors);
   }
   return eligibleAdvisors;
}

findCandidateAdvisors() 由子类复写,因为研究注解式的@AspectJ,它的处理器是 AnnotationAwareAspectJAutoProxyCreator,它的父类 AspectJAwareAdvisorAutoProxyCreator 是处理XML配置文件的声明式AOP。

@Override
protected List<Advisor> findCandidateAdvisors() {
   // Add all the Spring advisors found according to superclass rules.
   // 这里调用父类的方法找到所有定义在xm<x>l中的AOP通知,可见向上兼容了,并没有抛弃xm<x>l
   List<Advisor> advisors = super.findCandidateAdvisors();
   // Build Advisors for all AspectJ aspects in the bean factory.
   if (this.aspectJAdvisorsBuilder != null) {
      // 这里才是去处理注解AOP通知,重点地方
      advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
   }
   return advisors;
}

终于到了一个关键地方,这里能解答AOP工作原理之一,框架是如何搜寻所有的切面类的?

注释说了,在当前Spring bean 工厂中搜索带@AspectJ的类,把它们中的所有通知方法包装成增强器返回。至少这里知道一点,这些通知器是从所有被Spring托管的对象中创建的,Spring托管对象不一定是代理对象,只有被AOP动态代理的才会生成代理对象

public List<Advisor> buildAspectJAdvisors() {
    // 当前容器中所有切面类的名称
   List<String> aspectNames = this.aspectBeanNames;
    // 重点分支
   if (aspectNames == null) {
      synchronized (this) {
         aspectNames = this.aspectBeanNames;
           // 锁双重判断机制,广泛运用在单例模式中
         if (aspectNames == null) {
           // 装载所有增强方法的容器
            List<Advisor> advisors = new ArrayList<>();
            aspectNames = new ArrayList<>();
           // 因为切面名称是空,现在去获取所有bean的名称。在beanFactory中注册过的bean的名称都会被提取出来
            String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                  this.beanFactory ob<x>ject.class true false);
           // 遍历所有bean,找出带有@AspectJ的
            for (String beanName : beanNames) {
               if (!isEligibleBean(beanName)) {
                  continue;
               }
              // 从当前bean工厂中获取它的Class对象
               Class<?> beanType = this.beanFactory.getType(beanName);
               if (beanType == null) {
                  continue;
               }
              // 如果Class对象带有@AspectJ,就命中
               if (this.advisorFactory.isAspect(beanType)) {
                  // 把名字加入切面集合中去
                  aspectNames.add(beanName);
                  // 包装成切面对象元数据
                  Aspectme<x>tadata amd = new Aspectme<x>tadata(beanType beanName);
                  if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                     me<x>tadataAwareAspectInstanceFactory factory =
                           new BeanFactoryAspectInstanceFactory(this.beanFactory beanName);
                  // 关键,获取到切面中的增强方法
                     List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                  // 按照单例与否,不同的缓存策略,单例就直接将名字和切面中的增强方法缓存起来
                     if (this.beanFactory.isSingleton(beanName)) {
                        this.advisorsCache.put(beanName classAdvisors);
                     }
                     else {
                         // 这个bean是多实例的,将切面实例工厂缓存起来,后续通过这个工厂实例获取增强方法
                        this.aspectFactoryCache.put(beanName factory);
                     }
                     advisors.addAll(classAdvisors);
                  }
                  else {
                     // 切面类是多例的逻辑,不关注
                  }
               }
            }
            this.aspectBeanNames = aspectNames;
            return advisors;
         }
      }
   }
    // 容器非Null,但是空集合,证明前面一定做过初始化逻辑,但是当前没有切面
   if (aspectNames.isEmpty()) {
      return Collections.emptyList();
   }
    // 走到这里是第N次获取,直接遍历切面,收集增强器返回就行
   List<Advisor> advisors = new ArrayList<>();
   for (String aspectName : aspectNames) {
      List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
      if (cachedAdvisors != null) {
         advisors.addAll(cachedAdvisors);
      }
      else {
         me<x>tadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
         advisors.addAll(this.advisorFactory.getAdvisors(factory));
      }
   }
   return advisors;
}

读完以上代码,我们知道了原来所有的切面是从beanFactory里解析所有带@AspectJ注解的类获取的,还要提取其中的增强方法包装成增强器返回。但是是如何解析通知方法的我们还不知道,原理就在ReflectiveAspectJAdvisorFactory.getAdvisor() 中实现

@Override
public List<Advisor> getAdvisors(me<x>tadataAwareAspectInstanceFactory aspectInstanceFactory) {
   Class<?> aspectClass = aspectInstanceFactory.getAspectme<x>tadata().getAspectClass();
   String aspectName = aspectInstanceFactory.getAspectme<x>tadata().getAspectName();
    // 进一步校验当前的切面类是不是切面
   validate(aspectClass);
   // We need to wrap the me<x>tadataAwareAspectInstanceFactory with a decorator
   // so that it will only instantiate once.
   me<x>tadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
         new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
   List<Advisor> advisors = new ArrayList<>();
    // 获取到切面类中不是@Pointcut的方法,通过反射拿到类中所有方法方法
   for (Method method : getAdvisorMethods(aspectClass)) {
      // 遍历每个方法,获取到真正的增强方法,判断方法上是否带有@Before @After等AOP注解
      // 如果有注解根据不同的注解处理器选择对应的aop增强对象
      Advisor advisor = getAdvisor(method lazySingletonAspectInstanceFactory advisors.size() aspectName);
      if (advisor != null) {
         advisors.add(advisor);
      }
   }
   // If it's a per target aspect emit the dummy instantiating aspect.
   if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectme<x>tadata().isLazilyInstantiated()) {
      Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
      advisors.add(0 instantiationAdvisor);
   }
   // Find introduction fields.
   for (Field field : aspectClass.getDeclaredFields()) {
      Advisor advisor = getDeclareParentsAdvisor(field);
      if (advisor != null) {
         advisors.add(advisor);
      }
   }
   return advisors;
}
private List<Method> getAdvisorMethods(Class<?> aspectClass) {
   final List<Method> methods = new ArrayList<>();
   ReflectionUtils.doWithMethods(aspectClass method -> {
      // 将不是 @Pointcut 的方法搜集起来
      if (AnnotationUtils.getAnnotation(method Pointcut.class) == null) {
         methods.add(method);
      }
   } ReflectionUtils.USER_DECLARED_METHODS);
   methods.sort(METHOD_COMPARATOR);
   return methods;
}
public Advisor getAdvisor(Method candidateAdviceMethod me<x>tadataAwareAspectInstanceFactory aspectInstanceFactory
      int declarationOrderInAspect String aspectName) {
   validate(aspectInstanceFactory.getAspectme<x>tadata().getAspectClass());
    // 从这个候选方法中尝试获取切点表达式,注意这里的切点表达式不是单指 @Pointcut这个注解
    // 实际上AOP中的 @Before @After @AfterReturning @AfterThrowing 这些注解都可以指定切点
    // 并且这里的每一个 candidateAdviceMethod 都不会是 @Pointcut 因为前面过滤过了
   AspectJex<x>pressionPointcut ex<x>pressionPointcut = getPointcut(
         candidateAdviceMethod aspectInstanceFactory.getAspectme<x>tadata().getAspectClass());
    // 如果获取不到切点表达式,这个方法就不是通知方法,返回空
   if (ex<x>pressionPointcut == null) {
      return null;
   }
    // 根据切点表达式生成增强器
   return new InstantiationModelAwarePointcutAdvisorImpl(ex<x>pressionPointcut candidateAdviceMethod
         this aspectInstanceFactory declarationOrderInAspect aspectName);
}
private AspectJex<x>pressionPointcut getPointcut(Method candidateAdviceMethod Class<?> candidateAspectClass) {
    // 找到方法上的AOP切点注解
   AspectJAnnotation<?> aspectJAnnotation =
         AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    // 没有切点注解,是Class类中的此方法不是通知方法
   if (aspectJAnnotation == null) {
      return null;
   }
   AspectJex<x>pressionPointcut ajexp =
         new AspectJex<x>pressionPointcut(candidateAspectClass new String[0] new Class<?>[0]);
    // 封装好注解上的切点表达式返回
   ajexp.setex<x>pression(aspectJAnnotation.getPointcutex<x>pression());
   if (this.beanFactory != null) {
      ajexp.setBeanFactory(this.beanFactory);
   }
   return ajexp;
}
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
    /*
           这个找指定AOP注解的方法也很简单,就是遍历尝试下面的注解
           private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
                    Pointcut.class Around.class Before.class After.class AfterReturning.class AfterThrowing.class
            };
    */
   for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
      AspectJAnnotation<?> foundAnnotation = findAnnotation(method (Class<Annotation>) clazz);
      // 如果方法中有任意一个切点注解就返回这个注解
      if (foundAnnotation != null) {
         return foundAnnotation;
      }
   }
   return null;
}

从 ReflectiveAspectJAdvisorFactory.getAdvisor() 开始分析,读完上面的代码,我们知道了:

当前的类对象是确定了的切面类,任务就是提取其中的所有通知方法。通过反射拿到所有声明方法对象,过滤掉@Pointcut方法,遍历这些方法,筛选出那些@Before @After等通知方法,拿到注解上的表达式值,根据表达式值生成增强器返回。

当循环结束,就收集到了切面中所有通知方法且转为了增强器。其中增强器是由对象 InstantiationModelAwarePointcutAdvisorImpl 实现的。注释说明了对于每一个目标方法,这个通知器都会生成一个实例,也就是被这个通知器的切点切到的方法都会被运用这个通知。

在实例化它的时候,它获取到了:切点、这个增强器所代表的增强方法、方法参数等等...

@SuppressWarnings("serial")
final class InstantiationModelAwarePointcutAdvisorImpl
      implements InstantiationModelAwarePointcutAdvisor AspectJPrecedenceInformation Serializable {
         
   private static final Advice EMPTY_ADVICE = new Advice() {};
   private final AspectJex<x>pressionPointcut declaredPointcut;
   private final Class<?> declaringClass;
   private final String methodName;
   private final Class<?>[] parameterTypes;
   private transient Method aspectJAdviceMethod;
   private final AspectJAdvisorFactory aspectJAdvisorFactory;
   private final me<x>tadataAwareAspectInstanceFactory aspectInstanceFactory;
   private final int declarationOrder;
   private final String aspectName;
   private final Pointcut pointcut;
   private final boolean lazy;
   @Nullable
   private Advice instantiatedAdvice;// 增强方法,实例化的方法是下面的
   private Advice instantiateAdvice(AspectJex<x>pressionPointcut pointcut) {
       Advice advice = this.aspectJAdvisorFactory.getAdvice(
         this.aspectJAdviceMethod // 切面类中的增强方法
               pointcut// 切点对象,对应@Before @After等AOP注解
           this.aspectInstanceFactory // 切面实例工厂
           this.declarationOrder
           this.aspectName);
       return (advice != null ? advice : EMPTY_ADVICE);
    }
   ... 省略

完成这些简单的初始化赋值后还有个成员变量 private Advice instantiatedAdvice 它是代表增强方法的实例对象,它的实例化策略不同,如果是懒加载的就会在第一次调用这个通知器的时候通过 getAdvice()实例化,否则就直接实例化,这2钟策略都是通过 instantiateAdvice() 实现。不同的切点注解有不同的运用逻辑 ,好比@Before 和 @After 就不同,它们对于目标方法的增强时机不同,所以需要不同的增强器实例来实现。这个方法的上半部分逻辑和前面的getAdvice()很相似,但它们是2个不同的方法。

public Advice getAdvice(Method candidateAdviceMethod AspectJex<x>pressionPointcut ex<x>pressionPointcut
      me<x>tadataAwareAspectInstanceFactory aspectInstanceFactory int declarationOrder String aspectName) {
   Class<?> candidateAspectClass = aspectInstanceFactory.getAspectme<x>tadata().getAspectClass();
   validate(candidateAspectClass);
    // 去找到这个方法上的切点注解
    // 如果是InstantiationModelAwarePointcutAdvisorImpl对象的调用,这里aspectJAnnotation不可能为空
   AspectJAnnotation<?> aspectJAnnotation =
         AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
   if (aspectJAnnotation == null) {
      return null;
   }
   AbstractAspectJAdvice springAdvice;
    // 根据通知的注解类型选择对应的增强器实现
   switch (aspectJAnnotation.getAnnotationType()) {
      case AtPointcut:
         if (logger.isDebugEnabled()) {
            logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
         }
         return null;
      case AtAround:
         springAdvice = new AspectJAroundAdvice(
               candidateAdviceMethod ex<x>pressionPointcut aspectInstanceFactory);
         break;
      case AtBefore:
         springAdvice = new AspectJMethodBeforeAdvice(
               candidateAdviceMethod ex<x>pressionPointcut aspectInstanceFactory);
         break;
      case AtAfter:
         springAdvice = new AspectJAfterAdvice(
               candidateAdviceMethod ex<x>pressionPointcut aspectInstanceFactory);
         break;
      case AtAfterReturning:
         springAdvice = new AspectJAfterReturningAdvice(
               candidateAdviceMethod ex<x>pressionPointcut aspectInstanceFactory);
         AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
         if (StringUtils.hasText(afterReturningAnnotation.returning())) {
            springAdvice.setReturningName(afterReturningAnnotation.returning());
         }
         break;
      case AtAfterThrowing:
         springAdvice = new AspectJAfterThrowingAdvice(
               candidateAdviceMethod ex<x>pressionPointcut aspectInstanceFactory);
         AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
         if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
            springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
         }
         break;
      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;
}

OK,@Before对应AspectMethodBeforeAdvice,@After对应.....等等,想知道这些增强器的实现原理?那就去看这几个增强器的实现就行了吧!以最常用的@Before @After来看,其中这几个增强器的实现又有点不同,MethodBeforeAdvice、ReturningAdvice都是通过中间类MethodIntecerptor连接上目标方法的以此实现增强通知。MethodAfterAdvice、AroundAdvice、AfterThrowingAdvice都是直接实现MethodInterceptor,在创建代理对象时会将它们当做拦截器注入到代理对象中,当代理对象调用目标方法时会先调用拦截器的invoke(),在invoke()中调用切面对象的通知方法。BeforeAdvice会被注入 BeforeAdviceInterceptor,before()中调用切面对象的增强方法,以此实现通知和目标方法的连接。

这个拦截器中通过反射先调用增强对象的增强方法,返回后调用后续的拦截器链

来看看 AfterAdvice 直接实现拦截器,在invoke()中先调用方法的拦截器链,返回后调用自己的增强方法。

OK,走到这一步已经完成了对Spring中所有切面类的增强方法的收集,此时Spring容器中有了所有的切面对象,切面对象中所有的增强方法,并且根据通知策略的不同被包装成了不同的增强器,有些直接实现了方法拦截器,有些靠一个中间的拦截器被注入到代理对象中。

现在回顾一下调用链:

  • AutoProxyCreator.postProcessAfterInitialization() ->
  • getAdviesAndAdvisorsForBean() ->
  • findCandidateAdvisor() ->
  • findAdvisorsThatCanApply() ->
  • buildAspectJAdvisor() ->
  • ReflectiveAspectJAdvisorFactory.getAdvisor() ->
  • PointcutAdvisorImpl对象 -> 在对象内部 instantiateAdvice() -> getAdvice()

最终根据通知策略不同分别返回:BeforeAdvice AfterAdvice 等增强器对象(有些通知器对象直接实现方法拦截器称为拦截器,有些依靠中间类方法拦截器)

现在注意力回到调用链的 findEligibleAdvisor() 函数中,上面那么多的逻辑也只是走完了这一步方法,实例化并创建了系统中所有的切面类和增强方法而已(如果是第二个托管bean,它们可能从缓存中取)。但是现在针对这个托管bean,我们并不知道它被哪些切点命中了,这些增强器哪一些可以运用在这个bean上。所以下一步就是去判断哪些增强器是合资格的。

接下来的findAdvisorThatCanApply()的旅程就需要Debugg追踪,更加清楚了。

就以项目中 ParameterCheckAspect 切面举例,它是我在项目中编写用来校验每个Controller入参的参数合法性。现在断点拦截到一个 OrderController 的对象。可以看到当前的通知器集合中包含了Method类型的 doCheckParameterValidity() 通知方法,它的增强器类型是BeforeAdvice。切点 pointcut是AspectJExpressionPointcut类型包含了切点表达式。

这个和借助@Pointcut注解的切点是不同的。

这个增强器的注解直接指定表达式,没有靠@Pointcut。

在切点匹配目标对象中决定当前通知器是否可运用在当前目标对象上最关键的方法就是 canApply()。在图中可以看到 MethodMatcher 是用来决定最后匹配目标对象所有方法的对象,遍历目标对象的接口方法和它自己声明的方法,只要找到一个符合切点表达式就返回。

来看看canApply方法,怎么决定一个Class类是否被代理的

public static boolean canApply(
Pointcut pc, // 切点对象
Class<?> targetClass, // 目标Class
boolean hasIntroductions) {

		Assert.notNull(pc, "Pointcut must not be null");
		if (!pc.getClassFilter().matches(targetClass)) {
			return false;
		}

        // 一般是AOP注解上的切点表达式封装成的匹配对象
		MethodMatcher methodMatcher = pc.getMethodMatcher();
		if (methodMatcher == MethodMatcher.TRUE) {
			// 如果表达式直接命中所有方法,直接就返回
			return true;
		}

		Set<Class<?>> classes = new LinkedHashSet<>();
		if (!Proxy.isProxyClass(targetClass)) {
            // 这里以防万一,还是去拿了目标类的真实父类,对于CGlib代理是会生成子类继承目标类的
			classes.add(ClassUtils.getUserClass(targetClass));
		}
        // 从这里可以看出是拿到目标类的所有接口
		classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
        // 遍历目标所有接口和父类
		for (Class<?> clazz : classes) {
            // 拿到接口或父类的声明方法
			Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
            // 遍历方法,看看有没有增强器能匹配上的
            // 注意这里虽然拿到接口和类的所有声明方法,但是不代表能够匹配非public方法
			for (Method method : methods) {
				if (introductionAwareMethodMatcher != null ?
						introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
                        // 应用不同的AOP增强器的方法匹配器策略,匹配方法是否命中
                        methodMatcher.matches(method, targetClass)) {
					return true;
				}
			}
		}

		return false;
	}

canApply()之后的函数调用链还很长,最后的匹配工作是靠 AspectJ包下的SignaturePattern做的。这里只介绍重点的流程:

因为是方法匹配,所以来到 SignaturePattern.matchesExactlyMethod() 中,仔细观察切点表达式

public * com.byb.sznews.api.*.*.*(..)

再看看当前匹配方法 

com.byb.framework.result.JsonResult com.byb.sznews.api.order.OrderController.list(java.lang.Integer, java.lang.Integer)

如果是自己写这个匹配过程的话你能不能写出来?有什么思路?

切点中第一个参数 Public 代表匹配方法的修饰符,第二个参数匹配方法的返回类型,第三个一长串代表方法的全限定类路径名。

是不是可以设计一个算法,按照这个规则逐一匹配?

遇到模糊*,代表true,否则严格匹配每个 "." 之间的路径,直至方法的签名全部匹配上切点。

实际上框架内也差不多是这么做的,匹配名字,声明类型,返回类型,参数类型,抛出异常类型等等...  实际上没必要看到这里这么细的东西,知道Spring AOP最后是拿到切点表达式交给AspectJ匹配方法的全限定名,参数类型,返回值等等就行了...

全部校验点匹配上后代表当前这个方法匹配成功,返回true,一路返回到 canApply() 中返回true,回到findAdvisorThatCanApply()中将当前符合的通知器加入集合,继续遍历下一个候选通知器,等待全部收集完毕,再一路返回到整个AOP调用链最开始的地方 AutoProxyCreator 类中的postProcessAfterInitialization()的后置处理方法,现在这里就拿到了针对这个托管bean的所有匹配上的拦截器,下一步就是创建代理对象,根据策略选择JDK/Cglib,动态注入拦截器链到代理对象中,将这个代理对象用beanName缓存起来。

你以为这里就完了吗?还没有,上面说的这里开始进入下一阶段,创建代理对象。入口函数是 AbstractAutoProxyCreator.createProxy()

By the way:

我们注意到canApply方法是对目标类的所有接口和父类的所有方法进行匹配,看看哪个方法能够命中切点。那是不是AOP增强器能应用到非public方法呢?

答案是否定的,这里只是返回这个类能被切点命中,最后创建代理对象,注入增强器时会有选择的过滤非public方法。想想也知道,JDK动态代理是针对接口做代理,接口中所有声明的方法都是public类型,所以目标类的所有接口的方法都是public的。剩下父类或者自己,的确可能存在private方法,在cglib代理生成中,Enhancer#generateClass 这一步会调用

getMethods(sc, interfaces, actualMethods, interfaceMethods, forcePublic);

此函数会过滤掉private方法,生成的代理对象是没有private方法的,已实验。

同样@Transactional注解也不支持非public方法的应用,因为在方法中会校验目标方法的修饰符,对于非public方法是返回false

TransactionAttributeSourcePointcut.matches(Method method, Class<?> targetClass)

Data Access

参考文章:

SpringAOP私有方法导致注入失败原理_叮叮123232的博客-CSDN博客_aop 私有方法

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值