文档章节

Spring 源码分析(三) —— AOP(四)获取指定增强

水门-kay
 水门-kay
发布于 2016/03/08 03:51
字数 2419
阅读 1270
收藏 7

获取指定增强

        上文中已经提到了获取指定增强方法的两个步骤:(1)获取所有的增强,(2)寻找所有增强中适用于 bean 的增强并应用,而这两个步骤是由 findCandidateAdvisors 和 findAdvisorsThatCanApply 来完成的。下图是相关的时序图:

        但在介绍源码前,我必须先了解一下增强以及其相关逻辑。


获取增强

        通知器的解析

        好了,让我们重新回到源码分析,值得注意的是 我们分析的是使用注释的 AOP,我们对于 findCandidateAdvisors 方法的实现进行追踪,在 AnnotationAwareAspectAutoProxyCreator 中发现了他的实现方法。

AnnotationAwareAspectAutoProxyCreator.java

@Override
protected List<Advisor> findCandidateAdvisors() {
   // 当使用注释方式配置 AOP 的时候并不是丢弃了对 XML 配置的支持。
   // 在这里调用父类方法加载配置文件中的 AOP 声明
   List<Advisor> advisors = super.findCandidateAdvisors();
   // Build Advisors for all AspectJ aspects in the bean factory.
   advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
   return advisors;
}

        而在 AnnotationAwareAspectAutoProxyCreator 的 findAdvisorBeans 方法里间接继承了 AbstractAutoProxyCreator 中的 findAdvisorBeans 方法,在实现中除了保留父类的获取配置文件中定义的增强外,同时添加了获取 Bean 的注解增强的功能,而真正实现是由 this.aspectJAdvisorsBuilder.buildAspectJAdvisors()来实现的。

        看下代码,我们就能明白其中的思路。首先,获取所有在 beanFacotry 中注册的 Bean 都会被提取出来。然后遍历所有 beanName,并找出声明 AspectJ 注释的类,进一步处理。最后,将将结果加入缓存。

BeanFactoryAspectJAdvisorsBuilder.java

public List<Advisor> buildAspectJAdvisors() {
   List<String> aspectNames = null;

   synchronized (this) {
      aspectNames = this.aspectBeanNames;
      if (aspectNames == null) {
         List<Advisor> advisors = new LinkedList<Advisor>();
         aspectNames = new LinkedList<String>();
         // 获取所有的 beanName
         String[] beanNames =
               BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
         // 循环所有的 beanName 找出对应的增强方法
         for (String beanName : beanNames) {
            // 不合法的 bean 则略过,由子类定义规则,默认返回 true
            if (!isEligibleBean(beanName)) {
               continue;
            }
            // 获取对应的 bean 的类型
            Class<?> beanType = this.beanFactory.getType(beanName);
            if (beanType == null) {
               continue;
            }
            // 如果存在 Aspect 注解
            if (this.advisorFactory.isAspect(beanType)) {
               aspectNames.add(beanName);
               AspectMetadata amd = new AspectMetadata(beanType, beanName);
               if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                  MetadataAwareAspectInstanceFactory factory =
                        new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                  // 解析标记 AspectJ 注解的增强方法
                  List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                  if (this.beanFactory.isSingleton(beanName)) {
                     this.advisorsCache.put(beanName, classAdvisors);
                  }
                  else {
                     this.aspectFactoryCache.put(beanName, factory);
                  }
                  advisors.addAll(classAdvisors);
               }
               else {
                  if (this.beanFactory.isSingleton(beanName)) {
                     throw new IllegalArgumentException("Bean with name '" + beanName +
                           "' is a singleton, but aspect instantiation model is not singleton");
                  }
                  MetadataAwareAspectInstanceFactory factory =
                        new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                  this.aspectFactoryCache.put(beanName, factory);
                  advisors.addAll(this.advisorFactory.getAdvisors(factory));
               }
            }
         }
         this.aspectBeanNames = aspectNames;
         return advisors;
      }
   }

   if (aspectNames.isEmpty()) {
      return Collections.emptyList();
   }
   // 记录在缓存中
   List<Advisor> advisors = new LinkedList<Advisor>();
   for (String aspectName : aspectNames) {
      List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
      if (cachedAdvisors != null) {
         advisors.addAll(cachedAdvisors);
      }
      else {
         MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
         advisors.addAll(this.advisorFactory.getAdvisors(factory));
      }
   }
   return advisors;
}

        至此,我们已经完成了通知器的解析。往下我们就介绍最重要的也是最繁琐的通知器的获取。


        获取通知器

        在上面的步骤中最为重要也最为繁琐的就是通知器的获取。而这一功能看源码,是由 AspectJAdviosrFactory 接口的 getAdvisors 方法来完成的,下图是 AspectJAdviosrFactory 的继承关系图:

        我们根据 AspectJAdviosrFactory 接口的继承关系,在 ReflectiveAspectJAdvisorFactory 类中找到了他的 getAdvisors 实现类。

