Spring框架淺析 -- 資料庫事務處理
概述
在Spring框架淺析 -- 概述中,我們介紹過,Spring對於資料庫操作及事務處理的支援,是Spring功能中較為重要的一環。那麼資料庫事務是什麼?為什麼要Spring需要對資料庫操作及事務處理進行支援?Spring都提供了哪些方式對資料庫事務處理進行支援?這些方式中又有哪種比較適合當前工程開發的實踐?在本文中,我們將對這些問題一一展開闡述。
什麼是資料庫事務
相信這個問題,具備J2EE開發經驗的同學都不陌生。詳情可見:https://zh.wikipedia.org/zh-hans/%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BA%8B%E5%8A%A1
為什麼Spring需要對資料庫事務處理進行支援
對於J2EE領域的開發,通常都需要引入關係型資料庫用來儲存關係型資料。作為框架,Spring當然要提供通用的資料庫操作、資料庫事務處理的功能,從而減少業務方重複造輪子,重複封裝這些功能,向上遮蔽底層操作不同資料庫、管理資料庫事務的細節。試想,如果Spring不提供這些功能,每次開發新業務,都需要引入一大堆重複的獲取資料庫連線,開啟資料庫事務,關閉資料庫事務,回滾資料庫事務的程式碼,將是一件十分不優雅的事情。
Spring提供了哪些方式支援資料庫事務
在本文中,我們暫時不考慮分散式事務。分散式事務相關問題,我們會在後續的文章中加以敘述。
對於非分散式事務,Spring中最為常用的事務管理器為org.springframework.jdbc.datasource.DataSourceTransactionManager。Spring基於DataSourceTransactionManager提供了兩種方式來支援資料庫事務處理,程式設計式和宣告式。
程式設計式即為,在業務程式碼中,通過程式設計的方式,使用DataSourceTransactionManager的setDataSource(設定資料來源,關係型資料庫例項),doBegin(開始事務),doCommit(提交事務),doRollback(回滾事務),完成一次資料庫操作。這種方式的缺點是明顯的,就是對於通用的資料庫操作(操作行為大致相同,只是資料不同,可抽象成為統一的行為),程式碼侵入太強,不夠優雅。
宣告式即為,在操作資料庫的bean的函式上,使用@Transactional標註,宣告該方法內部將進行資料庫事務處理。藉助於AOP,容器在載入該bean的時候,對其進行增強,在標註了@Transactional的方法執行前和執行後動態織入資料庫事務處理的相關程式碼,也就是藉助Spring容器將程式設計式事務處理的程式碼動態織入進去,無需在業務程式碼中體現,對於不同的配置項(比如回滾條件等)配置在@Transactional上。
在日常實踐中,我們通常使用宣告式事務處理的方式,因為它將業務程式碼與通用資料庫處理程式碼分離開來,夠優雅,夠靈活。
Spring宣告式事務處理的原理
在介紹Spring宣告式事務處理的原理之前,我們先來看一下對事務處理器,如何進行配置。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="xxxDataSource">
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
結合配置檔案以及Spring框架淺析 -- IoC容器與Bean的生命週期中關於獲取BeanFactory、自定義名稱空間解析以及Bean的生命週期的相關介紹,可以梳理出Spring宣告式事務處理的執行過程,從而瞭解到如何對使用了@Transactional註解的方法進行增強,動態織入事務處理相關的邏輯。
步驟一
Spring容器啟動的時候,按照配置檔案中的配置,向BeanFactory中註冊事務處理器DataSourceTransactionManager,將資料來源xxxDataSource設定到其dataSource屬性。
步驟二
當解析到<tx:annotation-driven transaction-manager="transactionManager"/>時,由於這是一個自定義namespace,所以使用tx名稱空間的namespaceHandler-TxNamespaceHandler進行解析(namespace到namespaceHandler的對映關係配置在spring-tx jar包下的META-INF目錄下)。
步驟三
在TxNamespaceHanlder中,根據xml配置檔案中配置的transaction-manager屬性,獲取到其對應的BeanDefinitionParser-AnnotationDrivenBeanDefinitionParser,然後呼叫其parse方法,在其呼叫鏈中,向容器中以"org.springframework.aop.config.internalAutoProxyCreator"為beanName註冊InfrastructureAdvisorAutoProxyCreator,這是一個實現了InstantiationAwareBeanPostProcessor介面的BeanPostProcessor,所以其會在Bean的例項化、初始化過程中對Bean進行加工修飾。此外,還註冊了AnnotationTransactionAttributeSource、TransactionInterceptor、BeanFactoryTransactionAttributeSourceAdvisor,這三個元件也會在下邊的步驟中起作用。
步驟四
在Bean初始化之後,獲取註冊的BeanPostProcessor集合(這裡邊包含了InfrastructureAdvisorAutoProxyCreator),呼叫其postProcessAfterInitialization方法,獲取Bean的proxy,在代理中,針對標註了@Transactional的方法,進行增強,將事務處理相關邏輯動態織入。
我們一步一步地分析一下這個呼叫鏈。
InfrastructureAdvisorAutoProxyCreator的postProcessAfterInitialization方法,實質上呼叫的是其父類AbstractAutoProxyCreator。
@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;
}
從主入口進入
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;
}
// Create proxy if we have advice.
// 獲取Interceptors
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 建立proxy
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;
}
從以上程式碼看出,主要步驟有兩個,一個是獲取Interceptors,一個是基於Interceptors對當前處理的bean生成proxy。
說到proxy,其實瞭解一下代理模式,會對為什麼要生成proxy更加明晰。代理模式相關介紹參見:https://zh.wikipedia.org/wiki/%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F
簡單說就是,為被代理物件建立代理,代理對外部遮蔽了被代理物件的內部邏輯。當外部按照協議試圖呼叫被代理物件的方法時,實際上是落在了代理上,由代理加入增強邏輯,然後再呼叫被代理物件,將結果封裝,最後返回給外部呼叫方。由於呼叫過程中經過了代理,所以代理當然可以加入相應的增強邏輯,比如計數、記錄呼叫引數和返回結果、事務處理。
上邊wikipedia中的附圖能夠清晰地說明這些點。
我們依然回到程式碼中,獲取Interceptors的過程如下:
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
// 獲取候選Advisors,由於上文提到的BeanFactoryTransactionAttributeSourceAdvisor就是Advisor,所以會被加入
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// 獲取符合條件的Advisors
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
我們先進入findCandidateAdvisors方法,最為重要的程式碼如下,本質上是獲取到型別為Advisor的類。我們回到步驟三,可以看到BeanFactoryTransactionAttributeSourceAdvisor其實就是一個Advisor,因此也會被加入到Advisor候選集中。
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);
再回到findAdvisorsThatCanApply方法
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
}
}
boolean hasIntroductions = !eligibleAdvisors.isEmpty();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
// 對於BeanFactoryTransactionAttributeSourceAdvisor,它是PointcutAdvisor,所以走這個分支
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
}
return eligibleAdvisors;
}
再進入到canApply
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}
注意其中的pca.getPointcut,這個返回值如下所示:
private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
@Override
protected TransactionAttributeSource getTransactionAttributeSource() {
return transactionAttributeSource;
}
};
接下來看canApply方法
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "Pointcut must not be null");
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
// 呼叫StaticMethodMatcherPointcut(TransactionAttributeSourcePointcut的父類)的方法,返回值為this
MethodMatcher methodMatcher = pc.getMethodMatcher();
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
classes.add(targetClass);
for (Class<?> clazz : classes) {
Method[] methods = clazz.getMethods();
// 遍歷bean中的方法,關注if判斷的後一個判斷表示式
for (Method method : methods) {
if ((introductionAwareMethodMatcher != null &&
introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
關注其中的methodMatcher.matches方法
public boolean matches(Method method, Class<?> targetClass) {
if (TransactionalProxy.class.isAssignableFrom(targetClass)) {
return false;
}
TransactionAttributeSource tas = getTransactionAttributeSource();
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
tas實際上就是步驟三中設定的AnnotationTransactionAttributeSource,呼叫其getTransactionAttribute方法其實是呼叫其父類的同名方法,其中核心在於
protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// Ignore CGLIB subclasses - introspect the actual user class.
Class<?> userClass = ClassUtils.getUserClass(targetClass);
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
// If we are dealing with method with generic parameters, find the original method.
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
// First try is the method in the target class.
// 類中的方法標註了@Transactional為第一優先順序
TransactionAttribute txAtt = findTransactionAttribute(specificMethod);
if (txAtt != null) {
return txAtt;
}
// 類上標註了@Transactional為第二優先順序
// Second try is the transaction attribute on the target class.
txAtt = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAtt != null) {
return txAtt;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
txAtt = findTransactionAttribute(method);
if (txAtt != null) {
return txAtt;
}
// Last fallback is the class of the original method.
return findTransactionAttribute(method.getDeclaringClass());
}
return null;
}
我們僅以方法上標註@Transactional為例,分析一下是如何對標註進行解析的
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae) {
if (ae.getAnnotations().length > 0) {
for (TransactionAnnotationParser annotationParser : this.annotationParsers) {
// 實現TransactionAnnotationParser介面的共有三個
// 1. Ejb3TransactionAnnotationParser
// 2. JtaTransactionAnnotationParser
// 3. SpringTransactionAnnotationParser
// 我們以3為例進行分析
TransactionAttribute attr = annotationParser.parseTransactionAnnotation(ae);
if (attr != null) {
return attr;
}
}
}
return null;
}
以SpringTransactionAnnotationParser為例,終於看到了我們熟悉的標註@Transactional
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ae, Transactional.class);
if (attributes != null) {
return parseTransactionAnnotation(attributes);
}
else {
return null;
}
}
SpringTransactionAnnotationParser識別@Transactional標註,並解析其中的屬性,屬性列表詳見:https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html
至此,我們找到了@Transactional標註的方法,且識別了標註中的屬性,下一步就是建立proxy,注入事務處理邏輯了
步驟五
上一步中獲取到interceptor之後,這一步藉助於AbstractAutoProxyCreator的createProxy方法開始建立代理。
我們注意到createProxy中的最後一句:
return proxyFactory.getProxy(getProxyClassLoader());
再進一步:
public Object getProxy(ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
先看createAopProxy方法
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
// 目標類如果是介面實現類,使用JDK建立動態代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 否則,使用cglib建立代理
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
JDK動態代理和CgLib代理的區別與聯絡詳見:http://www.cnblogs.com/binyue/p/4519652.html
我們以JDK動態代理為例,JDK動態代理的原理可參考這篇文章:http://www.importnew.com/23168.html。
簡答說,proxy由JDK在執行時動態生成,然後裝載到JVM中,就像手寫的class一樣對外提供服務,只不過其每一個代理方法,都需要呼叫invocationHander.invoke,在該方法就可以加入需要增強的邏輯。
看一下JdkDynamicAopProxy的getProxy方法
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
可以看到最後一句,直接使用JDK中提供的方式,建立代理,並返回,至此完成了代理的建立。
不難看出,JdkDynamicAopProxy實現了重要的InvocationHandler方法,其invoke方法中比較重要的部分如下
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
簡答說就是同一個類的方法可能被多個interceptor按鏈式進行代理,假設interceptor鏈為空,那麼就呼叫其方法;
否則,就依次呼叫其interceptor鏈的代理方法。
在這裡,我們僅關注事務處理相關的interceptor,即TransactionInterceptor(步驟三中注入的),其invoke方法內部呼叫了invokeWithinTransaction。在該方法內部,就可以看到其基於TransactionManager進行事務開啟、遇異常回滾、提交等操作。
這樣,當外部訪問該bean時,實際上訪問的是其已注入Spring事務處理功能的proxy,自然會得到事務處理的支援,如開啟事務,提交事務,事務回滾等。
宣告式事務處理使用須知
關於這部分,其實網上有著大量的介紹,如https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html。但其實你只需要關注一點即可:
宣告式事務處理是基於動態proxy實現的。
所以,如果你標註了@Transactional的方法,無法體現在proxy中(無論是自呼叫還是非public方法),都不會體現在其proxy中,自然也不會被事務所支援。