概述
在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中,自然也不會被事務所支持。