第三十一章 Spring之假如让你来写事务——融入IOC容器篇

Spring源码阅读目录

第一部分------IOC篇

第一章 Spring之最熟悉的陌生人------IOC
第二章 Spring之假如让你来写IOC容器------加载资源篇
第三章 Spring之假如让你来写IOC容器------解析配置文件篇
第四章 Spring之假如让你来写IOC容器------XML配置文件篇
第五章 Spring之假如让你来写IOC容器------BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器------Scope和属性填充
第七章 Spring之假如让你来写IOC容器------属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器------拓展篇
第九章 Spring之源码阅读------环境搭建篇
第十章 Spring之源码阅读------IOC篇

第二部分------AOP篇

第十一章 Spring之不太熟的熟人------AOP
第十二章 Spring之不得不了解的内容------概念篇
第十三章 Spring之假如让你来写AOP------AOP联盟篇
第十四章 Spring之假如让你来写AOP------雏形篇
第十五章 Spring之假如让你来写AOP------Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP------Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP------Advice(通知)上篇
第十八章 Spring之假如让你来写AOP------Advice(通知)下篇
第十九章 Spring之假如让你来写AOP------番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP------Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP------Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP------Target Object(目标对象)篇
第二十三章 Spring之假如让你来写AOP------融入IOC容器篇
第二十四章 Spring之源码阅读------AOP篇

第三部分------事务篇

第二十五章 Spring之曾经的老朋友------事务
第二十六章 Spring之假如让你来写事务------初稿篇
第二十七章 Spring之假如让你来写事务------铁三角篇
第二十八章 Spring之假如让你来写事务------属性篇
第二十九章 Spring之假如让你来写事务------状态篇
第三十章 Spring之假如让你来写事务------管理篇
第三十一章 Spring之假如让你来写事务------融入IOC容器篇
第三十二章 Spring之源码阅读------事务篇


文章目录


前言

对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了

所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》


书接上回,在上篇 第三十章 Spring之假如让你来写事务------管理篇 中,A君 实现了 事务 全部功能了。接下来看看 A君 会有什么骚操作吧

尝试动手写IOC容器

出场人物:A君 (苦逼的开发)、老大(项目经理)

背景:老大 要求A君 在一周内开发个简单的 IOC容器

前情提要:A君 实现了 事务 全部功能了 。。。

第二十九版 融入IOC容器

"可以啊,A君 ,干的不错,感觉是不是就是顺手的事?" 老大 看着代码,笑着说道

"还行吧,整体逻辑是还好,只是要去了解 事务 的一些知识,以及考虑对其他框架的内容比较麻烦。" A君 忍不住抱怨到

"一个成功的框架必然要对主流框架进行兼容,不然就流行不起来了。" 老大 语重心长的说到 "接下来,你还要考虑一件事!"

"啥,这还不够吗?还有考虑哪里" A君 愕然

"事务 方面是足够了,但是呢,配置太过繁琐了,简单一点。" 老大

"好趴。" 反正都到这一步了,也不差这一步了。A君 自我安慰道

怎么和 IOC 融合,在前面做 AOP 的时候,A君 已经有经验了。只需要把 XML 解析成对应的 BeanDefinition 即可。老样子,A君 首先定义 xsd 文件,这个都是一样的,直接从 Spring 中拷贝就行了。接下来,就是解析器了,由于这是拓展标签,所以没有相应的解析器,需要自己实现。A君 先看下标签,如下:

xml 复制代码
   <!-- 配置事务的建议 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
            <tx:method name="update*" propagation="NESTED" rollback-for="java.lang.Exception"/>
            <tx:method name="delete*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
        </tx:attributes>
    </tx:advice>

总共算下来就三个标签,tx:advicetx:attributestx:method。逐个解析并注册就行了。A君 先定义 TxAdviceBeanDefinitionParser 类,重写 parse 方法,代码如下:

