关注
Java极
客技
术
,后台回复“
java
”,获取Java知识体系/面试必看资料
吐血推荐
今天,正式介绍一下Java极客技术知识星球
SpringBoot 精髓之 SpringBoot-starter
Spring 源码学习(八) AOP 使用和实现原理
Java:前程似锦的 NIO 2.0
我们谈谈面试技巧(初入职场年轻人该学的)
业务系统的数据,一般最后都会落入到数据库中,例如
MySQL
、
Oracle
等主流数据库,不可避免的,在数据更新时,有可能会遇到错误,这时需要将之前的数据更新操作撤回,避免错误数据。
Spring
的声明式事务能帮我们处理回滚操作,让我们不需要去关注数据库底层的事务操作,可以不用在出现异常情况下,在 try / catch / finaly 中手写回滚操作。
Spring
的事务保证程度比行业中其它技术(例如
TCC
/
2PC
/
3PC
等)稍弱一些,但使用
Spring
事务已经满足大部分场景,所以它的使用和配置规则也是值得学习的。
接下来一起学习
Spring
事务是如何使用以及实现原理吧。
使用例子
1.创建数据库表
create table test.user(id int auto_increment primary key ,name varchar (20 ) null , age int (3 ) null )engine =InnoDB charset =utf8;
2.创建对应数据库表的 PO
public class JdbcUser { private Integer id; private String name; private Integer age; ...(使用 ctrl + N 进行代码补全 setter 和 getter) }
3.创建表与实体间的映射
在使用
JdbcTemplate
时很纠结,在
Java
类中写了很多硬编码
SQL
,与
MyBatis
使用方法不一样,为了示例简单,使用了
JdbcTemplate
,不过还是建议朋友们用
MyBatis
,让代码风格整洁。
public class UserRowMapper implements RowMapper { @Override public Object mapRow (ResultSet rs, int rowNum) throws SQLException { JdbcUser user = new JdbcUser(); user.setId(rs.getInt("id" )); user.setName(rs.getString("name" )); user.setAge(rs.getInt("age" )); return user; } }
4.创建数据操作接口
public interface UserDao { void insertUser (JdbcUser user) ; void deleteById (Integer id) ; List selectAll () ; }
5.创建数据操作接口实现类
跟书中例子不一样,没有在接口上加入事务注解,而是在公共方法上进行添加,可以在每个方法上自定义传播事件、隔离级别。
public class UserJdbcTemplate implements UserDao { private DataSource dataSource; private JdbcTemplate jdbcTemplate; @Override @Transactional (propagation = Propagation.REQUIRED) public void insertUser (JdbcUser user) { String sql = "insert into user (id, name, age) values (?, ?, ?)" ; jdbcTemplate.update(sql, user.getId(), user.getName(), user.getAge()); System.out.println("Create record : " + user.toString()); } @Override @Transactional public void deleteById (Integer id) { String sql = "delete from user where id = ?" ; jdbcTemplate.update(sql, id); System.out.println("Delete record, id = " + id); throw new RuntimeException("aa" ); } @Override public List selectAll () { String sql = "select * from user" ; List users = jdbcTemplate.query(sql, new UserRowMapper()); return users; } public void setDataSource (DataSource dataSource) { this .dataSource = dataSource; this .jdbcTemplate = new JdbcTemplate(dataSource); } }
6.创建配置文件
<beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" 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/tx http://www.springframework.org/schema/tx/spring-tx.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?characterEncoding=utf8" /> <property name ="username" value ="root" /> <property name ="password" value ="12345678" /> bean > <bean id ="userJdbcTemplate" class ="transaction.UserJdbcTemplate" > <property name ="dataSource" ref ="dataSource" /> bean > <tx:annotation-driven /> <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" /> bean >beans >
7.添加依赖
记得添加数据库连接和
jdbc
、
tx
这两个
spring
模块的依赖
optional(project(":spring-jdbc")) // for Quartz support optional(project(":spring-tx")) // for Quartz support compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.6'
8.启动代码
public class
TransactionBootstrap { public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("transaction/transaction.xml" ); UserJdbcTemplate jdbcTemplate = (UserJdbcTemplate) context.getBean("userJdbcTemplate" ); System.out.println("--- Records Creation start ----" ); JdbcUser user = new JdbcUser(4 , "test" , 21 ); jdbcTemplate.insertUser(user); } }
通过上面的代码,我做了两个测试:
配置文件中,没开启事务。
也就是
这一行被注释了,虽然我们执行的方法中抛出了
RuntimeExcepton
,但是数据库中依旧被插入了数据。
配置文件中,开启事务。
将上面的注释去掉,删掉数据库中的记录,重新执行启动代码,发现数据没有被插入, 在程序抛出异常情况下,
Spring
成功执行了事务,回滚了插入操作。
注解属性 @Transactional
具体位置在:
org.springframework.transaction.annotation.Transactional
属性
类型
作用
value
String
可选的限定描述符,指定使用的事务管理器
propagation
枚举:Propagation
可选的事务传播行为
isolation
枚举:Isolation
可选的事务隔离级别设置
readOnly
boolean
设置读写或只读事务,默认是只读
rollbackFor
Class 数组,必须继承自 Throwable
导致事务回滚的异常类数组
rollbackForClassName
类名称数组,必须继承自 Throwable
noRollbackFor
Class 数组,必须继承自 Throwable
不会导致事务回滚的异常类数组
noRollbackForClassName
类名称数组,必须继承自 Throwable
事务的传播性 Propagation
这是默认的传播属性,如果外部调用方有事务,将会加入到事务,没有的话新建一个。
如果当前存在事务,则加入到该事务;如果当前没有事务,则以非事务的方式继续运行。
以非事务方式运行,如果当前存在事务,则把当前事务挂起。
以非事务方式运行,如果当前存在事务,则抛出异常。
事务的隔离性 Isolation
最低级别,只能保证不读取
物理上损害的数据,允许脏读
只能读到已经提交的数据
可重复读
串行化读,读写相互阻塞
这里只是简单描述了一下这两个主要属性,因为底层与数据库相关,可以看下我之前整理过的
MySQL锁机制
Spring 中实现逻辑
介绍完如何使用还有关键属性设定,本着知其然,知其所以然的学习精神,来了解代码是如何实现的吧。
解析
之前在解析自定义标签时提到,
AOP
和
TX
都使用了自定义标签,
按照我们上一篇
AOP
的学习,再来一遍解析自定义标签的套路:事务自定义标签。
定位到
TxNamespaceHandler
类的初始化方法:
@Override public void init () { registerBeanDefinitionParser("advice" , new TxAdviceBeanDefinitionParser()); registerBeanDefinitionParser("annotation-driven" , new AnnotationDrivenBeanDefinitionParser()); registerBeanDefinitionParser("jta-transaction-manager" , new JtaTransactionManagerBeanDefinitionParser()); }
根据上面的方法,
Spring
在初始化时候,如果遇到诸如
开头的配置后,将会使用
AnnotationDrivenBeanDefinitionParser
解析器的
parse
方法进行解析。
public BeanDefinition parse (Element element, ParserContext parserContext) { registerTransactionalEventListenerFactory(parserContext); String mode = element.getAttribute("mode" ); if ("aspectj" .equals(mode)) { registerTransactionAspect(element, parserContext); if (ClassUtils.isPresent("javax.transaction.Transactional" , getClass().getClassLoader())) { registerJtaTransactionAspect(element, parserContext); } } else { AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext); } return null ; }
Spring
中的事务默认是以
AOP
为基础,如果需要使用
AspectJ
的方式进行事务切入,需要在
mode
属性中配置:
<tx:annotation-driven mode ="aspectj" />
本篇笔记主要围绕着默认实现方式,动态
AOP
来学习,如果对于
AspectJ
实现感兴趣请查阅更多资料~
注册 InfrastructureAdvisorAutoProxyCreator
与
AOP
一样,在解析时,会创建一个自动创建代理器,在事务
TX
模块中,使用的是
InfrastructureAdvisorAutoProxyCreator
。
首先来看,在默认配置情况下,
AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext)
做了什么操作:
private static class AopAutoProxyConfigurer { public static void configureAutoProxyCreator (Element element, ParserContext parserContext) { AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element); String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME; if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) { Object eleSource = parserContext.extractSource(element); RootBeanDefinition sourceDef = new RootBeanDefinition( "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource" ); String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef); RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class); interceptorDef.getPropertyValues().add("transactionAttributeSource" , new RuntimeBeanReference(sourceName)); String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef); RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class); 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); } } }
在这里注册了代理类和三个
bean
,这三个关键
bean
支撑了整个事务功能,为了待会更好的理解这三者的关联关系,我们先来回顾下
AOP
的核心概念:
Pointcut
定义一个切点,可以在这个被拦截的方法前后进行切面逻辑。
Advice
用来定义拦截行为,在这里实现增强的逻辑,它是一个祖先接口
org.aopalliance.aop.Advice
。还有其它继承接口,例如
MethodBeforeAdvice
,特定指方法执行前的增强。
Advisor
用来封装切面的所有信息,主要是上面两个,它用来充当
Advice
和
Pointcut
的适配器。
advisor_consist
回顾完
AOP
的概念后,继续来看下这三个关键
bean
:
TransactionInterceptor
: 实现了
Advice
接口,在这里定义了拦截行为。
AnnotationTransactionAttributeSource
:封装了目标方法是否被拦截的逻辑,虽然没有实现
Pointcut
接口,但是在后面目标方法判断的时候,实际上还是委托给了
AnnotationTransactionAttributeSource.getTransactionAttributeSource
,通过适配器模式,返回了
Pointcut
切点信息。
TransactionAttributeSourceAdvisor
: 实现了
Advisor
接口,包装了上面两个信息。
这三个
bean
组成的结构与
AOP
切面环绕实现的结构一致,所以先学习
AOP
的实现,对事务的了解会有所帮助
接着看我们的自动创建代理器是如何创建的:
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element)
public static void registerAutoProxyCreatorIfNecessary ( ParserContext parserContext, Element sourceElement) { BeanDefinition beanDefinition = AopConfigUtils.registerAutoProxyCreatorIfNecessary( parserContext.getRegistry(), parserContext.extractSource(sourceElement)); useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement); registerComponentIfNecessary(beanDefinition, parserContext); }private static
void registerComponentIfNecessary (@Nullable BeanDefinition beanDefinition, ParserContext parserContext) { if (beanDefinition != null ) { parserContext.registerComponent( new BeanComponentDefinition(beanDefinition, AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME)); } }
在这一步中,注册了一个
beanName
是
org.springframework.aop.config.internalAutoProxyCreator
的
bean
:
InfrastructureAdsivorAutoProxyCreator
,下图是它的继承体系图:
infrastructrue_advisor_auto_proxy_creator_diagram
可以看到,它实现了
InstantiationAwareBeanPostProcessor
这个接口,也就是说在
Spring
容器中,所有
bean
实例化时,
Spring
都会保证调用其
postProcessAfterInitialization
方法。
与上一篇介绍的
AOP
代理器一样,在实例化
bean
的时候,调用了代理器父类
AbstractAutoProxyCreator
的
postProcessAfterInitialization
方法:
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; }
其中关于
wrapIfNecessory
方法,在上一篇
AOP
中已经详细讲过,这里讲下这个方法做了什么工作:
找出指定 `bean` 对应的增强器
根据找出的增强器创建代理
与创建
AOP
代理相似的过程就不再重复说,讲下它们的不同点:
判断目标方法是否适合 canApply
AopUtils#canApply(Advisor, Class, boolean)
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 { return true ; } }
我们在前面看到,
TransactionAttributeSourceAdvisor
的父类是
PointcutAdvisor
,所以在目标方法判断的时候,会取出切点信息
pca.getPointcut()
。
我们之前注入的切面类型
bean
是
AnnotationTransactionAttributeSource
,通过下面的方法包装,最后返回对象类型是
TransactionAttributeSourcePointcut
的切点信息
private
final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() { @Override @Nullable protected TransactionAttributeSource getTransactionAttributeSource () { return transactionAttributeSource; } };
匹配标签 match
在匹配
match
操作中,区别的是
AOP
识别的是
@Before
、
@After
,而我们的事务
TX
识别的是
@Transactional
标签。
判断是否是事务方法的入口方法在这:
org.springframework.transaction.interceptor.TransactionAttributeSourcePointcut#matches
@Override public boolean matches (Method method, Class> targetClass) { TransactionAttributeSource tas = getTransactionAttributeSource(); return (tas == null || tas.getTransactionAttribute(method, targetClass) != null ); }
那它到底到哪一步解析事务注解的呢,继续跟踪代码,答案是:
AnnotationTransactionAttributeSource#determineTransactionAttribute
protected TransactionAttribute determineTransactionAttribute (AnnotatedElement element) { for (TransactionAnnotationParser parser : this .annotationParsers) { TransactionAttribute attr = parser.parseTransactionAnnotation(element); if (attr != null ) { return attr; } } return null ; }
在这一步中,遍历注册的注解解析器进行解析,由于我们关注的是事务解析,所以直接定位到事务注解的解析器:
SpringTransactionAnnotationParser#parseTransactionAnnotation(AnnotatedElement)
public TransactionAttribute parseTransactionAnnotation (AnnotatedElement element) { AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes( element, Transactional.class, false , false ); if (attributes != null ) { return parseTransactionAnnotation(attributes); } else { return null ; } }
首先判断是否含有
@Transactional
注解,如果有的话,才继续调用
parse
解析方法:
protected TransactionAttribute parseTransactionAnnotation (AnnotationAttributes attributes) { RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute(); Propagation propagation = attributes.getEnum("propagation" ); rbta.setPropagationBehavior(propagation.value()); Isolation isolation = attributes.getEnum("isolation" ); rbta.setIsolationLevel(isolation.value()); rbta.setTimeout(attributes.getNumber("timeout" ).intValue()); rbta.setReadOnly(attributes.getBoolean("readOnly"
)); rbta.setQualifier(attributes.getString("value" )); List rollbackRules = new ArrayList<>(); for (Class> rbRule : attributes.getClassArray("rollbackFor" )) { rollbackRules.add(new RollbackRuleAttribute(rbRule)); } for (String rbRule : attributes.getStringArray("rollbackForClassName" )) { rollbackRules.add(new RollbackRuleAttribute(rbRule)); } for (Class> rbRule : attributes.getClassArray("noRollbackFor" )) { rollbackRules.add(new NoRollbackRuleAttribute(rbRule)); } for (String rbRule : attributes.getStringArray("noRollbackForClassName" )) { rollbackRules.add(new NoRollbackRuleAttribute(rbRule)); } rbta.setRollbackRules(rollbackRules); return rbta; }
小结
通过上面的步骤,完成了对应类或者方法的事务属性解析。
主要步骤在于寻找增强器,以及判断这些增强器是否与方法或者类匹配。
如果某个
bean
属于可被事务增强时,也就是适用于增强器
BeanFactoryTransactionAttributeSourceAdvisor
进行增强。
之前我们注入了
TransactionInterceptor
到
BeanFactoryTransactionAttributeSourceAdvisor
中,所以在调用事务增强器增强的代理类时,会执行
TransactionInterceptor
进行增强。同时,也就是在
TransactionInterceptor
类中的
invoke
方法中完成整个事务的逻辑。
运行
事务增强器 TransactionInterceptor
TransactionInterceptor
支撑着整个事务功能的架构。跟之前
AOP
的
JDK
动态代理 分析的一样,
TransactionInterceptor
拦截器继承于
MethodInterceptor
,所以我们要从它的关键方法
invoke()
看起:
public Object invoke (MethodInvocation invocation) throws Throwable { Class> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null ); return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed); }
实际调用了父类的方法:
TransactionAspectSupport#invokeWithinTransaction
protected Object invokeWithinTransaction (Method method, @Nullable Class> targetClass, final InvocationCallback invocation) throws Throwable { TransactionAttributeSource tas = getTransactionAttributeSource(); final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null ); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal; try { retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { completeTransactionAfterThrowing(txInfo, ex); throw
ex; } finally { cleanupTransactionInfo(txInfo); } commitTransactionAfterReturning(txInfo); return retVal; } else { final ThrowableHolder throwableHolder = new ThrowableHolder(); try { Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> { TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); ... return result; } } }
贴出的代码有删减,简略了错误异常的
try / catch
和编程式事务处理的逻辑。
因为我们更多使用到的是声明式事务处理,就是在
XML
文件配置或者
@Transactional
注解编码,实际通过
AOP
实现,而编程式事务处理是通过
Transaction Template
实现,比较少使用到,所以省略了这部分处理代码。
事务管理器
通过该方法,确定要用于给定事务的特定事务管理器
TransactionAspectSupport#determineTransactionManager
protected PlatformTransactionManager determineTransactionManager (@Nullable TransactionAttribute txAttr) { if (txAttr == null || this .beanFactory == null ) { return asPlatformTransactionManager(getTransactionManager()); } String qualifier = txAttr.getQualifier(); if (StringUtils.hasText(qualifier)) { return determineQualifiedTransactionManager(this .beanFactory, qualifier); } else if (StringUtils.hasText(this .transactionManagerBeanName)) { return determineQualifiedTransactionManager(this .beanFactory, this .transactionManagerBeanName); } else { PlatformTransactionManager defaultTransactionManager = asPlatformTransactionManager(getTransactionManager()); ... return defaultTransactionManager; } }
由于最开始我们在
XML
文件中配置过
transactionManager
属性,所以该方法在我们例子中将会返回类型是
DataSourceTransactionManager
的事务管理器,下面是
DataSourceTransactionManager
的继承体系:
datasource_transaction_manager
它实现了
InitializingBean
接口,不过只是在
afterPropertiesSet()
方法中,简单校验
dataSource
是否为空,不细说这个类。
事务开启
TransactionAspectSupport#createTransactionIfNecessary
protected TransactionInfo createTransactionIfNecessary (PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) { if (txAttr != null && txAttr.getName() == null ) { txAttr = new DelegatingTransactionAttribute(txAttr) { @Override public String getName () { return joinpointIdentification; } }; } TransactionStatus status = null ; if (txAttr != null ) { if (tm != null ) { status = tm.getTransaction(txAttr); } } return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); }
在创建事务方法中,主要完成以下三件事:
使用 `DelegatingTransactionAttribute` 包装 `txAttr` 实例
获取事务:`tm.getTransaction(txAttr)`
构建事务信息:`prepareTransactionInfo(tm, txAttr, joinpointIdentification, status)`
核心方法在第二点和第三点,分别摘取核心进行熟悉。
获取 TransactionStatus
status = tm.getTransaction(txAttr);
由于代码较长,直接来总结其中几个关键点
获取事务
创建对应的事务实例,我们使用的是
DataSourceTransactionManager
中的
doGetTransaction
方法,创建基于
JDBC
的事务实例。
protected Object doGetTransaction () { DataSourceTransactionObject txObject = new DataSourceTransactionObject(); txObject.setSavepointAllowed(isNestedTransactionAllowed()); ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource()); txObject.setConnectionHolder(conHolder, false ); return txObject; }
其中在同一个线程中,判断是否有重复的事务,是在
TransactionSynchronizationManager.getResource(obtainDataSource())
中完成的,关键判断逻辑是下面这个:
private static final ThreadLocal> resources = new NamedThreadLocal<>("Transactional resources" );private static Object doGetResource (Object actualKey) { Map map = resources.get(); if (map == null ) { return null ; } Object value = map.get(actualKey); if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) { map.remove(actualKey); if (map.isEmpty()) { resources.remove(); } value = null ; } return