承接Spring源碼情操陶冶-自定義節點的解析。本節關於事務進行簡單的解析
spring配置文件樣例
簡單的事務配置,對save/delete
開頭的方法加事務,get/find
開頭的設置為不加事務只讀模式
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
TxAdviceBeanDefinitionParser解析器
tx:advice
節點對應的解析器為TxAdviceBeanDefinitionParser
,下面針對該解析器作下詳細的解讀
實例化對象
直接看復寫的getBeanClass()
方法
@Override
protected Class<?> getBeanClass(Element element) {
return TransactionInterceptor.class;
}
即TxAdviceBeanDefinitionParser
解析器最終解析tx:advice
節點為TransactionInterceptor
對象
通用的屬性集合
private static final String METHOD_ELEMENT = "method";
private static final String METHOD_NAME_ATTRIBUTE = "name";
private static final String ATTRIBUTES_ELEMENT = "attributes";
private static final String TIMEOUT_ATTRIBUTE = "timeout";
private static final String READ_ONLY_ATTRIBUTE = "read-only";
private static final String PROPAGATION_ATTRIBUTE = "propagation";
private static final String ISOLATION_ATTRIBUTE = "isolation";
private static final String ROLLBACK_FOR_ATTRIBUTE = "rollback-for";
private static final String NO_ROLLBACK_FOR_ATTRIBUTE = "no-rollback-for";
針對上述的屬性,我們可以看下其中的具體解析
doParse()-解析tx:advice節點
源碼端上
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
// 解析transaction-manager屬性對應的bean ref名,默認名為transactionManager
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);
// 解析tx:attribute集合
RootBeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);
builder.addPropertyValue("transactionAttributeSource", attributeSourceDefinition);
}
else {
// 注冊解析器用於解析注解@Transactional
builder.addPropertyValue("transactionAttributeSource",
new RootBeanDefinition("org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"));
}
}
對於@Transactional
方式的解析我們不在此處展開,我們先看下通用的parseAttributeSource()
方法解析tx:attribute
集合,其會被包裝為NameMatchTransactionAttributeSource.class
對象。源碼如下
private RootBeanDefinition parseAttributeSource(Element attrEle, ParserContext parserContext) {
// 解析tx:method節點
List<Element> methods = DomUtils.getChildElementsByTagName(attrEle, METHOD_ELEMENT);
ManagedMap<TypedStringValue, RuleBasedTransactionAttribute> transactionAttributeMap =
new ManagedMap<TypedStringValue, RuleBasedTransactionAttribute>(methods.size());
transactionAttributeMap.setSource(parserContext.extractSource(attrEle));
//
for (Element methodEle : methods) {
// 解析name屬性,其可符合ant-style模式.包裝成TypedStringValue對象
String name = methodEle.getAttribute(METHOD_NAME_ATTRIBUTE);
TypedStringValue nameHolder = new TypedStringValue(name);
nameHolder.setSource(parserContext.extractSource(methodEle));
// 解析propagation、isolation、timeout、read-only屬性
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)));
}
// 解析rollback-for、no-rollback-for屬性
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);
}
// 最后包裝成NameMatchTransactionAttributeSource對象,存放上述的配置
RootBeanDefinition attributeSourceDefinition = new RootBeanDefinition(NameMatchTransactionAttributeSource.class);
attributeSourceDefinition.setSource(parserContext.extractSource(attrEle));
attributeSourceDefinition.getPropertyValues().add("nameMap", transactionAttributeMap);
return attributeSourceDefinition;
}
代碼很簡單,都是解析屬性的,不過還是對上述的一些配置作下白話的總結
-
name 支持ant-style語法,即匹配對應的方法,比如
save*
,匹配saveUser()/save()
等方法 -
propagation 事務傳播方式,對應spring的TransactionDefinition接口類常量
- required 對應
PROPAGATION_REQUIRED
,對當前的方法判斷如果不存在事務,則創建事務。默認配置 - required_new 對應
PROPAGATION_REQUIRED_NEW
,對當前方法判斷如果存在事務,則創建新事務,待方法執行完畢后恢復事務;反之創建新事務,讓方法運行在新事務環境下。即當前方法將運行在獨立的新事務下 - supports 對應
PROPAGATION_SUPPORTS
,對當前方法判斷如果存在事務,則加入該事務;反之則讓方法處於非事務狀態執行 - not_spported 對應
PROPAGATION_NOT_SUPPORTED
,對當前方法判斷如果存在事務,則掛起該事務,等方法執行完畢后,再恢復事務。即當前方法不需要事務支持 - mandatory 對應
PROPAGATION_MANDATORY
,對當前方法判斷如果存在事務,則加入該事務;反之不能新建事務,且拋出異常。即必須處於事務下運行 - never 對應
PROPAGATION_NEVER
,對當前方法判斷如果存在事務,則拋異常;反之正常運行。即必須在非事務下運行 - nested 對應
PROPAGATION_NESTED
,可嵌入式的事務。
- required 對應
-
isolation 事務隔離級別,對應spring的TransactionDefinition接口類常量
- default 對應
ISOLATION_DEFAULT
,不作隔離要求,可能會導致dirty read/unrepeatable read/phantom read
- read_uncommitted 對應JDBC Connection的
TRANSACTION_READ_UNCOMMITTED
,可能會導致dirty read/unrepeatable read/phantom read
- read_committed 對應JDBC Connection的
TRANSACTION_READ_COMMITTED
,可能會導致unrepeatable read/phantom read
- reaptable_read 對應JDBC Connection的
TRANSACTION_REPEATABLE_READ
,可能會導致phantom read
- serializable 對應JDBC Connection的
TRANSACTION_SERIALIZABLE
,最安全但最耗性能
其中關於臟讀、不可重復讀、幻讀
的概念見引文。另附言博主對不可重復讀、幻讀的理解兩者均是在同一事務中會出現的情況,執行的條件均一樣。但不可重復讀關心返回的數據是否一致,而幻讀關心返回的數據條數是否一致
- default 對應
-
timeout 超時參數,單位為s。其只應用於事務傳播方式為
Required/Required_new
,默認為-1 -
read-only 是否配置事務只讀,默認為false
-
rollback-for 異常回滾策略配置,即出現何種異常進行回滾,可配置多個異常,支持
,
分隔。注意此處的配置的異常名也符合ant-style模式 -
no-rollback-for 異常不回滾策略配置,即出現何種異常不進行回滾,可配置多個異常,支持
,
分隔。注意此處的配置的異常名也符合ant-style模式
事務攔截邏輯-TransactionInterceptor
UML一覽
通過上圖我們發現其也是Advice
接口的實現類,說明此類可應用於aop:advisor
配置
invoke()-MethodInterceptor公共調用方法
所有的Advisor封裝類都會含有MethodInterceptor的實現類的引用,我們可以看下事務處理的切面處理方式
@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();
}
});
}
其會調用invokeWithinTransation()方法來解決此類問題,通過表面文字我們可以猜出其會判斷對相應的方法是否添加事務來執行,由於代碼過長,博主就截取重要的片段來分析
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;
}
從以上的代碼可知,我們可以得到以下結論
- 根據method對應的事務配置,創建
TransactionInfo
對象。即判斷是否對相應的方法加上事務- 再執行相應的方法的業務
- 如果執行業務過程中,出現異常則根據異常匹配規則進行相應的回滾策略
- 無第三點的條件則會保存當前的事務狀態
- 最后提交事務,使增刪改查操作生效,保持一致性、原子性
小結
tx:advice配置多與spring aop結合使用,通過切面的解耦使其可以在方法每次執行的時候根據配置是否添加事務,是個很好的代碼設計。