1. 程式人生 > >Spring學習筆記 —— AOP(面向切面程式設計) 之AspectJ

Spring學習筆記 —— AOP(面向切面程式設計) 之AspectJ

引言

在上一篇文章, Spring學習筆記 —— AOP(面向切面程式設計) 之Spring內建AOP中,我們簡單介紹了AOP的概念,也分析了Spring中使用proxyFactroyBean生成代理物件的實現原理。

當我們只需要對一個物件進行代理的時候,使用proxyFactoryBean是方便的,但是,當我們需要代理多個物件的時候,如果每個物件都需要先宣告一個自身的bean,再宣告一個proxy bean,無疑會使配置檔案變的複雜,也會讓人感到疑惑而不知道到底應該使用哪一個bean。

因此,今天介紹一個更為簡潔的AOP例項——基於AspectJ的AOP。AspectJ的AOP方便在於,我們只需要在配置檔案上進行一次定義,再宣告一個切面類就夠了。但這並不代表AspectJ用了什麼特殊的方法實現AOP,只不過是Spring AOP框架自動化地實現了相關的類。

這篇文章會分成兩部分,第一部分會給大家介紹如何使用AspectJ,第二部分分析其實現原理。

AspectJ AOP例項

直接上例子吧,首先還是一個TargetObjectSample.java

public class TargetObjectSample {
    public void getMessage() {
        System.out.println("getMsg!");
    }

    public void getPointCutMessage() {
        System.out.println("get point cut Msg!"
); } }

依舊聲明瞭兩個方法,區分AOP與非AOP。
AspectJ AOP與Spring AOP的區別是,我們不再需要顯示地宣告Advisor, PointCut這些類了,只需要宣告一個Aspect類,這個類中包含有代理方法即可。
AspectSample.java

public class AspectSample {
    public void beforeExecute() {
        System.out.println("before execute");
    }
}   

而相對應的,在配置檔案裡面,也變得更為簡單清晰。Spring中更多的是為一個類宣告對應的代理物件,而這裡則是針對切面做宣告。我們宣告一個特定的切面,並且確定這個切面將會應用到哪些連線點上。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">

   <aop:config>
      <aop:aspect id="sample" ref="aspectSample">
         <!--直接使用表示式進行切點描述,更加方便且更加清晰 -->
         <aop:pointcut id="pointCutSample" expression="execution(public void com.study.AspectJ.TargetObjectSample.getPointCutMessage(..))"/>
         <aop:before pointcut-ref="pointCutSample" method="beforeExecute"/>
      </aop:aspect>
   </aop:config>

   <bean id="aspectSample" class="com.study.AspectJ.AspectSample" scope="singleton">
   </bean>

   <bean id="targetObjectSample" class="com.study.AspectJ.TargetObjectSample" scope="singleton">
   </bean>

</beans>

因為這個時候我們需要使用AspectJ的庫,要新增對應的maven依賴。

<dependencies> 

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.2.6.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.9</version>
    </dependency>

  </dependencies>

最後,就是main.java

public class Main {
    public static void main(String args[]) {
        ApplicationContext applicationConText = new ClassPathXmlApplicationContext("AspectBeans.xml");
        //在這裡也不需要根據特定id,而是可以直接根據class獲取bean例項(當然,前提是這個class對應的bean有且只有一個。
        TargetObjectSample obj = applicationConText.getBean(TargetObjectSample.class);
        obj.getMessage();
        //getMsg!
        obj.getPointCutMessage();
        //before method execution!
        //get point cut Msg!
    }
}

AspectJ AOP實現分析

在Spring裡面,AspectJ的AOP實現可以分成以下幾個部分
- AOP名字空間的註冊
- 使用AspectJ AOP生成Bean的流程解析

AOP名字空間解析

首先我們引起我們注意的應該是切面的宣告方式:

<aop:config>
      <aop:aspect id="sample" ref="aspectSample">
         <!--直接使用表示式進行切點描述,更加方便且更加清晰 -->
         <aop:pointcut id="pointCutSample" expression="execution(public void com.study.AspectJ.TargetObjectSample.getPointCutMessage(..))"/>
         <aop:before pointcut-ref="pointCutSample" method="beforeExecute"/>
      </aop:aspect>
   </aop:config>

這裡看到的標籤並不是一個bean的標籤,而是一個以aop作為名字空間的標籤,因此在parse的時候,也必須要有對應的handler。而關於handler,spring中將所有副檔名稱空間的解析器都放在了META-INF/spring.handlers中。

而我們,也能夠spring-aop-4.2.6.RELEASE.jar中對應的目錄檔案看到

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

這就代表著,AOP名字空間下的相關定義(即所有定義為aop:xx形式的標籤),會由AopNamespaceHandler來完成。具體解析的流程則變成了如下圖所示

副檔名稱空間Bean解析過程

而我們看到具體程式碼,AopNamespaceHandler.init

public void init() {
        //註冊'config'標籤的解析器,包含了aspect,pointcut,before等我們宣告切面使用的標籤
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        //註冊'spectj-autoproxy'標籤的解析器
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        //註冊'scoped-proxy`的解析器
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
        //註冊'spring-configured'的解析器
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }

而對於我們宣告的AOP標籤,其解析的過程如下圖所示:
Parse AOP Def

由上圖可以看到,對於AOP空間的XML元素,我們首先會註冊AspectJAwareAdvisorAutoProxyCreator這個Bean的definition,然後再根據定義的aop:aspcet元素,生成advicepointCutAdvisor的Bean definition,並進行註冊。

我們來具體分析一下,幾個parse方法。
首先是解析aop:aspect標籤的parseAspect

private void parseAspect(Element aspectElement, ParserContext parserContext) {
        String aspectId = aspectElement.getAttribute(ID);
        String aspectName = aspectElement.getAttribute(REF);

        try {
            this.parseState.push(new AspectEntry(aspectId, aspectName));
            List<BeanDefinition> beanDefinitions = new ArrayList<BeanDefinition>();
            List<BeanReference> beanReferences = new ArrayList<BeanReference>();

            List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
            for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
                Element declareParentsElement = declareParents.get(i);
                beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
            }

            // 遍歷所有子節點
            NodeList nodeList = aspectElement.getChildNodes();
            boolean adviceFoundAlready = false;
            for (int i = 0; i < nodeList.getLength(); i++) {
                Node node = nodeList.item(i);
                //找出所有宣告為aop:before/aop:after等的具體Advice節點
                if (isAdviceNode(node, parserContext)) {
                    if (!adviceFoundAlready) {
                        adviceFoundAlready = true;
                        if (!StringUtils.hasText(aspectName)) {
                            parserContext.getReaderContext().error(
                                    "<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",
                                    aspectElement, this.parseState.snapshot());
                            return;
                        }
                        //儲存bean的引用,後續用於建立ComponentDefinition
                        beanReferences.add(new RuntimeBeanReference(aspectName));
                    }
                    AbstractBeanDefinition advisorDefinition = parseAdvice(
                            aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
                    beanDefinitions.add(advisorDefinition);
                }
            }

            AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
                    aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
            parserContext.pushContainingComponent(aspectComponentDefinition);
            //解析所有的pointCut
            List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
            for (Element pointcutElement : pointcuts) {
                parsePointcut(pointcutElement, parserContext);
            }

            parserContext.popAndRegisterContainingComponent();
        }
        finally {
            this.parseState.pop();
        }
    }

然後是parseAdvice