ReflectiveAspectJAdvisorFactory.java 

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory maaif) {
   // 获取标记为 AspectJ 的类
   final Class<?> aspectClass = maaif.getAspectMetadata().getAspectClass();
   // 获取标记为 AspectJ 的name
   final String aspectName = maaif.getAspectMetadata().getAspectName();
   // 验证
   validate(aspectClass);

   final MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
         new LazySingletonAspectInstanceFactoryDecorator(maaif);

   final List<Advisor> advisors = new LinkedList<Advisor>();
   // 声明为 Pointcut 的方法不处理
   for (Method method : getAdvisorMethods(aspectClass)) {
      Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
      if (advisor != null) {
         advisors.add(advisor);
      }
   }

   if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
      // 如果寻找的增强器不为空而且又配置了增强延迟初始化那么需要在首位加入同步实例化增强器
      Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
      advisors.add(0, instantiationAdvisor);
   }

   // 获取 DeclareParents 注解
   for (Field field : aspectClass.getDeclaredFields()) {
      Advisor advisor = getDeclareParentsAdvisor(field);
      if (advisor != null) {
         advisors.add(advisor);
      }
   }

   return advisors;
}

        函数中首先完成了对增强器的获取,包括获取注解以及根据注解生成增强的步骤,然后考虑到在配置中可能会将增强配置成延迟初始化,那么需要在首位加入同步实例化增强以保证增强使用之前的实例化,最后对 DeclareParents 注解的获取,下面将详细介绍一下每个步骤。


        获取普通增强

        普通增强器的获取逻辑通过 getAdvisor 方法来实现,实现步骤包括对切点的注解以及根据注解信息生成增强。

ReflectiveAspectJAdvisorFactory.java 

@Override
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aif,
      int declarationOrderInAspect, String aspectName) {

   validate(aif.getAspectMetadata().getAspectClass());
   
   // 切点信息的获取
   AspectJExpressionPointcut ajexp =
         getPointcut(candidateAdviceMethod, aif.getAspectMetadata().getAspectClass());
   if (ajexp == null) {
      return null;
   }
   // 根据切点信息生成增强器
   return new InstantiationModelAwarePointcutAdvisorImpl(
         this, ajexp, aif, candidateAdviceMethod, declarationOrderInAspect, aspectName);
}

        (1)切点信息的获取。所谓获取切点信息就是指定注解的表达式信息的获取,如@Before("test()")。

ReflectiveAspectJAdvisorFactory.java 

private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
   // 获取方法上的注解
   AspectJAnnotation<?> aspectJAnnotation =
         AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
   if (aspectJAnnotation == null) {
      return null;
   }
   // 使用AspectJExpressionPointcut 实例封装获取的信息
   AspectJExpressionPointcut ajexp =
         new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
   // 提取得到的注解中的表达式如:
   // @Pointcut("execution(* test.TestBean.*(..))")
   ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
   return ajexp;
}

AbstractAspectJAdvisorFactory.java

protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
   // 设置敏感的注解类
   Class<?>[] classesToLookFor = new Class<?>[] {
         Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};
   for (Class<?> c : classesToLookFor) {
      AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) c);
      if (foundAnnotation != null) {
         return foundAnnotation;
      }
   }
   return null;
}

// 获取指定方法上的注解并使用 AspectJAnnotation 封装
private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
   A result = AnnotationUtils.findAnnotation(method, toLookFor);
   if (result != null) {
      return new AspectJAnnotation<A>(result);
   }
   else {
      return null;
   }
}

        (2)根据切点信息生成增强。所有的增强都由 Advisor 的实现类 InstantiationModelAwarePointcutAdvisorImpl 统一封装的。

InstantiationModelAwarePointcutAdvisorImpl.java 

public InstantiationModelAwarePointcutAdvisorImpl(AspectJAdvisorFactory af, AspectJExpressionPointcut ajexp,
      MetadataAwareAspectInstanceFactory aif, Method method, int declarationOrderInAspect, String aspectName) {
    
   // test()
   this.declaredPointcut = ajexp;
   // public void test.AspectJTest.beforeTest()
   this.method = method;
   
   this.atAspectJAdvisorFactory = af;
   this.aspectInstanceFactory = aif;
   this.declarationOrder = declarationOrderInAspect;
   this.aspectName = aspectName;

   if (aif.getAspectMetadata().isLazilyInstantiated()) {
      // Static part of the pointcut is a lazy type.
      Pointcut preInstantiationPointcut =
            Pointcuts.union(aif.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);

      this.pointcut = new PerTargetInstantiationModelPointcut(this.declaredPointcut, preInstantiationPointcut, aif);
      this.lazy = true;
   }
   else {
      // A singleton aspect.
      this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
      this.pointcut = declaredPointcut;
      this.lazy = false;
   }
}

        在封装过程中只是简单地将信息封装在类的实例中而已,所有的信息单纯地赋值,在实例初始化的过程中还完成了对于增强器的初始化。因为不同的增强所体现的逻辑是不同的,而根据注解中的信息初始化对应的增强器是在 instantiateAdvice 方法中完成的。

