1. 程式人生 > >【筆記】Spring 事務原理分析和原始碼剖析

【筆記】Spring 事務原理分析和原始碼剖析

概述

事務處理是一個重要並且涉及範圍很廣的領域,涉及到併發和資料一致性方面的問題。作為應用平臺的 Spring 具有在多種環境中配置和使用事務處理的能力,也就是說通過使用 Spring 的事務處理,可以把事務處理的工作統一起來,為事務處理提供統一支援。

由於這方面的內容比較多,這裡只講事務處理中最為基本的使用場景,即 Spring 是怎樣實現對單個數據庫區域性事務處理的。

在 Spring 中,既支援程式設計式事務管理方式,又支援宣告式事務處理方式。程式設計式事務管理程式碼如下所示:

Connection conn = null;
UserTransaction tx = null;
try {
tx = getUserTransaction(); //1.獲取事務 tx.begin(); //2.開啟JTA事務 conn = getDataSource().getConnection(); //3.獲取JDBC // 4.宣告SQL String sql = "select * from INFORMATION_SCHEMA.SYSTEM_TABLES"; PreparedStatement pstmt = conn.
prepareStatement(sql);//5.預編譯SQL ResultSet rs = pstmt.executeQuery(); //6.執行SQL process(rs); //7.處理結果集 closeResultSet(rs); //8.釋放結果集 tx.commit(); //7.提交事務 } catch (Exception e) { tx.
rollback(); //8.回滾事務 throw e; } finally { conn.close(); //關閉連線 }

也就是把事務的開始、提交、回滾顯式的寫到了程式碼裡。這種方式好處是靈活,缺點是囉嗦,對開發人員要求較高,稍有不慎就寫出故障來了。

Spring 中的宣告式事務處理是使用者最常用的,通過將事務處理的過程和業務程式碼分離開來,大大簡化了程式碼的編寫。宣告式事務有兩種使用方式,第一種是純粹的 xml 檔案配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="  
           http://www.springframework.org/schema/beans  
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
           http://www.springframework.org/schema/aop  
           http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
           http://www.springframework.org/schema/context  
           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
	<!-- 配置資料來源 -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/test" />
		<property name="username" value="root" />
		<property name="password" value="[email protected]" />
	</bean>
	<!--配置一個JdbcTemplate例項,並將這個“共享的”,“安全的”例項注入到不同的DAO類中去 -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource" />
	</bean>
	<!-- 宣告事務管理器 -->
	<bean id="txManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>
	<!-- 需要實施事務增強的目標業務Bean -->
	<bean id="libraryTarget" class="com.mucfc.dao.LibraryDaoImpl"
		p:jdbcTemplate-ref="jdbcTemplate" />
 
	<!-- 使用事務代理工廠類為目標業務Bean提供事務增強 -->
	<bean id="libraryFactory"
		class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
		p:transactionManager-ref="txManager" p:target-ref="libraryTarget">
		<!-- 事務屬性配置 -->
		<property name="transactionAttributes">
			<props>
				<!-- 以get開頭的方法採用只讀型事務控制型別 -->
				<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
				<!-- 所有方法均進行事務控制,如果當前沒有事務,則新建一個事務 -->
			<prop key="addBook">PROPAGATION_REQUIRED</prop>
			</props>
		</property>
 
	</bean>
</beans>

在這裡配置了資料來源 dataSource、事務管理器 transactionManager、待事務增強的目標業務 Bean、事務增強配置。第二種是註解方式,不過 xml 中依然需要配置資料來源 dataSource 和事務管理器 transactionManager,額外還需要加上 annotation-driven 配置。

@Transactional
public void run() {
	// DB 操作
}

<tx:annotation-driven/>

原始碼解析

這裡,我們以註解方式為例,講一下宣告式事務的實現原理。註解方式的宣告式事務本質上是把程式設計式事務中原本需要使用者自己寫的程式碼進行了封裝,並通過 AOP 的方式來生成了具體業務類的代理類。

xml 配置解析

無論是純粹 xml 配置,還是註解加配置的方式,都需要 Spring 通過 TxNamespaceHandler 來解析事務標籤,程式碼如下所示:

public class TxNamespaceHandler extends NamespaceHandlerSupport {
	static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";
	static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";

	static String getTransactionManagerName(Element element) {
		return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ?
				element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME);
	}

	@Override
	public void init() {
		registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
		registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
		registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
	}
}

從程式碼中可知,annotation-driven 標籤是由 AnnotationDrivenBeanDefinitionParser 類來解析的。首先 tx:annotation-driven 有一個 model 屬性,有兩個值 proxy 和 aspectj。該類根據 xml 配置來決定載入哪些 AOP 相關類。

