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