第三十一章 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君 怀着快乐的心情,开始收拾包裹回家去咯


总结

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

相关推荐
yuluo_YX1 小时前
使用 Spring AI Aliabab Module RAG 构建 Web Search 应用
前端·人工智能·spring
程序猿大波2 小时前
基于Java,SpringBoot,Vue,HTML高校社团信息管理系统设计
java·vue.js·spring boot
小李同学_LHY2 小时前
微服务架构中的精妙设计:环境和工程搭建
java·spring·微服务·springcloud
慕容魏2 小时前
面经分享,中科创达(安卓开发,二面挂)
java·开发语言
不辉放弃3 小时前
Java/Scala是什么
java·scala
喵手3 小时前
Java实现视频格式转换的完整指南:从FFmpeg到纯Java方案!
java·开发语言·ffmpeg
天上掉下来个程小白3 小时前
Redis-04.Redis常用命令-字符串常用命令
java·数据库·redis·springboot·苍穹外卖
Zz_waiting.3 小时前
多线程 - 线程安全 2 -- > 死锁问题
java·开发语言
就改了4 小时前
Java进阶——Lombok的使用
java·服务器·前端
Agome994 小时前
linux面试题
java·开发语言·excel