private AbstractBeanDefinition parseAdvice(
            String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext,
            List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {

        try {
            this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));

            //這裡首先針對Advice中的方法,建立一個Bean的定義(因為是在parse,所以不生成具體的bean)。最後要根據這個MethodLocatingFacatoryBean來獲取真正的代理執行方法。
            RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
            methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
            methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
            methodDefinition.setSynthetic(true);

            // 同時也要儲存對Aspcet Bean的引用,因為最後的方法來自於Aspcet Bean物件
            RootBeanDefinition aspectFactoryDef =
                    new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
            aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
            aspectFactoryDef.setSynthetic(true);

            // 有了method和aspect bean,就能夠建立依賴於這兩個bean的Advice了。但這裡的Advice特別在於,Advice中包含了pointCut的引用。具體可以看到createAdviceDefinition裡面的parsePointcutProperty
            AbstractBeanDefinition adviceDef = createAdviceDefinition(
                    adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
                    beanDefinitions, beanReferences);

            // 有了Advice,Advice裡面又包含有poinCut,我們就能夠建立Advisor了
            RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
            advisorDefinition.setSource(parserContext.extractSource(adviceElement));
            advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
            if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
                advisorDefinition.getPropertyValues().add(
                        ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
            }

            //註冊Advisor
    parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);

            return advisorDefinition;
        }
        finally {
            this.parseState.pop();
        }
    }

最後是parsePointCut,因為在parseAdvice中只是儲存了poincut的引用,因此我們還需要真正地把pointCut的定義從XML定義轉化成bean的定義。

private AbstractBeanDefinition parsePointcut(Element pointcutElement, ParserContext parserContext) {
        String id = pointcutElement.getAttribute(ID);
        String expression = pointcutElement.getAttribute(EXPRESSION);

        AbstractBeanDefinition pointcutDefinition = null;

        try {
            this.parseState.push(new PointcutEntry(id));
            //解析出來的Bean定義預設scope是prototype的。
            pointcutDefinition = createPointcutDefinition(expression);
            pointcutDefinition.setSource(parserContext.extractSource(pointcutElement));

            String pointcutBeanName = id;
            if (StringUtils.hasText(pointcutBeanName)) {
                parserContext.getRegistry().registerBeanDefinition(pointcutBeanName, pointcutDefinition);
            }
            else {
                pointcutBeanName = parserContext.getReaderContext().registerWithGeneratedName(pointcutDefinition);
            }

            parserContext.registerComponent(
                    new PointcutComponentDefinition(pointcutBeanName, pointcutDefinition, expression));
        }
        finally {
            this.parseState.pop();
        }

        return pointcutDefinition;
    }

所有這些Bean的定義都準備好了之後,就可以進行下一步,AOP代理物件的建立了。

AOP代理物件的建立

談到AOP代理物件的建立,我們就繞不開AspectJAwareAdvisorAutoProxyCreator這個類。 這個類實現了BeanPostProcessor這個介面。也就是說,這個類裡包含了Bean建立前/後的相關處理邏輯。

而這個類,也是在postProcessAfterInstantiation這個方法中,完成代理的。

@Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (!this.earlyProxyReferences.contains(cacheKey)) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }

再進一步往下看AbstractAutoProxyCreator.wrapIfNecessary

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
            return 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;
        }

        // 為這個類找到對應的Advisor,原理是先通過beanFactory找到所有實現了Advisor.class介面的類,然後再根據Advisor中的PointCut來進行classFilter和MethodMatcher
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            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;
    }

最後,就能夠獲得生成的代理物件了。

小結

這篇文章分析了Spring中AspectJ的AOP實現。相比於使用proxyFactory進行實現,AspectJ AOP的配置要更為簡潔,而且需要宣告的類也更少。但在實際過程中,生成的類物件並沒有變少。在解析aop:aspect標籤的時候,我們仍然生成了pointCut, pointCutAdvisorAdvice這三個類的對應Bean定義。

AspectJ AOP做的改變就是,在Bean的建立後置處理函式中,判斷這個Bean是否屬於被代理的,如果是,則使用pointCutAdvisor以及Advice生成對應的代理物件。如果不是,則直接返回。

參考文章