Spring事务管理---中
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring事务管理---中相关的知识,希望对你有一定的参考价值。
Spring事务管理---中
本系列文章:
使用Spring 2.x的声明事务配置方式
上面我们介绍完了三种XML元数据驱动的声明式事务的使用方式,下面我们介绍最后一种基于Spring 2.x的声明事务配置方式。
Spring 2.x后提供的基于XML Schema的配置方式,专门为事务管理提供了一个单独的命名空间用于简化配置,结合新的TX命名空间,现在的声明式事务管理看起来如下:
具体使用方式有以下几个步骤:
- 引入相关aop和tx命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-beans.xsd">
- dataSource,transactioManager,业务对象准备
<!--数据源准备-->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
...
</bean>
<!-- JdbcTemplate准备 -->
<bean id="jt" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务管理器准备 -->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 业务对象准备 -->
<bean id="testService" class="org.transaction.TestService">
<constructor-arg ref="jt"/>
</bean>
- 使用专有的advice命名空间声明相关advice,aop命名空间配置自动代理创建器
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* org.transaction.TestService.update())"/>
<aop:advisor advice-ref="txAdivce" pointcut-ref="pointCut"/>
</aop:config>
<tx:advice id="txAdivce" transaction-manager="tm">
<tx:attributes>
<tx:method name="update" propagation="REQUIRED" read-only="true" timeout="20"/>
</tx:attributes>
</tx:advice>
< tx:advice >是专门为声明事务Adivce而设置的配置元素,底层还是TransactionInterceptor,其transaction-manager指明拦截器需要使用的事务管理器是哪个,如果容器中事务管理器的beanName恰好就是transactionManager,那么可以不明确指定。
< tx:attributes >提供声明式事务所需要的元数据映射信息,对应着拦截器中之前配置的TransactionAttributeSource。
< tx:method >的name属性必须指定,其他事务定义相关属性,如果不指定,采用默认的配置,即DefaultTransactionDefioiton。
tx:method可设置属性列表如下:
< tx:adivce > 指定的是拦截器的配置,那么需要有AOP的支持才能织入到具体的业务对象中去,所以剩下的工作实际上是AOP的配置。
和之前编码过程一样,还是通过自动代理创建器来创建代理对象,而aop:config底层就是依赖自动代理机制的,但是具体解析源码我就不带大家看了。
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* org.transaction.TestService.update())"/>
<aop:advisor advice-ref="txAdivce" pointcut-ref="pointCut"/>
</aop:config>
aop:config标签底层会由aop命名空间解析器进行解析,然后自动代理实现类会根据该元素内部对应的point,advisor及aspect的子元素取得必要的织入信息,然后为容器内注册的bean进行自动代理。
如果各位对aop命名空间开头的标签解析过程感兴趣的话,可以去看看AopNamespaceHandler类的源码,因为Spirng对于非默认命令空间的解析,对应都会去寻找对应的命名空间解析器进行解析。
public class AopNamespaceHandler extends NamespaceHandlerSupport
@Override
public void init()
// In 2.0 XSD as well as in 2.5+ XSDs
//config标签由ConfigBeanDefinitionParser负责解析--感兴趣可以自行研究一下,这里篇幅有限,不展开看了
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
//通过标签方式开启aop自动代理的方式--对应AspectJAutoProxyBeanDefinitionParser解析器
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
// Only in 2.0 XSD: moved to context namespace in 2.5+
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
注解元数据驱动的声明式事务
上面,我们介绍完了XML元数据驱动的声明式事务,下面来看看注解元数据驱动的声明式事务
注解元数据驱动,我相信大家都猜到了,就是我们最常使用的Transactional注解,注解元数据驱动的声明式事务基本原理就是,将业务方法的事务元数据,直接通过注解标注到业务方法或者业务方法所在的对象上,然后再业务方法执行期间,通过反射读取标注在业务方法上的注解所包含的元数据信息,最终根据读取的信息为业务方法构建事务管理的支持。
Transactional注解用于标注业务方法所对应的事务元数据信息,通过该注解可以指定与< te:method />标签几乎相同的信息。
当然,现在不需要指定方法名称了,因为注解直接标注到了业务方法或者业务方法所在的对象定义上。
@Target(ElementType.TYPE, ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
String[] label() default ;
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
String timeoutString() default "";
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default ;
String[] rollbackForClassName() default ;
Class<? extends Throwable>[] noRollbackFor() default ;
String[] noRollbackForClassName() default ;
基于注解的驱动式事务基本使用如下:
@Transactional
@Component
@RequiredArgsConstructor
public class TestService
private final JdbcTemplate jt;
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true,timeout = 20)
public void update() throws Exception
throw new RuntimeException("我是来捣乱的");
有下面几点需要注意:
- @Transactional标注为对象级别的话,该注解标注的事务管理信息会应用到该类所有方法上。通过将相同的事务管理行为提取到对象级别的@Transactional,可以有效减少标注的数量。
- 如果只标注在了指定方法上,那么只会应用在指定方法上。
- 如果不为@Transactional注解指定一些自定义事务配置,那么它会像< tx:method />一样采用与DefaultTransactionDefinition一样的事务定义内容。
如果仅仅只通过@Transactional标注业务对象和对象中的业务方法,并不会给相应的业务方法执行提供任何事务管理信息的支持,该注解的作用类似TransactionAttributeSource中保存的一条映射关系,即通过该注解我们只是知道了当前方法需要什么样的事务支持,但是我们只有在方法执行时通过反射读取这些事务信息,才能去构建事务,从而使得事务生效。
模拟解析注解
首先,我们需要弄清楚解析步骤:
- 判断哪些bean上标注了@Transactional注解,然后解析得到TransactionAttribute
- 将当前方法和TransactionAttribute的映射关系加入TransactionAttributeSource中
- 加入TransactionAttributeSource的前提是我们已经准备好了一个TransactionAttributeSource,当然还有TransactionInterceptor和TransactionManager,还有最重要的自动代理创建器
- 但是事务并不是任何情况下都需要的,即TI,TM,TAS和自动代理创建器并不是什么任何情况下都需要创建的
- 因此,我们需要准备一个开关,需要的时候打开开关,创建上面四个组件,别的时候就不管
分析完上面的需求后,我们大概知道了具体需要怎么做,那么到底怎么完成上面的需求呢?
- 判断哪些bean上标注了@Transactional注解—>需要对每一个bean都进行检查,这不就想到了bean的后置处理器吗,而且还需要为每一个需要事务支持的bean生成代理对象,那么同时兼具这两个功能的,不就是自动代理创建器吗?
- 在什么时候获取当前方法和对应TransactionAttribute的映射关系呢? —>利用自动代理创建器,这个具体下面会讲
- 需要一个开关–>这个比较简单,我们可以自定义一个事务开启的注解,当需要开启事务的时候,我们就将上面四个组件导入到容器中。
思路也有了,解决办法也有了,下面就开始实战吧:
- 开关最简单,我们这里先准备好
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
//导入一个类
@Import(MyTransactionManagementConfigurationSelector.class)
public @interface MyEnableTransactionManagement
/**
* 是否默认采用cglib进行代理
*/
boolean proxyTargetClass() default false;
/**
* 代理模式--是jdk,cglib代理还是aspectj代理
*/
AdviceMode mode() default AdviceMode.PROXY;
- 开关打开后,我们就需要导入上面说的那些组件了,而导入的工作就交给了MyTransactionManagementConfigurationSelector 来完成
/**
* 注册TM,拦截器和自动代理创建器
*/
public class MyTransactionManagementConfigurationSelector implements ImportSelector
/**
* 返回的是需要导入的bean的全类名
* 这里我们需要导入一个配置类和一个自动代理创建器
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata)
return new String[]
//注册TM,拦截器等组件
MyTransactionConfiguration.class.getName(),
//注册自动代理创建器到容器
AutoProxyRegistrar.class.getName()
;
ImportSelector的原理涉及配置类的解析过程,我下面会稍微提一嘴,该接口的作用就是可以向容器中放入某些bean集合
- 先来看看TM,拦截器的注册,就是MyTransactionConfiguration就是导入一个配置类,但是注意这个配置类必须放在启动类或者自己加的@CompoentScan注解扫描不到的地方
@Configuration
public class MyTransactionConfiguration
//注册TM
@Bean
//标注角色为基础设施类,该bean被解析后,其BeanDefinition中会标记当前bean的role为ROLE_INFRASTRUCTURE
//ROLE_INFRASTRUCTURE与我们待会要注入的自动代理创建器有关,暂时先不用深究
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionManager transactionManager(DataSource dataSource)
return new JdbcTransactionManager(dataSource);
//注册transactionAttributeSource---这里放入的是我们自定义的MyTransactionAttributeSource
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource()
return new MyTransactionAttributeSource();
//注册拦截器
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(TransactionManager transactionManager,TransactionAttributeSource transactionAttributeSource)
return new TransactionInterceptor(transactionManager,transactionAttributeSource);
/**
* transactionAttributeSourceAdvisor增强器最大的作用在于
* 给我们提供了一个TransactionAttributeSourcePointcut
* 该pointcut可以帮助我们进行类级别的过滤和方法级别的过滤
* 方法级别的过滤是当前方法在TransactionAttributeSource中能否找到对应的一个TransactionAttribute
*/
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSourceAdvisor transactionAttributeSourceAdvisor(TransactionInterceptor transactionInterceptor)
return new TransactionAttributeSourceAdvisor(transactionInterceptor);
- 自动代理创建器导入的是InfrastructureAdvisorAutoProxyCreator,通过AutoProxyRegistrar导入的,因为他实现了ImportBeanDefinitionRegistrar接口,该接口实现后,也可以向容器中导入bean,和ImportSelector功能一致
- 在来看看我们自定义的transactionAttributeSource
public class MyTransactionAttributeSource implements TransactionAttributeSource
/**
* 负责解析@Transactional注解的
*/
private TransactionAnnotationParser parser = new SpringTransactionAnnotationParser();
/**
* 方法名和方法名关联的TransactionAttribute
*/
private final Map<String, TransactionAttribute> nameMap = new HashMap<>();
/**
* 存放已经解析过的方法
*/
private final Set<String> parsedMethod=new HashSet<>();
/**
* 类过滤信息缓存--key是类名,value表示当前类是否需要事务支持: 1表示需要,其他数字表示不需要
*/
private final Map<String,Integer> classFilter=new HashMap<>();
private final Integer NEED_TRANSACTION=1;
/**
* 用于pointcut的类过滤---判断类和类的每个方法上是否标注了@Transacion注解
*/
@Override
public boolean isCandidateClass(Class<?> targetClass)
//查询缓存
Integer res = classFilter.get(targetClass.getName());
if(res!=null)
return res.equals(NEED_TRANSACTION);
//当前类是否需要事务,即当前类上,方法上是否存在@Transactional注解
if(parser.isCandidateClass(targetClass))
classFilter.put(targetClass.getName(),NEED_TRANSACTION);
return true;
return false;
/**
* 该方法在TransactionAttributeSourceAdvisor增强器的point中,用于方法级别过滤
* 如果第一次来,缓存没有会进行解析,第二次来直接走缓存即可
*/
@Override
public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass)
//先去缓存中寻找--保存已经解析过
String cacheKey = getCacheKey(method, targetClass);
if(parsedMethod.contains(cacheKey))
return nameMap.get(cacheKey);
//如果当前类上,类的方法上都没有标注该注解,那么就跳过
if(!parser.isCandidateClass(targetClass))
parsedMethod.add(cacheKey);
return null;
//解析方法上标注的@Transactional注解
TransactionAttribute attr = parser.parseTransactionAnnotation(method);
if(attr==null)
//如果方法上没标注,再尝试解析类上的注解
attr=parser.parseTransactionAnnotation(targetClass);
nameMap.put(cacheKey,attr);
parsedMethod.add(cacheKey);
return attr;
private String getCacheKey(Method method, Class<?> targetClass)
return targetClass.getName()+method.getName();
- 测试
//关闭事务的自动配置
@SpringBootApplication(exclude = TransactionAutoConfiguration.class)
//开启我们手动的事务配置
@MyEnableTransactionManagement
public class TransactionMain
public static void main(String[] args) throws ClassNotFoundException, SQLException
ConfigurableApplicationContext app = SpringApplication.run(TransactionMain.class, args);
TestService testService = app.getBean(TestService.class);
try
testService.update();
catch (Exception e)
e.printStackTrace();
SpringBoot会自动开启事务的相关配置,因此我们需要先关闭一下,替换为我们自定义的事务管理实现
模拟流程的原理解析
如果能看懂上面模拟解析的每个步骤,那么说明你对Spring源码研究的还不错,如果没看懂,没关系,下面我一点点带领大家来分析:
import导入配置
就先从最简单的开始吧:
@Import(MyTransactionManagementConfigurationSelector.class)
public class MyTransactionManagementConfigurationSelector implements ImportSelector
/**
* 返回的是需要导入的bean的全类名
* 这里我们需要导入一个配置类和一个自动代理创建器
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata)
return new String[]
MyTransactionConfiguration.class.getName(),
AutoProxyRegistrar.class.getName()
;
因为Import注解,ImportSelector相关接口的解析都涉及到了@Configuration标注的配置类解析过程,所以这里先来大致浏览一下配置类解析是个怎么样的流程:
配置类是由ConfigurationClassPostProcessor工厂后置处理器解析的,工厂后置处理器相关接口都会在refresh方法中被调用:
@Override
public void 以上是关于Spring事务管理---中的主要内容,如果未能解决你的问题,请参考以下文章