文章目录
1、spring声明式事务概述
事务管理对于企业应用来说是至关重要的,当出现异常情况时,它可以保证数据的一致性。spring支持编程式事务管理和声明式事务管理两种方式。
首先我们来看看spring框架的事物抽象。Spring的事务策略由TransactionManager接口定义,PlatformTransactionManager接口和ReactiveTransactionManager接口继承了TransactionManager接口。我们的程序大多用的都是PlatformTransactionManager接口。
Spring 5.0之后引入了reactive web框架webflux,与webflux平级的就是webmvc,webflux是一个完全的响应式并且非阻塞的web框架,因此spring 5.2之后Spring还为响应式web框架提供了事务管理抽象,即ReactiveTransactionManager接口。我们下面主要讲的是PlatformTransactionManager事务管理抽象。
下面是PlatformTransactionManager接口的源码:
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
void commit(TransactionStatus var1) throws TransactionException;
void rollback(TransactionStatus var1) throws TransactionException;
}
- getTransaction方法通过传入一个TransactionDefinition类型的参数,来获取一个TransactionStatus对象,如果当前的调用堆栈里已经存在一个匹配的事务,TransactionStatus代表的就是这个已存在的事务,否则TransactionStatus代表一个新的事务。
- TransactionStatus接口为事务代码提供了一些控制事务执行和查询事务状态的方法。
- TransactionDefinition是一个接口,该接口里面有一些默认方法,这些默认方法的返回值是声明一个事务的必要属性,比如getPropagationBehavior()、getIsolationLevel()、getTimeout()、isReadOnly()。getPropagationBehavior方法指获取事务的传播行为,getIsolationLevel方法指获取事务的隔离级别,getTimeout方法指获取事务的超时时间,isReadOnly方法指是否为只读事务,即只有读操作而没有写操作的事务。由于TransactionDefinition是一个接口,所以需要实现才能被使用,实现类可以覆盖接口的默认方法,也可以不覆盖,如果不覆盖则使用默认值。
- commit方法则用于提交事务,rollback方法用于回滚事务。
声明式事务一般使用@Transactional注解,并且使用@EnableTransactionManagement开启事务管理就足够了,但接下来我们讲的是其背后的工作原理。
声明式事务是建立在AOP之上的,首先我们的应用程序会通过XML的方式或者注解的方式提供元数据,AOP与事务元数据结合产生一个代理。当执行目标方
法时拦截器TransactionInterceptor会对目标方法进行拦截,然后在目标方法的前后调用代理。其本质是在目标方法开始之前创建或者加入一个事务,在执行完目标
方法之后根据执行情况提交或者回滚事务。
拦截器TransactionInterceptor通过检查方法返回类型来检测是哪种事务管理风格。如果返回的是响应式类型(例如Publisher)的方法则符合响应式事务管理的条
件,其他返回类型包括void则使用PlatformTransactionManager。
@Transactional注解是基于注解的声明式事务,当然基于XMl配置的也可以,但因为是在Spring Boot应用中使用,所以一律使用基于注解的声明式事务。
@Transactional可以作用于接口定义上、接口方法上、类定义上和类的公共方法上,如果作用于私有方法或者包可见的方法上,虽然不会引发错误,但是并不会激
活事务的一些行为。另外,将@Transactional作用于类上要比作用于接口上要更好,下面我来说明下原因。Spring AOP框架中有两种模式,分别是基于代理和
基于AspectJ,而基于代理又分为两种,一种是基于接口的,一种是基于类的,如果是基于类的代理或是基于AspectJ,则将@Transactional作用于接口上不会起到
任何作用。
上面说到了事务是基于AOP的,那么如何让事务支持多种模式呢?@EnableTransactionManagement注解提供了相关的支持,@EnableTransactionManagement注解的源码如下:
public @interface EnableTransactionManagement {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
可以看到,事务管理默认模式为基于代理,即mode=AdviceMode.PROXY,且默认的代理方式为基于接口的,因为proxyTargetClass=false。
下面来看一下@Transactional注解的属性有哪些:
属性 | 类型 | 描述 |
---|---|---|
value | String | 指定事务管理器 |
propagation | enum: Propagation | 事务传播行为 |
isolation | enum: Isolation | 事务隔离级别 |
timeout | Int(单位为秒) | 事务超时时间 |
readOnly | boolean | 是否只读 |
rollbackFor | Class[] | 引起回滚的异常类数组 |
rollbackForClassName | String[] | 引起回滚的异常类名称数组 |
noRollbackFor | Class[] | 不会引起回滚的异常类数组 |
noRollbackForClassName | String[] | 不会引起回滚的异常类名称数组 |
无论您在 Spring 中选择声明式还是编程式事务管理,定义正确的TransactionManager实现都是绝对必要的,TransactionManager的实现通常与它们工作的环境有关:JDBC、JTA、Hibernate 等等。
如果是纯 JDBC,则使用DataSourceTransactionManager,如下代码所示。
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
如果是Hibernate,则使用HibernateTransactionManager。(Hibernate相较于纯JDBC多了一层sessionFactory)
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=${hibernate.dialect}
</value>
</property>
</bean>
<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
2、本地事务源码之事务方法是如何被AOP代理拦截到的?
1、现象(单数据源多DB)
如下图所示,由于test_1和test_2这两个DB在同一个数据源下面,因此是本地事务,spring事务是直接就可以支持的。
@Service
@Transactional
public class MultiDBServiceImpl implements IMultiDBService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void save() {
String sql1 = "insert into test_2.t_class(Fname,Fnum) values(\"303班\",30);";
String sql2 = "insert into test_1.t_student(Fname,Fage,Fclass) values(\"曹操\",30,3);";
jdbcTemplate.execute(sql1);
jdbcTemplate.execute(sql2);
// 回滚
throw new RuntimeException();
}
}
CREATE TABLE t_student (
Fid int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
Fname varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
Fage int(255) NULL DEFAULT NULL,
Fclass int(255) NULL DEFAULT NULL,
PRIMARY KEY (Fid) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE t_class (
Fid int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
Fname varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
Fnum int(11) NULL DEFAULT NULL,
PRIMARY KEY (Fid) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
如果没打@Transactional注解,则直接进入目标方法,否则,进入到cglib生成的代理类中,如下图所示。
可以看到,cglib生成的代理为是com.bobo.springbootdemo.service.impl.MultiDBServiceImpl E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB2afb0e3b类型,
且实现了3个接口(其中Advised接口颇为重要),继承自目标类即com.bobo.springbootdemo.service.impl.MultiDBServiceImpl。
2、如何生成AOP代理类
AOP代理对象的生成是在Bean初始化方法中,有一些BeanPostProcessor对象,其中有一个叫AnnotationAwareAspectJAutoProxyCreator,是专门用于创建代理对象的,AnnotationAwareAspectJAutoProxyCreator是AbstractAutoProxyCreator的子类,AbstractAutoProxyCreator是BeanPostProcessor的子类,AbstractAutoProxyCreator实现了postProcessAfterInitialization方法,如下所示。
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
wrapIfNecessary方法核心代码如下图所示。
这里分两步叙述:
- 如何获取该bean的Interceptors?
- 如何创建代理对象?
1、如何获取该bean的Interceptors?
getAdvicesAndAdvisorsForBean方法点进去,会找到如下方法。
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
// 找到候选的Advisors
List<Advisor> candidateAdvisors = findCandidateAdvisors();
// 从候选Advisors中找到符合条件的Advisors,判断是否符合条件前面已经介绍过了,说白了就是有事务注解
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}
下面介绍如何找候选的Advisors。
通过一路跟踪,最终是走到了BeanFactoryAdvisorRetrievalHelper类的findAdvisorBeans方法。
候选的Advisors的寻找方法实际上就等价于从Bean工厂中找到所有Advisor类型的bean。
如上图所示,会从BeanFactory中找到一个beanName为org.springframework.transaction.config.internalTransactionAdvisor、类型为Advisor的bean BeanFactoryTransactionAttributeSourceAdvisor,该bean通过依赖注入了Advice类型的advice属性,TransactionInterceptor是Advice的子类。
通过全局搜索可以得知,TransactionInterceptor和BeanFactoryTransactionAttributeSourceAdvisor这两个bean的配置在ProxyTransactionManagementConfiguration类里面,如下图所示。
BeanFactoryTransactionAttributeSourceAdvisor间接继承自PointcutAdvisor,实现了getPointcut方法,如下图所示。
TransactionAttributeSource只是个接口,那么它的实现是什么,如何找到切入点的?
TransactionAttributeSource实现前面的图中已经给出了,是AnnotationTransactionAttributeSource,我们来看看这个类是怎么找到事务方法的。
这个类中有两个重要的方法,源码如下。
@Override
@Nullable
protected TransactionAttribute findTransactionAttribute(Class<?> clazz) {
return determineTransactionAttribute(clazz);
}
@Override
@Nullable
protected TransactionAttribute findTransactionAttribute(Method method) {
return determineTransactionAttribute(method);
}
可以看到参数可以是Class类型也可以是Method类型,这说明@Transactional注解既可以作用于类上也可以作用于方法上。
点进findTransactionAttribute方法,如下所示。
@Nullable
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
for (TransactionAnnotationParser parser : this.annotationParsers) {
TransactionAttribute attr = parser.parseTransactionAnnotation(element);
if (attr != null) {
return attr;
}
}
return null;
}
TransactionAnnotationParser的实现有:Ejb3TransactionAnnotationParser、JtaTransactionAnnotationParser和SpringTransactionAnnotationParser,由于这是Spring事务,因此点进去SpringTransactionAnnotationParser看,源码如下。
@Override
@Nullable
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
element, Transactional.class, false, false);
if (attributes != null) {
return parseTransactionAnnotation(attributes);
}
else {
return null;
}
}
可以看到,其实就是看有没有Transactional注解,如果有,则进入parseTransactionAnnotation方法,parseTransactionAnnotation是用来解析Transactional注解的各种属性的。
下面通过debug验证一下。
通过debug,可以看到将Transactional注解打在MultiDBServiceImpl上之后,确实走到了findTransactionAttribute(Class<?> clazz)方法,如下图所示。
2、如何创建代理对象?
AbstractAutoProxyCreator.createProxy方法如下图所示。
proxyFactory.getProxy方法如下所示。
public Object getProxy(@Nullable ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
// createAopProxy方法
protected final synchronized AopProxy createAopProxy() {
// AopProxyFactory接口目前只有一种实现:DefaultAopProxyFactory
return getAopProxyFactory().createAopProxy(this);
}
// DefaultAopProxyFactory的createAopProxy方法
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 这里的config实际上就ProxyFactory对象,ProxyFactory是ProxyConfig的子类
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
ObjenesisCglibAopProxy是CglibAopProxy的子类,CglibAopProxy.getProxy方法如下图所示。
getProxy方法主要代码如下所示。
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));
// 获取callbacks,其中就包括CglibAopProxy的内部类DynamicAdvisedInterceptor
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
// fixedInterceptorMap only populated at this point, after getCallbacks call above
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
// 创建代理类的Class对象和实例
return createProxyClassAndInstance(enhancer, callbacks);
3、执行目标方法时被CglibAopProxy拦截
点进去之后,发现被CglibAopProxy拦截,如下图所示。
4、总结
当用@Autowired注入IMultiDBService Bean并且调用它的save时,会经过如下过程:
- Bean实例化
- Bean初始化
- 执行BeanPostProcessor(AbstractAutoProxyCreator)的postProcessAfterInitialization方法
- 获取该bean的Advisors(获取候选的Advisors(事先配置好的))
- 获取满足条件的Advisors
- 使用Enhancer结合callbacks(DynamicAdvisedInterceptor)生成动态代理对象
- 执行代理对象的方法(IMultiDBService.save)
- 被DynamicAdvisedInterceptor(Callback)拦截
- 获取Advisors链
- 执行Advisors链(TransactionInterceptor)
后面就是TransactionInterceptor的逻辑了,本教程暂不介绍。