下面以 proxy 方式為例,說一下都載入了哪些類?

	private static class AopAutoProxyConfigurer {
		public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
			// 註冊 InfrastructureAdvisorAutoProxyCreator 類
			AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);

			String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;
			if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
				Object eleSource = parserContext.extractSource(element);

				// 註冊 AnnotationTransactionAttributeSource 類.
				RootBeanDefinition sourceDef = new RootBeanDefinition(
						"org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
				sourceDef.setSource(eleSource);
				sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
				String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);

				// 註冊 TransactionInterceptor 類.
				RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
				interceptorDef.setSource(eleSource);
				interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
				registerTransactionManager(element, interceptorDef);
				interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
				String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);

				// 註冊 TransactionAttributeSourceAdvisor 類.
				RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
				advisorDef.setSource(eleSource);
				advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
				advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
				advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
				if (element.hasAttribute("order")) {
					advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
				}
				parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);

				CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
				compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
				compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
				compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));
				parserContext.registerComponent(compositeDef);
			}
		}
	}

在這裡註冊了四個類:

  1. InfrastructureAdvisorAutoProxyCreator:實現了 BeanPostProcessor 介面,在 postProcessAfterInitialization 方法中會根據需要建立 Proxy 類。
  2. AnnotationTransactionAttributeSource:解析事務類,得到事務配置相關資訊。
  3. TransactionInterceptor:事務攔截器,實現了 Advice、MethodInterceptor 介面。
  4. BeanFactoryTransactionAttributeSourceAdvisor:實現了 PointcutAdvisor 介面,依賴 TransactionInterceptor 和 TransactionAttributeSourcePointcut。

事務代理類的建立

Spring 在建立 bean 之後,會執行 BeanPostProcessor 的 postProcessAfterInitialization 方法。而 InfrastructureAdvisorAutoProxyCreator 繼承了 AbstractAutoProxyCreator 類,AbstractAutoProxyCreator 實現了 BeanPostProcessor 介面:

	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) {
		// 查詢適合此 bean 的 advisor
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			// 建立代理
			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;
	}

如何查詢適合某個 bean 的 advisor 呢?可以看下面的程式碼:

	protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
		// 查詢適合此 bean 的 advisor
		List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
		if (advisors.isEmpty()) {
			return DO_NOT_PROXY;
		}
		return advisors.toArray();
	}
	protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		// 查詢適合此 bean 的 advisor
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}
	protected List<Advisor> findAdvisorsThatCanApply(
			List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
		ProxyCreationContext.setCurrentProxiedBeanName(beanName);
		try {
			return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
		} finally {
			ProxyCreationContext.setCurrentProxiedBeanName(null);
		}
	}
	public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
		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) {
				continue;
			}
			if (canApply(candidate, clazz, hasIntroductions)) {
				eligibleAdvisors.add(candidate);
			}
		}
		return eligibleAdvisors;
	}
	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;
		}
	}
	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;
		}

		MethodMatcher methodMatcher = pc.getMethodMatcher();
		if (methodMatcher == MethodMatcher.TRUE) {
			// No need to iterate the methods if we're matching any method anyway...
			return true;
		}
		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 = ReflectionUtils.getAllDeclaredMethods(clazz);
			for (Method method : methods) {
				if ((introductionAwareMethodMatcher != null &&
						introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
						methodMatcher.matches(method, targetClass)) {
					return true;
				}
			}
		}
		return false;
	}

看到最後,可以知道有兩種方法可以判斷:

  1. Pointcut.getMethodMatcher().matches();
  2. IntroductionAdvisor.getClassFilter().matches。

事務攔截器的實現

在前面提到過,註冊了 BeanFactoryTransactionAttributeSourceAdvisor 類,該類實現了 PointcutAdvisor 介面,其中的切面 pointcut 便是通過 TransactionAttributeSourcePointcut 來實現的。TransactionAttributeSourcePointcut 的 matches 方法是根據能否可以從目標 bean 中得到 TransactionAttribute 來判斷是否匹配的。

public interface PointcutAdvisor extends Advisor {
	Pointcut getPointcut();
}
public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
	private TransactionAttributeSource transactionAttributeSource;
	private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
		@Override
		protected TransactionAttributeSource getTransactionAttributeSource() {
			return transactionAttributeSource;
		}
	};

            
           

相關推薦

筆記Spring 事務原理分析原始碼剖析

概述 事務處理是一個重要並且涉及範圍很廣的領域,涉及到併發和資料一致性方面的問題。作為應用平臺的 Spring 具有在多種環境中配置和使用事務處理的能力,也就是說通過使用 Spring 的事務處理,可以把事務處理的工作統一起來,為事務處理提供統一支援。 由於這

筆記 Spring Boot [ 3 ] 之命令列啟動方式啟用不同的配置檔案

命令列啟動方式 在springboot專案的根目錄下執行 mvn spring-boot:run 或 mvn install cd target java -jar xxx.jar

Spring學習筆記Spring中Application ContextServlet Context的區別

