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


总结

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

相关推荐
用户128526116021 小时前
我把祖传Java项目重构后,接口响应从3s砍到了200ms,只改了这几行代码
java
Linsk1 小时前
组件 = 模板 + 业务逻辑
java·前端·vue.js
星沉远浦2 小时前
用Gemini高效解决Java代码报错难以定位的问题
java
用户298698530145 小时前
Word 文档字符级格式化:Java 实现方案详解
java·后端
笨鸟飞不快6 小时前
从单个服务到集群:一次完整的性能排查复盘
java·前端
荣码6 小时前
用Streamlit给AI应用套个界面,10行代码出Web页面
java·python
SamDeepThinking6 小时前
Java微服务练习方式
java·后端·微服务
朦胧之17 小时前
AI 编程-老项目改造篇
java·前端·后端
程序猿大帅21 小时前
别再只当调包侠了:用 Spring AI 落地 Function Calling,我被大模型硬生生砸出了三个大坑
java
程序员晓琪1 天前
约定大于配置:基于 Java 包名自动生成 API 版本路由的最佳实践
java·spring boot·后端