前言
事务从 AOP 开始,有了 AOP 基础就可以学习 Spring 事务阶段了
事务加载、对象创建
一切从配置文件开始,提前把 BeanDefinition 定义信息准备好,到达实例化阶段再统一处理
代码
jdbc.username=root jdbc.password=123456 jdbc.url=jdbc:mysql://localhost:3306/demo jdbc.driverClassName=com.mysql.jdbc.Driver
<context:property-placeholder location="classpath:dbconfig.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="url" value="${jdbc.url}"/> <property name="driverClassName" value="${jdbc.driverClassName}"/> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" > <constructor-arg name="dataSource" ref="dataSource"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="bookService" class="com.vnjohn.tx.xml.service.BookService"> <property name="bookDao" ref="bookDao"/> </bean> <bean id="bookDao" class="com.vnjohn.tx.xml.dao.BookDao"> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> <aop:config> <aop:pointcut id="txPoint" expression="execution(* com.vnjohn.tx.xml.*.*.*(..))"/> <aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/> </aop:config> <tx:advice id="myAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="checkout" propagation="REQUIRED"/> <tx:method name="updateStock" propagation="REQUIRES_NEW"/> </tx:attributes> </tx:advice>
${} 读取本地资源文件的变量
,核心类是 PropertySourcesPlaceholderConfigurer
,只要 XML 配置了 <context:property-placeholder>
标签元素,先会创建该类的 beanDefinition 信息,后会再去执行 BFPP 时先将这个类进行创建,然后执行 postProcessBeanFactory 方法进行资源文件的加载,加载完所有的资源文件以后,再对所有时用到占位符读取变量的地方进行赋值操作.
准备 BeanDefinition 信息
AopNamespaceHandler 执行器对应的解析类是 ConfigBeanDefinitionParser,TxNamespaceHandler 执行器对应的解析类是 TxAdviceBeanDefinitionParser
解析 <aop:config>
标签时,先会注册自动代理创建器,后续会用它来操作生成类的代理对象,接着会解析其下的子标签
// 解析aop:config子节点下的aop:pointcut/aop:advice/aop:aspect List<Element> childElts = DomUtils.getChildElements(element); for (Element elt: childElts) { String localName = parserContext.getDelegate().getLocalName(elt); if (POINTCUT.equals(localName)) { parsePointcut(elt, parserContext); } else if (ADVISOR.equals(localName)) { parseAdvisor(elt, parserContext); } else if (ASPECT.equals(localName)) { parseAspect(elt, parserContext); } }
在上述的案例配置文件中,有两个标签,<aop:pointcut>
会创建表达式切入点的定义信息:AspectJExpressionPointcut
,会在其信息下追加 expression 字符串用于后面的类匹配和方法匹配、<aop:advisor>
创建 advice 持有者定义信息:DefaultBeanFactoryPointcutAdvisor,会其下信息设置 adviceBeanName 属性、pointcut-ref 属性,这两者都是字符串会在后续加载具体的 Bean
解析
<tx:advice>
标签时,会先执行父类 AbstractSingleBeanDefinitionParser#parseInternal 方法去构建一个最外层类型为 TransactionInterceptor 类型的定义信息,调用子类TxAdviceBeanDefinitionParser#doParse
方法解析其属性和子节点信息,首先设置 transactionManager 属性值,如果有配置其属性就取设置的,没有取默认的 transaction-manager;随后判断其下的<tx:attribute>
子节点「类型为 NameMatchTransactionAttributeSource」,超过一个会记录错误日志信息,再获取<tx:attributes>
标签下所有的<tx:method>
节点,每一个节点都作为 「RuleBasedTransactionAttribute 类型」& 设置该节点所配置的属性,最后所有的<tx:method>
定义信息作为 value,key 为 nameMap 进行存储;nameMap 又作为 NameMatchTransactionAttributeSource 类型的一个属性.
class TxAdviceBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { // ...... @Override protected Class<?> getBeanClass(Element element) { return TransactionInterceptor.class; } }
tx:method 标签可以配置以下几个属性👇
- 传播机制:propagation 默认值(REQUIRED)
- 隔离级别:isolation 默认值(DEFAULT)
- 超时时间:timeout 默认值(-1 不超时)
- 是否只读:readOnly 默认值(false)
从以上流程图和文字描述来看,大部分都是需要待解析的属性,在实例化 bean 时会对这些类的属性进行具体的解析,解析整个 <tx:advice>
标签后,类的结构如下图所示👇
创建实例处理过程
依然按照以上的 xml 配置文件内容来进行流程讲解;加载完所有 Bean 定义信息,进行实例化过程
- 普通 Bean【非内部的类型,比如 Advice、Pointcut、Advisor、AopInfrastructureBean】在执行时
会触发 AbstractAutoProxyCreator 类调用 postProcessBeforeInstantiation(实例化之前的方法)),此时会将所有的 advisor 全部加载好存入缓存
- 实例化完 bookService 以后,填充属性阶段,属性类型为 RunTimeBeanReference,接着进行 bookDao 实例化流程,实例化完 bookDao 以后,填充属性阶段,设置好相关的数据
- 接着调用 AbstractAutoProxyCreator#postProcessAfterInitialization(初始化之后的方法)
@Override public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { // 获取当前bean的key:如果beanName不为空,则以beanName为key,如果为FactoryBean类型, // 前面还会添加&符号,如果beanName为空,则以当前bean对应的class为key Object cacheKey = getCacheKey(bean.getClass(), beanName); // 判断当前bean是否正在被代理,如果正在被代理则不进行封装 if (this.earlyProxyReferences.remove(cacheKey) != bean) { // 如果它需要被代理,则需要封装指定的bean return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
前面所有的工作处理完以后,剩下的就是创建代理对象了,这里面涉及到会去创建具体的事务 advisor 增强器
- 先将所有符合条件的 advisor 查找出来,再对其进行匹配,验证类、方法是否能够适配
- 提供 hook 钩子方法用于对目标 advisor 进行扩展,会通过 advisor 持有者去加载其下的 advice 增强器,如果在一级缓存中获取不到该对象,那么就会走一个整体的创建 bean 过程,adviceBeanName:myPoint 这个 advice 对应的类型是
TransactionInterceptor
,之前在加载 beanDefinition 信息时它下面是存在两个属性的,实例化完、填充属性阶段将其补充后这个事务相关的 advice 就创建好了 - 当所有的 advice 加载好以后,再添加一个新的
advice:ExposeInvocationInterceptor
,将它置于首位,标为 0 - 对 advice 进行拓扑排序后,接下来就是创建真正代理对象的过程了,这里不做过多阐述,
如果是 CGLIB 通过 enhancer.create 进行创建,JDK 就通过 Proxy.newProxyInstance 进行创建.