InstantiationModelAwarePointcutAdvisorImpl.java 

private Advice instantiateAdvice(AspectJExpressionPointcut pcut) {
   return this.atAspectJAdvisorFactory.getAdvice(
         this.method, pcut, this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
}

        而与 AspectJAdviosrFactory 接口的 getAdvisors 方法一样,getAdvice 方法也是在 ReflectiveAspectJAdvisorFactory 类中完成的。

ReflectiveAspectJAdvisorFactory.java

@Override
public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut ajexp,
      MetadataAwareAspectInstanceFactory aif, int declarationOrderInAspect, String aspectName) {

   Class<?> candidateAspectClass = aif.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 springAdvice;

   // 根据不同的注解类封装增强器
   switch (aspectJAnnotation.getAnnotationType()) {
      case AtBefore:
         springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, ajexp, aif);
         break;
      case AtAfter:
         springAdvice = new AspectJAfterAdvice(candidateAdviceMethod, ajexp, aif);
         break;
      case AtAfterReturning:
         springAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, ajexp, aif);
         AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
         if (StringUtils.hasText(afterReturningAnnotation.returning())) {
            springAdvice.setReturningName(afterReturningAnnotation.returning());
         }
         break;
      case AtAfterThrowing:
         springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, ajexp, aif);
         AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
         if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
            springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
         }
         break;
      case AtAround:
         springAdvice = new AspectJAroundAdvice(candidateAdviceMethod, ajexp, aif);
         break;
      case AtPointcut:
         if (logger.isDebugEnabled()) {
            logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
         }
         return null;
      default:
         throw new UnsupportedOperationException(
               "Unsupported advice type on method " + candidateAdviceMethod);
   }

   // Now to configure the advice...
   springAdvice.setAspectName(aspectName);
   springAdvice.setDeclarationOrder(declarationOrderInAspect);
   String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
   if (argNames != null) {
      springAdvice.setArgumentNamesFromStringArray(argNames);
   }
   springAdvice.calculateArgumentBindings();
   return springAdvice;
}

        从函数中,我们可以看出,Spring 会根据不同的注解生成不同的增强器,因为太过庞杂了,这里就不先讲述了,笔者会在后面介绍设计方法的时候慢慢讲述的。

        

        增强同步实例化增强器

        如果寻找的增强器不为空而且又配置了增强延迟初始化,那么就需要在首位加入同步实例化增强器,具体实现如下:

ReflectiveAspectJAdvisorFactory.java

protected static class SyntheticInstantiationAdvisor extends DefaultPointcutAdvisor {

   public SyntheticInstantiationAdvisor(final MetadataAwareAspectInstanceFactory aif) {
      super(aif.getAspectMetadata().getPerClausePointcut(), new MethodBeforeAdvice() {
         // 目标方法前调用,类似@Before
         @Override
         public void before(Method method, Object[] args, Object target) {
            // 简单初始化 aspect
            aif.getAspectInstance();
         }
      });
   }
}


        获取 DeclareParents 注解

        DeclareParents 主要用于引介增强的注解形式的实现,而其实现方式与普通增强很类似,只是做了封装。

ReflectiveAspectJAdvisorFactory.java

private Advisor getDeclareParentsAdvisor(Field introductionField) {
   DeclareParents declareParents = introductionField.getAnnotation(DeclareParents.class);
   if (declareParents == null) {
      // Not an introduction field
      return null;
   }

   if (DeclareParents.class.equals(declareParents.defaultImpl())) {
      // This is what comes back if it wasn't set. This seems bizarre...
      // TODO this restriction possibly should be relaxed
      throw new IllegalStateException("defaultImpl must be set on DeclareParents");
   }

   return new DeclareParentsAdvisor(
         introductionField.getType(), declareParents.value(), declareParents.defaultImpl());
}


寻找匹配的通知

        前面的函数中已经完成了所有的增强的解析,但对于所有增强来讲,并不一定都适用于当前的 Bean,还要挑选出适合的增强,也就是满足我们配置的通配符的增强器。具体实现在 findAdvisorsThatCanApply 中,而且 findAdvisorsThatCanApply 也比上面来的简单的多

