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這一塊,這一塊基本就是程式設計式事務實現事務管理的邏輯
}