第三十一章 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:advice、tx:attributes、tx: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 对应着方法拦截器,而拦截器有依赖于 transactionAttributeSource、transactionManager 这两个属性,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;

整体代码没什么难度,解析对应的标签,并且添加到对应的对象中即可。解析器定义完成之后,还需要注册解析器,需要继承 NamespaceHandlerSupport,A君 定义 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君 怀着快乐的心情,开始收拾包裹回家去咯


总结

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

相关推荐
缓慢更新6 小时前
审批的加签与会签:多人意见怎么合
android·java·数据库·低代码·rxjava
yuniko-n7 小时前
【JUC】volatile 非原子性深入解析
java
sun_weitao8 小时前
极简版 MCP 工具实现方案
java·linux·服务器
金銀銅鐵9 小时前
[Java] 展示增删元素时 LinkedList 的内部变化
java·后端·ai编程
布吉岛的石头9 小时前
Java 程序员第 50 阶段3:数据工程:预训练语料清洗与去重
java·开发语言·人工智能·深度学习·transformer
大文说跨境10 小时前
防关联浏览器的指纹覆盖到什么程度:六类参数的实测与自检方法
java·指纹浏览器·tiktok运营
Carol063010 小时前
上肢及肩肘关节
java·服务器·数据库
yliucherish10 小时前
Log4Shell 复现实录:我读 OpenJDK 源码,发现两道防线的默认值竟然相反
java
维拉德10 小时前
Java新人第一次接打标需求:从抓包找接口到客户端展示
java
luobobo89610 小时前
支付回调验签连续失败三天,最后发现是网关的时间戳单位问题
java