1. 程式人生 > >Spring事務管理之AOP方法

Spring事務管理之AOP方法


使用AOP完成Spring事務管理

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true" propagation="REQUIRED" />
            <!-- rollback-for只對檢查型異常適用 這裡一般為自定義的Exception 繼承Exception父類 因為RuntimeException和Error,Spring預設進行回顧,我們在原始碼分析中可以看到這一點 -->
<tx:method name="*" propagation="REQUIRED" rollback-for="Exception" /> </tx:attributes> </tx:advice> <!-- 適用切面來新增適用處理的功能 --> <aop:config> <aop:pointcut expression="execution(* wangcc.service.impl.*.*(..))" id="serviceMethod"
/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" /> </aop:config>

當我們使用AOP完成Spring事務管理的時候,我們在處理業務邏輯的時候只需要關心業務邏輯的處理,而Spring事務會通過動態代理的方式來增強需要使用事務的方法來完成Spring事務。

AOP完成Spring事務管理原始碼分析

tx:advice節點的解析

我們觀看上面的XML檔案配置,很明顯tx:advice這個節點生成的Bean是來完成Spring事務管理功能的,在AOP中充當增強方法處理的作用。而aop:pointcut節點就很明顯是指定到底在哪些類的那些方法中執行這個方法增加,即Spring事務管理。

對於AOP,我們已經分析過其原始碼了,不過當時分析的是aop:pointcut與aop:aspect的組合。而這裡aop:aspect換成了aop:advisor。我們知道在分析aop原始碼的時候aop:aspect節點下的aop:before,aop:after等節點都會傳化為AspectJPointcutAdvisor型別的Bean,並且其中包含一個AbstractAspectJAdvice的子類型別的Bean,而且這些子類除了AspectJMethodBeforeAdvice(之後進行了一層封裝後實現介面),都直接實現了MethodInterceptor介面,而最後我們需要的就是實現了MethodInterceptor介面的類。

那麼我們在分析tx:advice節點的時候,顯然可以想象得到他的最終產物也對應著一個Interceptor。

事實也是如此,我們找到解析該節點的類TxAdviceBeanDefinitionParser。找到getBeanClass方法,你會發現BeanClass對應著是TransactionInterceptor,而他也是實現了MethodInterceptor介面的一個類。

@Override
    protected Class<?> getBeanClass(Element element) {
        return TransactionInterceptor.class;
    }

我們來看下TxAdviceBeanDefinitionParser的宣告

class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
public abstract class AbstractSingleBeanDefinitionParser extends AbstractBeanDefinitionParser {
public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser {

我們可以看到直接實現BeanDefinitionParser介面的是他祖先類AbstractBeanDefinitionParser類。

  • parse

    @Override
    public final BeanDefinition parse(Element element, ParserContext parserContext) {
        AbstractBeanDefinition definition = parseInternal(element, parserContext);
    //...省略了一坨程式碼,主要是將得到的Bean定義註冊到Spring容器中,並做一些處理。不是關注的重點
        return definition;
    }

    在parse方法中,重點是parseInternal方法的實現,也就是如何得到Bean定義。而這個方法在AbstractBeanDefinitionParser中是個抽象方法。具體的實現在AbstractSingleBeanDefinitionParser中。

  • parseInternal

    @Override
    protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        //看是否有parent
        String parentName = getParentName(element);
        if (parentName != null) {
            builder.getRawBeanDefinition().setParentName(parentName);
        }
        //getBeanClass留給子類實現
        //得到該Bean的Class,這裡就是之前說的TransactionInterceptor
        Class<?> beanClass = getBeanClass(element);
        //將Class註冊到Bean定義中
        if (beanClass != null) {
            builder.getRawBeanDefinition().setBeanClass(beanClass);
        }
        else {
            //getBeanClassName留給子類實現,但是這裡TxAdviceBeanDefinitionParser並沒實現
            String beanClassName = getBeanClassName(element);
            if (beanClassName != null) {
                builder.getRawBeanDefinition().setBeanClassName(beanClassName);
            }
        }
        builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
        if (parserContext.isNested()) {
            // Inner bean definition must receive same scope as containing bean.
            builder.setScope(parserContext.getContainingBeanDefinition().getScope());
        }
        if (parserContext.isDefaultLazyInit()) {
            // Default-lazy-init applies to custom bean definitions as well.
            builder.setLazyInit(true);
        }
        //對Bean定義進行一系列的引數設定之後,終於來到解析節點,這個方法也是給子類實現的。
        doParse(element, parserContext, builder);
        return builder.getBeanDefinition();
    }

    在對Bean定義進行了一系列引數的設定之後,呼叫doParse對節點進行解析。