1. Servlet Context It is initilized when an servlet application is deployed. Servlet Context holds all the configurations (init

JavaSpring MVC 擴展SSM框架整合

nco span con odin typealias eal nag key ping 開發web項目通常很多地方需要使用ajax請求來完成相應的功能,比如表單交互或者是復雜的UI設計中數據的傳遞等等。對於返回結果,我們一般使用JSON對象來表示,那麽Spring MVC

筆記Installd , Installer 分析

                                          &nb

筆記 定位演算法效能分析

目錄 1 CRLB Computation 2 Mean and Variance Analysis PERFORMANCE ANALYSIS FOR LOCALIZATION ALGORITHMS CRLB給出了使用相同資料的任何無偏估計可獲得的方差的下界,因此它可以作為與

spring boot使用Druid監控配置

Druid是Java語言中最好的資料庫連線池,並且能夠提供強大的監控和擴充套件功能。 Spring Boot預設的資料來源是:org.apache.tomcat.jdbc.pool.DataSource 業界把 Druid 和 HikariCP 做對比後,雖說 HikariCP

筆記《軟體系統分析與設計》複習筆記

7:00考試,3:00複習,我覺得海星。 第四章 資料庫設計(資訊工程模型) 4.1 問題引入與基本概念 基本概念:資料,資料元素,資料物件,資料結構,資料型別,抽象資料型別,資料建模 實體關係圖ERD:利用符號標記實體與關係,實現對資料刻畫的一種資料模型。

筆記Spring MVC攔截入參、出參實現入參解密,出參加密統一管理

需求:為提高介面的安全性,對資料傳輸加密。 前提:Controller層使用@RequestBody接收入參,@ResponseBody出參 入參解密 package com.sep6th.base.core.advice; import java.lang.re

筆記Spring基礎配置及物件的基本獲取

一、首先安裝Spring Tool Suite 二、匯入需要用到基本jar包  commons-logging-1.1.3.jar spring-beans-4.0.0.RELEASE.jar spring-context-4.0.0.RELEASE.jar spr

筆記第一類Stirling數第二類Stirling數

從Stirling這個名字會聯想到Stirling估計式,Stirling估計式同來估算 n!~ sqrt(2pi*n)[(e/n)^n] -------------------------------------------------------------------

筆記JavaScript編碼規範- 逗號分號

不要再語句前面使用逗號。 // bad var story = [ once , upon , aTime ]; // good var story = [ once, upon, aTime ]; 不要有多餘逗號:這會在IE6、IE7和IE9的怪異模式中導致一些問

筆記Spring整合EasyExcel

EasyExcel EasyExcel 由五部分組成,分別是配置讀取類(EasyExcelHalper)、匯出Excel類(EasyExcelExportUtil)、匯入Excel類(EasyExcelImportUtil)、匯入Excel返回結果類(Easy

面試Spring事務面試考點吐血整理(建議珍藏)

Spring和事務的關係 關係型資料庫、某些訊息佇列等產品或中介軟體稱為事務性資源,因為它們本身支援事務,也能夠處理事務。 S

筆記大數加減法 (Java BigInteger原始碼

BigInteger與uint[] 用uint[]來表示非負大數,其中陣列開頭是大數的最高32位,陣列結尾是大數最低32位。其與BigInteger的轉換方法 /// <summary> /// <see cref="uint"/>陣列轉為非負大整數 /// <

轉載spring-session負載均衡原理分析

註明轉載:https://www.jianshu.com/p/beaf18704c3c 第一部分:我會用循序漸進的方式來展示原始碼,從大家最熟悉的地方入手,而不是直接從系統啟動來debug原始碼。直接debug原始碼看到後來大家都會一頭霧水。 本文先從request.getSession()開始

RocketMQ學習筆記二之DefaultMQPushConsumer使用與流程原理分析

版本:        <dependency>        <groupId>org.apache.rocketmq</groupId>   &

專欄 - Android應用安全防護逆向分析學習筆記

Android應用安全防護和逆向分析學習筆記 這裡給大家分享的是《Android應用安全防護和逆向分析》的學習筆記,個人認為移動端的安全也是不可忽視的,我們Android工程師應該重視Android的安全這一塊,希望這個專欄的部落

SpringSpring MVC原理及配置詳解

進行 return sub sca scrip uil 線程安全 松耦合 必須 1.Spring MVC概述: Spring MVC是Spring提供的一個強大而靈活的web框架。借助於註解,Spring MVC提供了幾乎是POJO的開發模式,使得控制器的開發和測試更加簡

原創源碼角度分析Android的消息機制系列(三)——ThreadLocal的工作原理

沒有 cit gen 管理 pre 靜態 bsp 允許 clas ι 版權聲明:本文為博主原創文章,未經博主允許不得轉載。 先看Android源碼(API24)中對ThreadLocal的定義: public class ThreadLocal<T>