AbstractAdvisorAutoProxyCreator.java

protected List<Advisor> findAdvisorsThatCanApply(
      List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {

   ProxyCreationContext.setCurrentProxiedBeanName(beanName);
   try {
      // 过滤已经得到的 advisors
      return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
   }
   finally {
      ProxyCreationContext.setCurrentProxiedBeanName(null);
   }
}

        继续看 findAdvisorsThatCanApply:

AopUtils.java

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
   if (candidateAdvisors.isEmpty()) {
      return candidateAdvisors;
   }
   List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
   // 首先处理引介增强
   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) {
         continue;
      }
      // 对于普通 bean 的处理
      if (canApply(candidate, clazz, hasIntroductions)) {
         eligibleAdvisors.add(candidate);
      }
   }
   return eligibleAdvisors;
}

        findAdvisorsThatCanApply 函数的主要功能是寻找所有增强器中适合于当前 class 的增强器。引介增强与普通的增强是处理不一样的,所以分开处理。而对于真正的匹配在 canApply 中实现。

AopUtils.java

public static boolean canApply(Advisor advisor, Class<?> targetClass) {
   return canApply(advisor, targetClass, false);
}

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
   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 {
      return true;
   }
}

        至此,getAdvicesAndAdvisorsForBean 方法要做的事就完成了,接下来就是 createProxy 方法生成代理了。

 


——水门(2016年3月于杭州

© 著作权归作者所有

水门-kay
粉丝 460
博文 19
码字总数 59660
作品 0
杭州
后端工程师
私信 提问
Spring 源码分析(三) —— AOP(三)实现思路

核心逻辑 上文中提到了 AOP 创建代理等等的具体操作都是在 AnnotationAwareAspectAutoProxyCreator 类中来成的,通过上文的自动注册,下面让我们看 AnnotationAwareAspectAutoProxyCreator ...

水门-kay
2016/03/08
1K
2
Spring中的AOP(三)——基于Annotation的配置方式(一)

AspectJ允许使用注解用于定义切面、切入点和增强处理,而Spring框架则可以识别并根据这些注解来生成AOP代理。Spring只是使用了和AspectJ 5一样的注解,但并没有使用AspectJ的编译器或者织入器...

摆渡者
2014/03/22
4K
0
Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密...

小致Daddy
2018/08/03
22K
1
深入聊一聊 Spring AOP 实现机制!

作者 | 张书康 责编 | 郭 芮 AOP(Aspect-Oriented Programming,即面向切面编程。Spring Aop 在 Spring框架中的地位举足轻重,主要用于实现事务、缓存、安全等功能。本篇主要是对源码进行深...

CSDN资讯
01/26
0
0
Spring中的AOP(五)——在Advice方法中获取目标方法的参数

获取目标方法的信息 访问目标方法最简单的做法是定义增强处理方法时,将第一个参数定义为JoinPoint类型,当该增强处理方法被调用时,该JoinPoint参数就代表了织入增强处理的连接点。JoinPoi...

摆渡者
2014/03/23
96.1K
9

没有更多内容

加载失败,请刷新页面

加载更多

Sqoop之导入Mysql数据到Hive出现ASCII

问题是这样的,从Mysql中导入数据到Hive中,Mysql中数据是"T2",到Hive中后,发现变为"54 32",咦,怎么乱码了,感觉这像ASCII编码。 现象有了,之前都没遇到过这样,觉得很奇怪,迅速找了下...

克虏伯
7分钟前
2
0
深度科技受邀参加鲲鹏计算产业峰会·海南

10月22日,华为及生态伙伴共同举办了以“鲲鹏展翅,力算未来 共赢多样性计算时代”为主题的“鲲鹏计算产业峰会•海南”,来自各领域的300多名行业客户、专家及生态伙伴齐聚一堂,共同分享计算...

后浪涛涛
8分钟前
2
0
【Android JetPack系列】LifeCycles

一、简介 Lifecycles 是一个持有组件生命周期状态(如活动或片段)信息的类,并允许其他对象观察此状态。 生命周期使用两个主要枚举来跟踪其关联组件的生命周期状态: Event:从 框架 和 Li...

Agnes2017
9分钟前
2
0
配置码云git自动更新的webhook

配置项目提交到git的时候自动同步服务器代码 一、在服务器项目跟目录新建文件hook.php 代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php $json = file_get_contents("php://input"); $data ......

dragon_tech
16分钟前
2
0
一道题深度剖析this指向

< script > var num = 1 ; var myObject = { num : 2 , add : function (){ this . num = 3 ; ( function (){ // 这里的 this 是 window,因为自调用函数的调用者是windo,使用箭头函数则指向......

李超明
19分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部