1.「译」Spring 事务管理:@Transactional 深度解析|by MarcoBehler.md
2.SpringBoot源码 | refreshContext方法解析
3.SpringIoc和Aop底层原理
4.spring框架中都用到了哪些设计模式?事务事务
「译」Spring 事务管理:@Transactional 深度解析|by MarcoBehler.md
原文链接为: Spring Transaction Management: @Transactional In-Depth | MarcoBehler
你可以通过本文,对@Transactional 注解在 Spring 事务管理中的底层底层运行机制,形成一个简明实用的源码原理理解。
唯一的事务事务阅读前提?你需要对数据库 ACID 原则有个大致了解,即数据库事务是底层底层什么,我们为什么需要它。源码原理丁香疫情页面源码此外,事务事务本文没有覆盖分布式事务和反应式事务(reactive transactions),底层底层尽管在 Spring 中下文提到的源码原理一些通用原则也是适用的。
在本文中,事务事务你将学到 Spring 事务抽象框架的底层底层核心概念,同时会有很多示例代码帮助你理解。源码原理
相对于 Spring 官方文档,事务事务本文不会让你迷失在 Spring 的底层底层上层概念里。 相反你会以不同寻常的源码原理路径来学习 Spring 事务管理。从底层开始,一层层向上。也就是说,你将从普通原始的 JDBC 事务学起。
普通的 JDBC 事务是如何工作的?如何 start, commit 或 rollback JDBC 事务?最终,他们都做了同样的事来开启和关闭(或称为“管理”)数据库事务。纯 JDBC 事务管理代码如下:这 4 行高度简化的代码,就是 Spring@Transactional 为你在背后做的所有事情。在下一章节中,你将会学到他们是如何工作的。在此之前,我们还有一丁点知识点要补充。
如何使用 JDBC 隔离级别和保存点(savepoints)?如果你已经使用过 Spring@Transactional 注解,你可能碰到过类似用法:我们会在后文更加详细的介绍 Spring 嵌套事务和隔离级别,在这重复提及,是因为这些参数最终可提炼成如下 JDBC 代码:
Spring 或 Spring Boot 的事务是如何工作的?既然现在你对 JDBC 事务有了基础的理解,让我们再去探究下纯粹的 Spring 核心事务。这里所讲都可 1:1 适用于 Spring Boot 和 Sring MVC,element form源码但又做了一些补充。到底什么是 Spring 事务管理或事务抽象框架?记住,事务管理可简单理解为:Spring 如何 start, commit 或 rollback JDBC 事务?是不是听着和前文讲得很相似?抓住重点:基于 JDBC 你只有一个方法(setAutocommit(false))来开启事务管理,Spring 提供了许多不同,但更方便的封装来做相同的事情。
如何使用 Spring 编程式事务管理?最初,但现在很少使用方式,是在 Spring 通过编程定义事务:通过TransactionTemplate 或者直接使用 PlatformTransactionManager。代码示例如下「译者注:参见配套项目 BookingServcie 实现」:与 JDBC 示例 比较:尽管这是一个不小的改进,但编程式事务管理并不是 Spring 事务框架主要关注的。相反,声明式事务管理才是重头戏。让我们一探究竟
如何使用 Spring 的@Transactional 注解(声明式事务管理)?让我们看下时下的 Spring 事务管理通常怎么用:这是怎么做到的?没有了冗余的 XML 配置和额外的编码。相反,你只需要做两件事:所以,为了让@Transactional 工作,你需要:现在,我说 Spring 透明的为你处理事务,到底在指什么? 在有了 JDBC 事务示例 的知识储备后,@Transactional 标注的 UserService 可以翻译简化成:这个示例看起来像魔术,让我们继续探究下 Spring 是如何为你自动插入这些连接代码的。
CGLIB & JDK 代理 - 在@Transactional 之下?Spring 不能真的像我上面做的那样,去重写你的 Java 类来插入连接代码(除非你使用字节码增强等高级技术,在这我们暂时忽略它)
你的registerUser() 方法还是只是调用了 userDao.save(user),这是无法实时改变的。但是 Spring 有它的优势。在核心层,它有一个 IoC 容器。它实例化一个UserService 单例并可自动注入到任何需要 UserService 的 Bean 中。不管何时你在一个 Bean 上使用@Transactional,Spring 使用了个小伎俩。它不是有好源码直接实例化一个 UserService 原始对象,而是一个 UserService 的事务代理对象。借助 Cglib library 的能力,它可以使用子类继承代理(proxy-through-subclassing)的方式来实现。当然还有其他方式可以构造代理对象(例如 Dynamic JDK proxies 「译者注:这要求代理对象有相应接口类」),这里暂不做展开。
你的UserService 可以动态生成代理类,并且代理可以帮你管理事务。但是并不是代理类本身去处理事务状态(open,commit,close),而是委托给了事务管理器(transaction manager)。
你的UserService 有一个 invoice() 事务方法。它调用了另外一个 InvoiceService 类上的 createPdf() 事务方法。然而从 Spring 看来,这里有 2 个逻辑事务存在:第一个在UserService,另外一个在 InvoiceService。Spring 足够智能知道让两个 @Transactional 标记的方法,在底层使用同一个物理数据库事务。
更改事务传播模式为requires_new 告诉 Spring:createPDF() 需要在它自己的、独立于其他任何已经在的事务里执行。这意味着你的底层代码会打开 2(物理)连接/事务 到数据库。Spring 依旧能够机智的把 2 个 逻辑事务( invoice()/createPdf() )映射到两个不同的物理数据库事务上。
每个 Spring 传播模式在 数据库或 JDBC 连接层面到底做了什么?练习: 在原始 Java 实现那节,我展示了 JDBC 能够对事务进行的所有操作。花几分钟思考下,每个 Spring 传播模式在数据库或 JDBC 连接层面到底做了什么。然后再看下下面的解答。
答案:如你所见,大多数传播模式并没有在数据库或 JDBC 层面做什么事情。更多的是通过 Spring 来组织你的代码,告诉 Spring 如何/什么时候/哪里需要事务处理。
在示例中,盒子溯源码任何时候你调用UserService 的 myMethod() 方法,Spring 期望这里有一个打开的事务。它不会为自己开启,相反,在没有已开启事务的情况下调用方法,Spring 会抛出异常。请记住这 “逻辑事务处理”的补充知识点。
@Transactional 上隔离级别(Isolation Levels)代表什么?这是个抖机灵的问题,但当你如下配置的时候,到底发生了什么?哈,这可以简单地等价于:然而数据库事务隔离级别,是一个复杂的主题,你需要自己花些时间去掌握。Pstgres 的官方文档中的 isolation levels 章节,是个不错的入门文档。
最容易踩的@Transactional 的坑?这里有一个 Spring 新手经常踩的坑,看下如下代码:你有一个UserService 类,事务方法 invoice 内部调用了事务方法 createPdf()。所以,当有人调用invoice() 的时候,最终有个多少个物理事务被打开?答案不是 2 个,而是 1 个,为什么呢?让我们回到本文中代理那章节。Spring 为你创建了UserService 代理类,但代理类内部的方法调用,是无法被代理的。也就是说,没有新的事务为你生成。这里有技巧(例如: self-injection 「译者注:参见示例项目 InnerCallSercie」),可以帮助你绕过该限制。但主要收获是:始终牢记代理事务的边界。
在 Spring Boot 或 Spring MVC 中使用@Transactional?我们目前只是实名网站源码讨论了纯粹的核心 Spring 上的用法。那在 Spring Boot 或 Spring MVC 中会有什么使用差异吗?答案是:没有。无论使用何种框架(或更确切地说:Spring 生态系统中的所有框架),您都将始终使用 @Transactional 注解,配合事务管理器,以及@EnableTransactionManagement 注解。没有其他用法了。但是,与 Spring Boot 的唯一区别是,通过 JDBC 自动配置,它会自动设置@EnableTransactionManagement 注解,并为你创建 PlatformTransactionManager。
关于 Spring 回滚的部分,将会在下一次文章修订中补充。Spring Boot 内回滚是通过@Transactional 注解上 rollback 系列配置实现的,读者可查阅源码注释了解使用方式,注释还是写得很完备的,本质上也是根据配置条件,确定何时调用 commit,何时调用 rollback。
Spring 和 JPA / Hibernate 事务管理是如何一起工作的?目标:同步 Spring@Transactional 和 Hibernate / JPA。在这个节点上,你期望 Spring 可以和其他数据库框架,类似 Hibernate(一个流行的 JPA 实现)或 Jooq 等整合。让我来看一个纯粹的 Hibernate 示例(注意:直接使用 Hibernate 还是通过 JPA 使用 Hibernate 都没关系)。用 Hibernate 将UserService 重写如下:然而上述代码有一个大问题:但最终我们还是可以将 Spring 和 Hibernate 无缝整合,也就是说他们其实可以理解对象的事务概念。代码如下:这是怎么做到的?使用 HibernateTransactionManager 有一个非常简单的解决此集成问题的方法:相比在 Spring 配置里使用 DataSourcePlatformTransactionManager,你可以替换成 HibernateTransactionManager(如果使用了原生 Hibernate)或 JpaTransactionManager(如果通过 JPA 使用了 Hibernate)。这个定制化的 HibernateTransactionManager 会确保:与往常一样,一图胜千言(不过请注意,代理和真实服务之间的流程在这被高度抽象和简化了)。
理解 Spring 事务管理后,你应该对 Spring 框架是如何处理事务,以及如何应用于其他 Spring 类库(例如 Spring Boot 或 Spring WebMVC)有了一个很好的了解。最大的收获应是:最终使用哪种框架都无关紧要,这一切可以映射到 JDBC 的基础概念上。正确理解它们(记住:getConnection(),setAutocommit(false),commit()),在以后碰到复杂的企业级项目的使用,你能更容易抓住本质。
到目前为止,你已经对 Spring 事务管理有了全面的了解,希望你在实践中能够灵活运用这些知识,解决实际项目中的事务问题。感谢阅读。
SpringBoot源码 | refreshContext方法解析
本文主要解析SpringBoot启动流程中的`refreshContext`方法。在SpringBoot启动过程中,主要涉及两个阶段:初始化`SpringApplication`对象和`SpringApplication.run`方法执行的内容。`refreshContext`方法的执行,标志着启动流程的深入。
`refreshContext`方法的主要功能是刷新容器,其源码揭示了这一过程的关键步骤。首先,方法通过调用`refresh`来实现底层`ApplicationContext`的刷新。`ApplicationContext`接口的抽象实现类`AbstractApplicationContext`,通过模板方法设计模式,要求具体子类实现抽象方法,以适应不同的配置存储需求。
`refresh`方法执行了一系列操作,包括准备刷新上下文、调用上下文注册为bean的工厂处理器、初始化上下文的消息源、初始化特定上下文子类中的其他特殊bean、检查监听器bean并注册,以及发布相应的事件并销毁已经创建的单例及重置active标志。
在`refresh`方法内部,`prepareRefresh`方法负责准备上下文以进行刷新,包括设置启动日期和活动标志,以及执行属性源的初始化。`obtainFreshBeanFactory`方法获取新的bean工厂,通过`refreshBeanFactory`方法进行配置,以及`getBeanFactory`方法返回当前上下文的内部bean工厂。
`prepareBeanFactory`方法配置工厂标准的上下文特征,如上下文类加载器、后置处理器等。`postProcessBeanFactory`方法进一步处理bean工厂,根据WebApplicationType选择特定的操作,如添加后置处理器以及注册特定的web作用域。
`invokeBeanFactoryPostProcessors`方法调用bean工厂的后置处理器,`registerBeanPostProcessors`方法实例化并注册所有后置处理器bean。`initMessageSource`方法初始化应用上下文消息源,而`initApplicationEventMulticaster`方法则为上下文初始化事件多播。
`onRefresh`方法执行刷新操作,`createWebServer`方法创建web服务,`registerListeners`方法检查并注册监听器。`finishBeanFactoryInitialization`方法实例化所有剩余的单例bean,而`finishRefresh`方法发布事件,重置Spring核心中的公共内省缓存,标志着容器刷新的结束。
`resetCommonCaches`方法重置Spring核心中的公共内省缓存,`contextRefresh.end`方法容器刷新结束,最终执行日志打印,完成启动流程。
总的来说,`refreshContext`方法的执行流程清晰,通过丰富的源码注释,便于学习者深入理解SpringBoot启动机制。本文仅提供方法解析的概览,更多细节请参考原始源码。
SpringIoc和Aop底层原理
文章内容
本文深入解析Spring框架中的Ioc和Aop底层原理。
首先,Ioc(依赖注入)通过Spring配置文件实现对象的创建,而不是传统的new方式。实现Ioc,主要有两种方法:配置文件和注解。Ioc底层原理包括:使用xml配置文件创建类,通过dom4j解析配置,工厂设计模式配合使用,以及反射技术。通过Ioc,开发者只需修改bean配置属性,就能更换UserService类,有效降低类间的耦合度。
与DI(依赖注入)相比,Ioc更加关注控制反转,将对象创建权交由Spring管理;而DI则着重于在对象内部注入属性值。Ioc与DI相辅相成,DI必须在Ioc的基础上进行操作,即先创建对象后进行属性注入。
文章接下来解释了Spring整合web项目原理,包括加载Spring核心配置文件、服务器启动时动态加载配置和创建对象,以及使用ServletContext和监听器实现这一过程。具体操作包括为每个项目创建ServletContext对象、监听对象创建事件,并在contextInitialized()方法中加载配置文件和创建对象,最后将对象存储在ServletContext域对象中以便于获取。
Aop(面向切面编程)在Spring中实现,通过动态代理扩展功能,无需修改源代码。动态代理分为两种情况:有接口时使用jdk动态代理,无接口时使用cglib动态代理。Aop提供多种增强类型,包括前置、后置、异常、最终和环绕增强。
文章最后建议读者关注学习资源,通过扫描二维码获取更多知识和学习资料。
spring框架中都用到了哪些设计模式?
面试官提问了 Spring 框架中设计模式的应用。候选者列举了一些常见的设计模式在 Spring 中的运用,如工厂模式(通过 ApplicationContext 或 BeanFactory 获取对象),单例模式(源码里使用 DCL 实现),代理模式(底层原理)以及模板方法模式(在事务管理器中应用),观察者模式(事件驱动模型的实现)。面试官进一步询问了候选者熟悉的设计模式,候选者提及了常见的如工厂模式、代理模式、模板方法模式、责任链模式、单例模式、包装设计模式、策略模式等,并解释了在项目中使用较多的是模板方法模式、责任链模式、策略模式、单例模式。在提及单例模式的实现方式时,候选者详细介绍了饿汉式、简单懒汉式、DCL 双重检查锁、静态内部类和枚举等实现方式,并解释了使用 DCL 的原因是为了避免指令重排问题,需要 volatile 关键字的支持。在项目的实际应用中,候选者提及了责任链模式用于处理请求、模板方法模式用于处理公共逻辑以及代理模式的较少使用。此外,候选者还推荐了一个 Java 开源项目,介绍其业务清晰、注释详细、文档齐全、代码质量高,几乎每个方法和类都有中文注释,且通过阿里开发插件检查,使用了多种可靠稳定的中间件。该项目应用了多种设计模式,并在 GitHub 和 Gitee 上收获了大量 star。最后,候选者提供了项目的 Gitee 链接、GitHub 链接以及项目文档和视频链接,并简述了项目的功能和意义,以及使用教程和工程模块、系统流程的介绍。