java 复制代码
	@Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        String defaultTMName = "transactionManager";
        /**
         * 定义TransactionInterceptor
         */
        BeanDefinition ti = new BeanDefinition(TransactionInterceptor.class);
        String adviceId = element.getAttribute(ADVICE_ID);
        ti.setId(adviceId);
        parserContext.registerBeanDefinition(adviceId, ti);
        /**
         * 事务管理器
         */
        String transactionManagerName = TxNamespaceHandler.getTransactionManagerName(element);
        ti.getProperties().add(defaultTMName, new RuntimeBeanReference(transactionManagerName));
        List<Element> txAttributes = DomUtils.getChildElementsByTagName(element, ATTRIBUTES_ELEMENT);
        if (txAttributes.size() == 1) {
            Element attributeSourceElement = txAttributes.get(0);
            BeanDefinition attributeSourceDefinition = parseAttributeSource(attributeSourceElement, parserContext);
            ti.getProperties().add("transactionAttributeSource", new RuntimeBeanReference(attributeSourceDefinition.getId()));
            ti.getProperties().add("transactionManagerBeanName", new RuntimeBeanNameReference(defaultTMName));
        } else {
            //TODO
        }
        return ti;
    }

tx:advice 对应着方法拦截器,而拦截器有依赖于 transactionAttributeSourcetransactionManager 这两个属性,transactionManager 可以直接从当前标签里边取,而 transactionAttributeSource 则需要解析 tx:attributes 标签。tx:attributes 则是对应的回滚规则,于是乎,A君 趁热打铁,继续实现解析 tx:attributes。新增 parseAttributeSource 方法。代码如下:

java 复制代码
private BeanDefinition parseAttributeSource(Element attrEle, ParserContext parserContext) {
        /**
         * 获取method标签
         */
        List<Element> methods = DomUtils.getChildElementsByTagName(attrEle, METHOD_ELEMENT);
        ManagedMap<TypedStringValue, RuleBasedTransactionAttribute> transactionAttributeMap =
                new ManagedMap<>(methods.size());

        for (Element methodEle : methods) {
            //解析方法名
            String name = methodEle.getAttribute(METHOD_NAME_ATTRIBUTE);
            TypedStringValue nameHolder = new TypedStringValue(name);
            /**
             * 解析事务传播行为
             * 解析事务超时时间
             * 解析事务隔离级别
             * 解析是否只读
             */
            RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute();
            String propagation = methodEle.getAttribute(PROPAGATION_ATTRIBUTE);
            String isolation = methodEle.getAttribute(ISOLATION_ATTRIBUTE);
            String timeout = methodEle.getAttribute(TIMEOUT_ATTRIBUTE);
            String readOnly = methodEle.getAttribute(READ_ONLY_ATTRIBUTE);
            if (StringUtils.isNotBlank(propagation)) {
                attribute.setPropagationBehaviorName(RuleBasedTransactionAttribute.PREFIX_PROPAGATION + propagation);
            }
            if (StringUtils.isNotBlank(isolation)) {
                attribute.setIsolationLevelName(RuleBasedTransactionAttribute.PREFIX_ISOLATION + isolation);
            }
            if (StringUtils.isNotBlank(timeout)) {
                attribute.setTimeoutString(timeout);
            }
            if (StringUtils.isNotBlank(readOnly)) {
                attribute.setReadOnly(Boolean.parseBoolean(methodEle.getAttribute(READ_ONLY_ATTRIBUTE)));
            }

            List<RollbackRuleAttribute> rollbackRules = new ArrayList<>(1);
            if (methodEle.hasAttribute(ROLLBACK_FOR_ATTRIBUTE)) {
                String rollbackForValue = methodEle.getAttribute(ROLLBACK_FOR_ATTRIBUTE);
                addRollbackRuleAttributesTo(rollbackRules, rollbackForValue);
            }
            if (methodEle.hasAttribute(NO_ROLLBACK_FOR_ATTRIBUTE)) {
                String noRollbackForValue = methodEle.getAttribute(NO_ROLLBACK_FOR_ATTRIBUTE);
                addNoRollbackRuleAttributesTo(rollbackRules, noRollbackForValue);
            }
            attribute.setRollbackRules(rollbackRules);
            transactionAttributeMap.put(nameHolder, attribute);
        }

        BeanDefinition attributeSourceDefinition = new BeanDefinition(NameMatchTransactionAttributeSource.class);
        attributeSourceDefinition.getPropertyValues().add("nameMap", transactionAttributeMap);
        parserContext.registerWithGeneratedName(attributeSourceDefinition);
        return attributeSourceDefinition;

整体代码没什么难度,解析对应的标签,并且添加到对应的对象中即可。解析器定义完成之后,还需要注册解析器,需要继承 NamespaceHandlerSupportA君 定义 TxNamespaceHandler 类,代码如下:

java 复制代码
public class TxNamespaceHandler extends NamespaceHandlerSupport {

    static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";

    static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";

    static String getTransactionManagerName(Element element) {
        return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ?
                element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME);
    }


    @Override
    public void init() {
        registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
    }

}