  • doParse

@Override
    protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
      //注入transcationManager屬性,如果在配置中沒有配置transaction-manager屬性,那麼這時Spring配置檔案中,事務管理器的Bean的Id必須是transcationManager。
        builder.addPropertyReference("transactionManager", TxNamespaceHandler.getTransactionManagerName(element));
    //得到tx:attributes節點
        List<Element> txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT);
      //該節點直接出現一次,如果出現多次,報錯
        if (txAttributes.size() > 1) {
            parserContext.getReaderContext().error(
                    "Element <attributes> is allowed at most once inside element <advice>", element);
        }
      //如果出現一次,那麼就可以開始解析這個節點裡的內容了
        else if (txAttributes.size() == 1) {
            // Using attributes source.
            Element attributeSourceElement = txAttributes.get(0);
            RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);
          //用得到的attributeSourceDefinition注入transactionAttributeSource屬性
            builder.addPropertyValue("transactionAttributeSource", attributeSourceDefinition);
        }
      //如果沒有顯式的配置這個節點,還是需要注入transactionAttributeSource屬性,這裡給出一個預設的AnnotationTransactionAttributeSource
        else {
            // Assume annotations source.
            builder.addPropertyValue("transactionAttributeSource",
                    new RootBeanDefinition("org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"));
        }
    }

我們可以看到doParse方法主要就是為TransactionInterceptor這個類註冊了兩個屬性,transactionAttributeSource和transactionManager。

我們再看看當有顯式配置tx:attributes節點時對transactionAttributeSource屬性的配置。

    RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);
  • parseAttributeSource
    private RootBeanDefinition parseAttributeSource(Element attrEle, ParserContext parserContext) {
      //得到所有的tx:method節點
        List<Element> methods = DomUtils.getChildElementsByTagName(attrEle, METHOD_ELEMENT);
      //建立一個Map,用來存放TypedStringValue(name屬性值的包裝類)為key,RuleBasedTransactionAttribute為Value的鍵值對
      //即每一個method name都對應著相應TransactionAttribute
        ManagedMap<TypedStringValue, RuleBasedTransactionAttribute> transactionAttributeMap =
            new ManagedMap<TypedStringValue, RuleBasedTransactionAttribute>(methods.size());
        transactionAttributeMap.setSource(parserContext.extractSource(attrEle));

        for (Element methodEle : methods) {
          //得到tx:method中name屬性的屬性值
            String name = methodEle.getAttribute(METHOD_NAME_ATTRIBUTE);
          //將屬性值封裝成TypedStringValue型別
            TypedStringValue nameHolder = new TypedStringValue(name);
            nameHolder.setSource(parserContext.extractSource(methodEle));

            RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute();
            String propagation = methodEle.getAttribute(PROPAGATION_ATTRIBUTE);
            String isolation = methodEle.getAttribute(ISOLATION_ATTRIBUTE);
            String timeout = methodEle.getAttribute(TIMEOUT_ATTRIBUTE);
            String readOnly = methodEle.getAttribute(READ_ONLY_ATTRIBUTE);
            if (StringUtils.hasText(propagation)) {
                attribute.setPropagationBehaviorName(RuleBasedTransactionAttribute.PREFIX_PROPAGATION + propagation);
            }
            if (StringUtils.hasText(isolation)) {
                attribute.setIsolationLevelName(RuleBasedTransactionAttribute.PREFIX_ISOLATION + isolation);
            }
            if (StringUtils.hasText(timeout)) {
                try {
                    attribute.setTimeout(Integer.parseInt(timeout));
                }
                catch (NumberFormatException ex) {
                    parserContext.getReaderContext().error("Timeout must be an integer value: [" + timeout + "]", methodEle);
                }
            }
            if (StringUtils.hasText(readOnly)) {
                attribute.setReadOnly(Boolean.valueOf(methodEle.getAttribute(READ_ONLY_ATTRIBUTE)));
            }

            List<RollbackRuleAttribute> rollbackRules = new LinkedList<RollbackRuleAttribute>();
            if (methodEle.hasAttribute(ROLLBACK_FOR_ATTRIBUTE)) {
                String rollbackForValue = methodEle.getAttribute(ROLLBACK_FOR_ATTRIBUTE);
                addRollbackRuleAttributesTo(rollbackRules,rollbackForValue);
            }
            if (methodEle.hasAttribute(NO_ROLLBACK_FOR_ATTRIBUTE)) {
                String noRollbackForValue = methodEle.getAttribute(NO_ROLLBACK_FOR_ATTRIBUTE);
                addNoRollbackRuleAttributesTo(rollbackRules,noRollbackForValue);
            }
            attribute.setRollbackRules(rollbackRules);

            transactionAttributeMap.put(nameHolder, attribute);
        }

        RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchTransactionAttributeSource.class);
        attributeSourceDefinition.setSource(parserContext.extractSource(attrEle));
        attributeSourceDefinition.getPropertyValues().add("nameMap", transactionAttributeMap);
        return attributeSourceDefinition;
    }

