前言
上篇文章:
【小家Spring】Spring-jdbc的使用以及Spring事务管理的8种方式介绍(声明式事务+编程式事务)
介绍了Spring事务的众多使用方式,其中讲到全注解@Transactional方式的时候一笔带过了,那么本文就以当下最流行的Spring事务的使用方式:全注解的@Transactional使用方式为切入点,扒开Spring事务管理的神秘面纱~
全注解@Transactional方式的Spring事务
SpringBoot大行其道的今天,基于XML配置的Spring Framework的使用方式注定已成为过去式。
注解驱动应用,面向元数据编程已然成受到越来越多开发者的偏好了,毕竟它的便捷程度、优势都是XML方式不可比拟的。
对SpringBoot有多了解,其实就是看你对Spring Framework有多熟悉~ 比如SpringBoot大量的模块装配的设计模式,其实它属于Spring Framework提供的能力
@Transactional的使用
1、开启注解驱动
@EnableTransactionManagement // 开启注解驱动 @Configuration public class JdbcConfig { ... }
提示:使用@EnableTransactionManagement注解前,请务必保证你已经配置了至少一个PlatformTransactionManager的Bean,否则会报错。(当然你也可以实现TransactionManagementConfigurer来提供一个专属的,只是我们一般都不这么去做~~~)
2、在你想要加入事务的方法上(或者类(接口)上)标注@Transactional注解
@Service public class HelloServiceImpl implements HelloService { @Autowired private JdbcTemplate jdbcTemplate; @Transactional @Override public Object hello() { // 向数据库插入一条记录 String sql = "insert into user (name,age) values ('fsx',21)"; jdbcTemplate.update(sql); // 做其余的事情 可能抛出异常 System.out.println(1 / 0); return "service hello"; } }
单元测试:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RootConfig.class, JdbcConfig.class}) public class TestSpringBean { @Autowired private HelloService helloService; @Test public void test1() { System.out.println(helloService.getClass()); //class com.sun.proxy.$Proxy29 helloService.hello(); } }
就这么简单,事务就生效了(这条数据并没有insert成功~)。
从使用步骤上,有没有一种似曾相识的感觉??
没错,特别特别的像@Async或者@Scheduled的使用。其实,原理和@Async也非常的类似(其实还有有本质区别的,一个借助的自动代理创建器,一个自己使用的后置处理器),因此强烈建议可议先参阅博文:
【小家Spring】Spring异步处理@Async的使用以及原理、源码分析(@EnableAsync)
还有这个:
【小家Spring】Spring AOP的核心类:AbstractAdvisorAutoProxy自动代理创建器深度剖析(AnnotationAwareAspectJAutoProxyCreator)
接下来分析注解驱动事务的原理,同样的我们从@EnableTransactionManagement开始:
@EnableTransactionManagement
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(TransactionManagementConfigurationSelector.class) public @interface EnableTransactionManagement { boolean proxyTargetClass() default false; AdviceMode mode() default AdviceMode.PROXY; int order() default Ordered.LOWEST_PRECEDENCE; }
简直不要太面熟好不好,属性和@EnableAsync
注解的一毛一样。不同之处只在于@Import
导入器导入的这个类,但是我们依然能发现端倪:它也是个ImportSelector
附@EnableAsync
注解源码:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(AsyncConfigurationSelector.class) public @interface EnableAsync { // 支持自定义注解类型 去支持异步~~~ Class<? extends Annotation> annotation() default Annotation.class; boolean proxyTargetClass() default false; AdviceMode mode() default AdviceMode.PROXY; int order() default Ordered.LOWEST_PRECEDENCE; }
TransactionManagementConfigurationSelector
它所在的包为org.springframework.transaction.annotation,jar属于:spring-tx(若引入了spring-jdbc,这个jar会自动导入)
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> { @Override protected String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { // 很显然,绝大部分情况下,我们都不会使用AspectJ的静态代理的~~~~~~~~ // 这里面会导入两个类~~~ case PROXY: return new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()}; case ASPECTJ: return new String[] {determineTransactionAspectClass()}; default: return null; } } private String determineTransactionAspectClass() { return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ? TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME : TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME); } }
依然可以看出,和@EnableAsync导入的AsyncConfigurationSelector如出一辙,都继承自AdviceModeImportSelector,这就是为何我强烈建议先看关于@Async那篇博文的原因,毕竟模式一样,触类旁通,一通百通~
AdviceModeImportSelector目前所知的三个子类是:AsyncConfigurationSelector、TransactionManagementConfigurationSelector、CachingConfigurationSelector。由此可见后面还会着重分析的Spring的缓存体系@EnableCaching,模式也是和这个极其类似的~~~
AutoProxyRegistrar
从名字是意思是:自动代理注册器。它是个ImportBeanDefinitionRegistrar
,可以实现自己向容器里注册Bean的定义信息
// @since 3.1 public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { boolean candidateFound = false; // 这里面需要特别注意的是:这里是拿到所有的注解类型~~~而不是只拿@EnableAspectJAutoProxy这个类型的 // 原因:因为mode、proxyTargetClass等属性会直接影响到代理得方式,而拥有这些属性的注解至少有: // @EnableTransactionManagement、@EnableAsync、@EnableCaching等~~~~ // 甚至还有启用AOP的注解:@EnableAspectJAutoProxy它也能设置`proxyTargetClass`这个属性的值,因此也会产生关联影响~ Set<String> annoTypes = importingClassMetadata.getAnnotationTypes(); for (String annoType : annoTypes) { AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType); if (candidate == null) { continue; } // 拿到注解里的这两个属性 // 说明:如果你是比如@Configuration或者别的注解的话 他们就是null了 Object mode = candidate.get("mode"); Object proxyTargetClass = candidate.get("proxyTargetClass"); // 如果存在mode且存在proxyTargetClass 属性 // 并且两个属性的class类型也是对的,才会进来此处(因此其余注解相当于都挡外面了~) if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() && Boolean.class == proxyTargetClass.getClass()) { // 标志:找到了候选的注解~~~~ candidateFound = true; if (mode == AdviceMode.PROXY) { // 这一部是非常重要的~~~~又到了我们熟悉的AopConfigUtils工具类,且是熟悉的registerAutoProxyCreatorIfNecessary方法 // 它主要是注册了一个`internalAutoProxyCreator`,但是若出现多次的话,这里不是覆盖的形式,而是以第一次的为主 // 当然它内部有做等级的提升之类的,这个之前也有分析过~~~~ AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); // 看要不要强制使用CGLIB的方式(由此可以发现 这个属性若出现多次,是会是覆盖的形式) if ((Boolean) proxyTargetClass) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); return; } } } } // 如果一个都没有找到(我在想,肿么可能呢?) // 其实有可能:那就是自己注入这个类,而不是使用注解去注入(但并不建议这么去做) if (!candidateFound && logger.isInfoEnabled()) { // 输出info日志(注意并不是error日志) } } }
这一步最重要的就是向Spring容器注入了一个自动代理创建器:org.springframework.aop.config.internalAutoProxyCreator,并且看看是采用CGLIB还是JDK代理。
跟踪AopConfigUtils的源码你会发现,事务这块向容器注入的是一个InfrastructureAdvisorAutoProxyCreator,它主要是读取Advisor类,并对符合的bean进行二次代理。在Spring AOP博文中有过详细介绍,这里略过~
参考:【小家Spring】Spring AOP的核心类:AbstractAdvisorAutoProxy自动代理创建器深度剖析(AnnotationAwareAspectJAutoProxyCreator)