注册器有了,还要让 容器 只要有这么个玩意,A君 修改 spring.handlers 文件,把对应的注册器注册进去

txt 复制代码
http\://www.springframework.org/schema/aop=com.hqd.ch03.v29.aop.config.AopNamespaceHandler
http\://www.springframework.org/schema/tx=com.hqd.ch03.v29.tx.transaction.config.TxNamespaceHandler

好了,就这些东西了,接下来又到了快乐的测试环节了,整体代码没什么变化,主要是配置文件变了,A君 定义 bean,xml 文件,配置如下:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns="http://www.springframework.org/schema/beans"
       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
                           http://www.springframework.org/schema/aop
                           https://www.springframework.org/schema/aop/spring-aop.xsd">


    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring?useSSL=false&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
        <property name="initialSize" value="5"/>
        <property name="minIdle" value="5"/>
        <property name="maxActive" value="20"/>
        <property name="validationQuery" value="SELECT 1"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="true"/>
        <property name="testOnReturn" value="false"/>
    </bean>
    <bean id="jdbcTemplate" class="com.hqd.ch03.v29.jdbc.core.SimpleJdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="userService" class="com.hqd.ch03.bean.jdbc.v29.UserServiceImplV29">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean id="transactionManager" class="com.hqd.ch03.v29.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <aop:config expose-proxy="true">
        <aop:pointcut id="txPoint" expression="execution(* com.hqd.ch03.bean.jdbc.v29.UserServiceImplV29.*Data(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint"/>
    </aop:config>


    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
            <tx:method name="update*" propagation="NESTED" rollback-for="java.lang.Exception"/>
            <tx:method name="delete*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
        </tx:attributes>
    </tx:advice>
</beans>

相较于之前,这个配置明显简单了许多。有了配置文件,代码有简单了。测试代码如下:

xml 复制代码
	@Test
    public void v29() throws Throwable {
        System.out.println("############# 第二十九版: 融入IOC篇 #############");
        com.hqd.ch03.v29.AbstractSpringImitation beanFactory =
                new com.hqd.ch03.v29.SpringImitationXml("classpath:v29/bean.xml");
        UserServiceImplV29 userService = (UserServiceImplV29) beanFactory.getBean("userService");
        System.out.println(userService);
        com.hqd.ch03.bean.jdbc.User user = new com.hqd.ch03.bean.jdbc.User();

        user.setId(1);
        user.setSex("nv");
        user.setName("ls");
        int i = new Random().nextInt(100);
        System.out.println(i);
        user.setAge(i);
        userService.updateData(user);
    }

测试结果如下:

修改前:

修改后:

好了,这下彻底完事了,终于可以下班了。A君 怀着快乐的心情,开始收拾包裹回家去咯


总结

正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)

相关推荐
shaoweijava11 分钟前
智能家居销量数据分析(源码+数据库)
java·开发语言·数据库·spring boot·mysql·mybatis·智能家居
Mercury_@222 小时前
功能篇:springboot实现防盗链功能
java·spring boot·后端
2401_890666133 小时前
(免费送源码)计算机毕业设计原创定制:Java+spring boot+MySQL springboot社区快递代取服务系统
java·c++·hive·spring boot·c#·php·课程设计
二十雨辰3 小时前
[Java]网络编程
java·开发语言
power-辰南3 小时前
Netty 常见面试题原理解析
java·开发语言·netty·nio
野蛮的大西瓜3 小时前
如何持续优化呼叫中心大模型呼出机器人的性能?
java·人工智能·语言模型·自然语言处理·机器人·信息与通信
鲤籽鲲4 小时前
C# using 详解
android·java·c#
飞天阁5 小时前
jvm 常用命令
java·jvm
阿moments5 小时前
Reactor 响应式编程(第三篇:R2DBC)
spring boot·spring