這個方法很長,但是並不難理解。

就是如果在tx:method節點中有對Methodname顯式的指定事務傳播屬性,事務級別,只讀,超時等屬性,就把他加入到RuleBasedTransactionAttribute中。沒有顯式指定的話就使用預設的。然後將所有的method節點的資訊都存入到NameMatchTransactionAttributeSource中。

那麼到這裡,我們就分析完了tx:advice節點了。

他主要就是建立了型別為TransactionInterceptor的Bean,並且注入了這個Bean的兩個屬性,transactionAttributeSource和transactionManager。而當有tx:method節點的時候,transactionAttributeSource屬性對應的型別是NameMatchTransactionAttributeSource,如果沒有,則對應AnnotationTransactionAttributeSource。

aop:config節點的解析

關於aop:config節點的解析,我們在講解Spring AOP實現的時候已經說過了,但是當時講的是aop:aspect和aop:pointcut的組合,現在我們要講解aop:pointcut和aop:advisor的組合。

我們直接看到ConfigBeanDefinitionParser中解析節點的方法。

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        CompositeComponentDefinition compositeDef =
                new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
        parserContext.pushContainingComponent(compositeDef);

        configureAutoProxyCreator(parserContext, element);

        List<Element> childElts = DomUtils.getChildElements(element);
        for (Element elt: childElts) {
            String localName = parserContext.getDelegate().getLocalName(elt);
            if (POINTCUT.equals(localName)) {
                parsePointcut(elt, parserContext);
            }
            else if (ADVISOR.equals(localName)) {
                parseAdvisor(elt, parserContext);
            }
            else if (ASPECT.equals(localName)) {
                parseAspect(elt, parserContext);
            }
        }

        parserContext.popAndRegisterContainingComponent();
        return null;
    }
  • parseAdvisor

    private void parseAdvisor(Element advisorElement, ParserContext parserContext) {
        AbstractBeanDefinition advisorDef = createAdvisorBeanDefinition(advisorElement, parserContext);
        String id = advisorElement.getAttribute(ID);
    
        try {
            this.parseState.push(new AdvisorEntry(id));
            String advisorBeanName = id;
            if (StringUtils.hasText(advisorBeanName)) {
                parserContext.getRegistry().registerBeanDefinition(advisorBeanName, advisorDef);
            }
            else {
                advisorBeanName = parserContext.getReaderContext().registerWithGeneratedName(advisorDef);
            }
    
            Object pointcut = parsePointcutProperty(advisorElement, parserContext);
            if (pointcut instanceof BeanDefinition) {
                advisorDef.getPropertyValues().add(POINTCUT, pointcut);
                parserContext.registerComponent(
                        new AdvisorComponentDefinition(advisorBeanName, advisorDef, (BeanDefinition) pointcut));
            }
            else if (pointcut instanceof String) {
                advisorDef.getPropertyValues().add(POINTCUT, new RuntimeBeanReference((String) pointcut));
                parserContext.registerComponent(
                        new AdvisorComponentDefinition(advisorBeanName, advisorDef));
            }
        }
        finally {
            this.parseState.pop();
        }
    }

    方法不難理解,就是將pointcut和advice-ref,也就是之前我們分析的那個節點對應的Bean,TranscationInterceptor關聯起來得到DefaultBeanFactoryPointcutAdvisor,然後注入到Spring容器中。

到這裡,我們的切面就完成了,然而最重要的是怎麼呼叫我們這個切面。

運用AOP實現的事務管理的呼叫過程

具體怎麼註冊攔截器,怎麼建立代理類的過程請看AOP原始碼解析部分。

    @Override
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        // Work out the target class: may be {@code null}.
        // The TransactionAttributeSource should be passed the target class
        // as well as the method, which may be from an interface.
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

        // Adapt to TransactionAspectSupport's invokeWithinTransaction...
        return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
            @Override
            public Object proceedWithInvocation() throws Throwable {
                return invocation.proceed();
            }
        });
    }

invokeWithinTransaction

    protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
            throws Throwable {

        // If the transaction attribute is null, the method is non-transactional.
        final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
        final PlatformTransactionManager tm = determineTransactionManager(txAttr);
        final String joinpointIdentification = methodIdentification(method, targetClass);

        if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
            // Standard transaction demarcation with getTransaction and commit/rollback calls.
        //開啟事務,設定事務隔離級別,傳播屬性等   
          TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
            Object retVal = null;
            try {
                // This is an around advice: Invoke the next interceptor in the chain.
                // This will normally result in a target object being invoked.
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                // target invocation exception
              //如果丟擲異常,處理他
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                cleanupTransactionInfo(txInfo);
            }
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }

    //省略了CallbackPreferringPlatformTransactionManager這一塊,這一塊基本就是程式設計式事務實現事務管理的邏輯
    }