文档章节

Spring Aop之Advisor解析

爱宝贝丶
 爱宝贝丶
发布于 08/17 21:35
字数 3858
阅读 300
收藏 9

       在上文Spring Aop之Target Source详解中,我们讲解了Spring是如何通过封装Target Source来达到对最终获取的目标bean进行封装的目的。其中我们讲解到,Spring Aop对目标bean进行代理是通过AnnotationAwareAspectJAutoProxyCreator.postProcessAfterInitialization()进行的,Spring Aop的代理主要分为三个步骤:获取所有的Advisor,过滤可应用到当前bean的Adivsor和使用Advisor为当前bean生成代理对象。本文主要对这三步中的第一步获取所有的Advisor进行讲解。

1. 骨架方法

       首先我们看看postProcessAfterInitialization()方法的实现:

public Object postProcessAfterInitialization(@Nullable Object bean, 
       String beanName) throws BeansException {
    if (bean != null) {
        // 获取当前bean的key:如果beanName不为空,则以beanName为key,如果为FactoryBean类型,
        // 前面还会添加&符号,如果beanName为空,则以当前bean对应的class为key
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        // 判断当前bean是否正在被代理,如果正在被代理则不进行封装
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            // 对当前bean进行封装
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

       从上述代码可以看出,对目标bean的封装是主要是通过wrapIfNecessary()方法进行的,该方法就是Spring对目标bean进行代理的骨架方法。如下是该方法的实现:

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    // 判断当前bean是否在TargetSource缓存中存在,如果存在,则直接返回当前bean。这里进行如此判断的
    // 原因是在上文中,我们讲解了如何通过自己声明的TargetSource进行目标bean的封装,在封装之后其实
    // 就已经对封装之后的bean进行了代理,并且添加到了targetSourcedBeans缓存中。因而这里判断得到
    // 当前缓存中已经存在当前bean,则说明该bean已经被代理过,这样就可以直接返回当前bean。
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    
    // 这里advisedBeans缓存了已经进行了代理的bean,如果缓存中存在,则可以直接返回
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    
    // 这里isInfrastructureClass()用于判断当前bean是否为Spring系统自带的bean,自带的bean是
    // 不用进行代理的;shouldSkip()则用于判断当前bean是否应该被略过
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        // 对当前bean进行缓存
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // 获取当前bean的Advices和Advisors
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        // 对当前bean的代理状态进行缓存
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // 根据获取到的Advices和Advisors为当前bean生成代理对象
        Object proxy = createProxy(
            bean.getClass(), beanName, specificInterceptors, 
            new SingletonTargetSource(bean));
        // 缓存生成的代理bean的类型,并且返回生成的代理bean
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

       在上述骨架方法中,Spring主要进行了三件事:

  • 判断当前bean是否已经生成过代理对象,或者是否是应该被略过的对象,是则直接返回,否则进行下一步;
  • 获取当前bean的Advisors和Advices,如果当前bean不需要代理,则返回DO_NOT_PROXY;
  • 通过生成的Advisors和Advices为目标bean生成代理对象。

       关于上述骨架方法,这里需要说明两个点:

  • shouldSkip()方法中对当前bean判断是否应该略过时,其主要做了两件事:a. 为当前bean生成需要代理的Advisors;b. 判断生成的Advisor是否为AspectJPointcutAdvisor类型。因而实际上判断略过的过程就是判断是否为AspectJPointcutAdvisor,判断这个类的原因在于Spring Aop的切面和切点的生成也可以通过在xml文件中使用<aop:config/>标签进行。这个标签最终解析得到的Adivsor类型就是``AspectJPointcutAdvisor类型的,因为其在解析aop:config/的时候就已经生成了Advisor,因而这里需要对这种类型的Advisor进行略过。这里aop:config/`也是一种自定义标签,关于其解析过程,读者可以参照本人前面的博文自行阅读器源码;
  • getAdvicesAndAdvisorsForBean()方法就其名称而言是获取Advisors和Advices,但实际上其返回值是一个Advisor的数组。Spring Aop在为目标bean获取需要进行代理的切面逻辑的时候最终得到的是Advisor,这里Advice表示的是每个切面逻辑中使用@Before@After@Around等需要织入的代理方法。因为每个代理方法都表示一个Advice,并且每个代理方法最终都会生成一个Advisor,因而Advice和Advisor就本质而言其实没有太大的区别。Advice表示需要织入的切面逻辑,而Advisor则表示将切面逻辑进行封装之后的织入者。

2. 切面的生成

       虽然在shouldSkip()方法中会为当前bean生成Advisor,但是在getAdvicesAndAdvisorsForBean()中也还是会获取一次,只不过在第一次生成的时候会将得到的Advisor都进行缓存,因而第二次获取时可以直接从缓存中获取。我们这里还是以getAdvicesAndAdvisorsForBean()方法为准来进行讲解。如下是该方法的源码:

protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, 
        @Nullable TargetSource targetSource) {
    // 为目标bean生成Advisor
    List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) {
        return DO_NOT_PROXY;
    }
    return advisors.toArray();
}

       我们继续看findEligibleAdvisors()方法:

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // 将当前系统中所有的切面类的切面逻辑进行封装,从而得到目标Advisor
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    // 对获取到的所有Advisor进行判断,看其切面定义是否可以应用到当前bean,从而得到最终需要应用的Advisor
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, 
        beanClass, beanName);
    // 提供的hook方法,用于对目标Advisor进行扩展
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        // 对需要代理的Advisor按照一定的规则进行排序
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

       在上述方法中,Spring Aop首先获取到了系统中所有的切面逻辑,并将其封装为了Advisor对象,然后通过遍历Advisor判断哪些Advisor是可以应用到当前bean的,最后将需要织入的Advisor返回。这里我们看看findCandidateAdvisors()的源码:

protected List<Advisor> findCandidateAdvisors() {
    // 找到系统中实现了Advisor接口的bean
    List<Advisor> advisors = super.findCandidateAdvisors();
    if (this.aspectJAdvisorsBuilder != null) {
        // 找到系统中使用@Aspect标注的bean,并且找到该bean中使用@Before,@After等标注的方法,
        // 将这些方法封装为一个个Advisor
        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    }
    return advisors;
}

       可以看到,findCandidateAdvisors()主要是通过两种方式获取切面逻辑,一种是在系统中找到实现了Advisor接口的所有类,另一种是在找到系统中使用@Aspect标注的类,并将其切面逻辑封装为Advisor,这两种Advisor都有可能是我们需要进行织入的切面逻辑。这里super.findCandidateAdvisors()方法最终调用的是BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans()方法,我们首先看看该方法的实现:

public List<Advisor> findAdvisorBeans() {
    String[] advisorNames = null;
    synchronized (this) {
        advisorNames = this.cachedAdvisorBeanNames;
        if (advisorNames == null) {
            // 获取当前BeanFactory中所有实现了Advisor接口的bean的名称
            advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                this.beanFactory, Advisor.class, true, false);
            this.cachedAdvisorBeanNames = advisorNames;
        }
    }
    if (advisorNames.length == 0) {
        return new LinkedList<>();
    }

    // 对获取到的实现Advisor接口的bean的名称进行遍历
    List<Advisor> advisors = new LinkedList<>();
    for (String name : advisorNames) {
        // isEligibleBean()是提供的一个hook方法,用于子类对Advisor进行过滤,这里默认返回值都是true
        if (isEligibleBean(name)) {
            // 如果当前bean还在创建过程中,则略过,其创建完成之后会为其判断是否需要织入切面逻辑
            if (this.beanFactory.isCurrentlyInCreation(name)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipping currently created advisor '" + name + "'");
                }
            } else {
                try {
                    // 将当前bean添加到结果中
                    advisors.add(this.beanFactory.getBean(name, Advisor.class));
                } catch (BeanCreationException ex) {
                    // 对获取过程中产生的异常进行封装
                    Throwable rootCause = ex.getMostSpecificCause();
                    if (rootCause instanceof BeanCurrentlyInCreationException) {
                        BeanCreationException bce = (BeanCreationException) rootCause;
                        String bceBeanName = bce.getBeanName();
                        if (bceBeanName != null && 
                            this.beanFactory.isCurrentlyInCreation(bceBeanName)) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Skipping advisor '" + name + 
                                    "' with dependency on currently created bean: " 
                                    + ex.getMessage());
                            }
                            continue;
                        }
                    }
                    throw ex;
                }
            }
        }
    }
    return advisors;
}

       这里findAdvisorBeans()方法逻辑其实非常简单,其主要是在BeanFactory中找打实现了Advisor接口的类,然后通过hook方法判断子类是否需要对Advisor进行过滤,最后将过滤之后的Advisor返回。

       接下来我们看看BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors()的实现:

public List<Advisor> buildAspectJAdvisors() {
    List<String> aspectNames = this.aspectBeanNames;
    if (aspectNames == null) {
        synchronized (this) {
            aspectNames = this.aspectBeanNames;
            if (aspectNames == null) {
                List<Advisor> advisors = new LinkedList<>();
                aspectNames = new LinkedList<>();
                // 获取当前BeanFactory中所有的bean
                String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                    this.beanFactory, Object.class, true, false);
                // 对获取到的所有bean进行循环遍历
                for (String beanName : beanNames) {
                    // 判断当前bean是否为子类定制的需要过滤的bean
                    if (!isEligibleBean(beanName)) {
                        continue;
                    }
                    
                    // 获取当前遍历的bean的Class类型
                    Class<?> beanType = this.beanFactory.getType(beanName);
                    if (beanType == null) {
                        continue;
                    }
                    
                    // 判断当前bean是否使用了@Aspect注解进行标注
                    if (this.advisorFactory.isAspect(beanType)) {
                        aspectNames.add(beanName);
                        
                        // 对于使用了@Aspect注解标注的bean,将其封装为一个AspectMetadata类型。
                        // 这里在封装的过程中会解析@Aspect注解上的参数指定的切面类型,如perthis
                        // 和pertarget等。这些被解析的注解都会被封装到其perClausePointcut属性中
                        AspectMetadata amd = new AspectMetadata(beanType, beanName);
                        // 判断@Aspect注解中标注的是否为singleton类型,默认的切面类都是singleton
                        // 类型
                        if (amd.getAjType().getPerClause().getKind() == 
                            PerClauseKind.SINGLETON) {
                            // 将BeanFactory和当前bean封装为MetadataAwareAspect-
                            // InstanceFactory对象,这里会再次将@Aspect注解中的参数都封装
                            // 为一个AspectMetadata,并且保存在该factory中
                            MetadataAwareAspectInstanceFactory factory =
                                new BeanFactoryAspectInstanceFactory(this.beanFactory, 
                                    beanName);
                            // 通过封装的bean获取其Advice,如@Before,@After等等,并且将这些
                            // Advice都解析并且封装为一个个的Advisor
                            List<Advisor> classAdvisors 
                                this.advisorFactory.getAdvisors(factory);
                            // 如果切面类是singleton类型,则将解析得到的Advisor进行缓存,
                            // 否则将当前的factory进行缓存,以便再次获取时可以通过factory直接获取
                            if (this.beanFactory.isSingleton(beanName)) {
                                this.advisorsCache.put(beanName, classAdvisors);
                            } else {
                                this.aspectFactoryCache.put(beanName, factory);
                            }
                            advisors.addAll(classAdvisors);
                        } else {
                            // 如果@Aspect注解标注的是perthis和pertarget类型,说明当前切面
                            // 不可能是单例的,因而这里判断其如果是单例的则抛出异常
                            if (this.beanFactory.isSingleton(beanName)) {
                                throw new IllegalArgumentException("Bean with name '" 
                                  + beanName + "' is a singleton, but aspect "
                                  + "instantiation model is not singleton");
                            }
                            
                            // 将当前BeanFactory和切面bean封装为一个多例类型的Factory
                            MetadataAwareAspectInstanceFactory factory =
                                new PrototypeAspectInstanceFactory(this.beanFactory, 
                                  beanName);
                            // 对当前bean和factory进行缓存
                            this.aspectFactoryCache.put(beanName, factory);
                            advisors.addAll(this.advisorFactory.getAdvisors(factory));
                        }
                    }
                }
                this.aspectBeanNames = aspectNames;
                return advisors;
            }
        }
    }

    if (aspectNames.isEmpty()) {
        return Collections.emptyList();
    }
    
    // 通过所有的aspectNames在缓存中获取切面对应的Advisor,这里如果是单例的,则直接从advisorsCache
    // 获取,如果是多例类型的,则通过MetadataAwareAspectInstanceFactory立即生成一个
    List<Advisor> advisors = new LinkedList<>();
    for (String aspectName : aspectNames) {
        List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
        // 如果是单例的Advisor bean,则直接添加到返回值列表中
        if (cachedAdvisors != null) {
            advisors.addAll(cachedAdvisors);
        } else {
            // 如果是多例的Advisor bean,则通过MetadataAwareAspectInstanceFactory生成
            MetadataAwareAspectInstanceFactory factory = 
                this.aspectFactoryCache.get(aspectName);
            advisors.addAll(this.advisorFactory.getAdvisors(factory));
        }
    }
    return advisors;
}

       对于通过@Aspect注解获取切面逻辑的方法,这里的逻辑也比较简单,Spring首先会过滤得到BeanFactory中所有标注有@Aspect的类,然后对该注解参数进行解析,判断其环绕的目标bean是单例的还是多例的。如果是单例的,则直接缓存到advisorsCache中;如果是多例的,则将生成Advisor的factory进行缓存,以便每次获取时都通过factory获取一个新的Advisor。上述方法中主要是对@Aspect注解进行了解析,我们前面讲过,Spring Aop的Advisor对应的是Advice,而每个Advice都是对应的一个@Before或者@After等标注方法的切面逻辑,这里对这些切面逻辑的解析过程就在上述的advisorFactory.getAdvisors(factory)方法调用中。这里我们看看该方法的实现:

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
    // 获取当前切面类的Class类型
    Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    // 获取当前切面bean的名称
    String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
    // 对当前切面bean进行校验,主要是判断其切点是否为perflow或者是percflowbelow,Spring暂时不支持
    // 这两种类型的切点
    validate(aspectClass);

    // 将当前aspectInstanceFactory进行封装,这里LazySingletonAspectInstanceFactoryDecorator
    // 使用装饰器模式,主要是对获取到的切面实例进行了缓存,保证每次获取到的都是同一个切面实例
    MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
        new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);

    List<Advisor> advisors = new LinkedList<>();
    // 这里getAdvisorMethods()会获取所有的没有使用@Pointcut注解标注的方法,然后对其进行遍历
    for (Method method : getAdvisorMethods(aspectClass)) {
        // 判断当前方法是否标注有@Before,@After或@Around等注解,如果标注了,则将其封装为一个Advisor
        Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 
           advisors.size(), aspectName);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }

    // 这里的isLazilyInstantiated()方法判断的是当前bean是否应该被延迟初始化,其主要是判断当前
    // 切面类是否为perthis,pertarget或pertypewithiin等声明的切面。因为这些类型所环绕的目标bean
    // 都是多例的,因而需要在运行时动态判断目标bean是否需要环绕当前的切面逻辑
    if (!advisors.isEmpty() && 
        lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        // 如果Advisor不为空,并且是需要延迟初始化的bean,则在第0位位置添加一个同步增强器,
        // 该同步增强器实际上就是一个BeforeAspect的Advisor
        Advisor instantiationAdvisor = new 
            SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
        advisors.add(0, instantiationAdvisor);
    }

    // 判断属性上是否包含有@DeclareParents注解标注的需要新添加的属性,如果有,则将其封装为一个Advisor
    for (Field field : aspectClass.getDeclaredFields()) {
        Advisor advisor = getDeclareParentsAdvisor(field);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }

    return advisors;
}

       在上述getAdvisors()方法中,Spring会遍历当前切面类所有的方法,包括父类和父接口的方法,找到其中没有使用@Pointcut注解标注的方法,然后对找到的方法进行遍历,将其封装为一个Advisor。这里我们继续看封装为Advisor的方法:

public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory 
        aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {

    // 校验当前切面类是否使用了perflow或者percflowbelow标识的切点,Spring暂不支持这两种切点
    validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());

    // 获取当前方法中@Before,@After或者@Around等标注的注解,并且获取该注解的值,将其
    // 封装为一个AspectJExpressionPointcut对象
    AspectJExpressionPointcut expressionPointcut = getPointcut(
       candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
    if (expressionPointcut == null) {
        return null;
    }

    // 将获取到的切点,切点方法等信息封装为一个Advisor对象,也就是说当前Advisor包含有所有
    // 当前切面进行环绕所需要的信息
    return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, 
        candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, 
        aspectName);
}

       到这里Spring才将@Before,@After或@Around标注的方法封装为了一个Advisor对象。需要说明的是,这里封装成的Advisor对象只是一个半成品。所谓的半成品指的是此时其并没有对切点表达式进行解析,其还只是使用一个字符串保存在AspectJExpressionPointcut对象中,只有在真正使用当前Advice逻辑进行目标bean的环绕的时候才会对其进行解析。

3. 小结

       本文主要讲解了Spring是如何获取所有的Advisor的,即首先获取BeanFactory中所有实现了Advisor接口的bean,然后获取BeanFactory中所有标注了@Aspect注解的bean,解析该bean中的所有的切面逻辑,并且封装为一个个Advisor,这两种方式得到的Advisor都有可能是最终会应用到目标bean上的切面逻辑。需要注意的是,这里获取到的Advisor并没有对切点表达式进行解析,实际的解析过程是在判断当前bean是否可以应用到目标bean时进行的。这也是一个小小的优化,因为解析切点表达式的过程是一个比较复杂的过程。

© 著作权归作者所有

共有 人打赏支持
爱宝贝丶
粉丝 137
博文 77
码字总数 258388
作品 0
武汉
程序员
Spring事务用法示例与实现原理

关于事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性,一致性,隔离性和持久性。对于Spring事务,其最终还是在数据库层面实现的,而Spring只是以一种比较...

爱宝贝丶
08/28
0
0
Spring AOP 源码分析 - 筛选合适的通知器

1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析。本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出合适的通知器(Advisor...

java高级架构牛人
06/21
0
0
Spring Aop原理之Advisor过滤

在上文(Spring Aop之Advisor解析)中我们讲到,Spring Aop对目标bean的代理主要分为三个步骤:获取所有的Advisor,过滤当前bean可应用的Advisor和使用Advisor为当前bean生成代理对象,并且上文...

爱宝贝丶
08/18
0
0
Spring系列之AOP基本主要类概述

在这篇文章中我将对自己了解的AOP中的基本主要类做一个概述,可能不包括一些AOP高级用法的类以及是自己还不了解的类。会不定期的进行补充和修改。 SpringAOP基础解析类 类名 作用概述 AopNam...

Mr_zebra
07/20
0
0
spring4.0源码分析━━━(AOP实现)

AOP的概念 AOP为Aspect Oriented Programming的缩写,意为:面向切面编程(也叫面向方面)。这就让一些问题很简单化了,例如:开始我们实现了一些逻辑并上线了,现在客户又来了一个新的需求。...

walala_Lee
2014/11/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

flume -- fileChannel简要分析其过程

flume之event写入FileChannel doPut(event)-->获取共享锁后[log.lockShared();]-->FlumeEventPointer ptr = log.put(transactionID, event); 此处的log.put即将transactionID及event进行后续......

-九天-
23分钟前
2
0
Linux与FreeBSD有什么区别?

基础 许多人所称的“Linux”实际上不是 Linux。Linux 从技术上说只是 Linux 内核,典型的 Linux 发行版则包括了 Linux 内核和许多软件。这是为什么 Linux 有时被称为 GNU/Linux。事实上,许多...

linux-tao
31分钟前
2
0
jQuery学习笔记180924

jQuery - AJAX 简介 什么是 AJAX? AJAX = 异步 JavaScript 和 XML(Asynchronous JavaScript and XML)。 简短地说,在不重载整个网页的情况下,AJAX 通过后台加载数据,并在网页上进行显示...

颖伙虫
44分钟前
1
0
springboot整合vue小试牛刀

序 本文主要研究一下如何在springboot工程整合vue maven <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-we......

go4it
46分钟前
2
0
使用python的profiler工具

主要用来检测python coding的执行时间 fly profiler

steel7c4
50分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部