标签:wing href 需要 junit 初始化 att class err 面试问题
以下面试题,基于网络整理,和自己编辑。具体参考的文章,会在文末给出所有的链接。
如果胖友有自己的疑问,欢迎在星球提问,我们一起整理吊吊的 Spring 面试题的大保健。
而题目的难度,艿艿尽量按照从容易到困难的顺序,逐步下去。
注意 1 ,这个面试题,暂时不包括 Spring MVC 部分。主要以 Spring IoC、Spring AOP、Spring Transaction 为主。
注意 2 ,T T Spring 怎么会有这么多问题,艿艿自己面试很多都不太问,跟背书一样。所以整理的过程,真的是痛苦万分。
Spring 是一个很庞大的技术体系,可以说包容一切,所以本文我们按照下面的顺序,罗列各种面试题:
Spring 是一个开源应用框架,旨在降低应用程序开发的复杂度。
它是轻量级、松散耦合的。
它的轻量级主要是相对于 EJB 。随着 Spring 的体系越来越庞大,大家被 Spring 的配置搞懵逼了,所以后来出了 Spring Boot 。
它具有分层体系结构,允许用户选择组件,同时还为 J2EE 应用程序开发提供了一个有凝聚力的框架。
如下是一张比较早期版本的 Spring Framework 的模块图:
艿艿:因为它的配色比较好看,哈哈哈哈。所以,没自己画一个最新的。
我们按照一个一个分块来说。
Spring 核心容器
对应图中,Core Container 。
该层基本上是 Spring Framework 的核心。它包含以下模块:
Spring Bean
核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
Spring Context
Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、事件机制、校验和调度功能。
SpEL (Spring Expression Language)
Spring 表达式语言全称为 “Spring Expression Language”,缩写为 “SpEL” ,类似于 Struts2 中使用的 OGNL 表达式语言,能在运行时构建复杂表达式、存取对象图属性、对象方法调用等等,并且能与 Spring 功能完美整合,如能用来配置 Bean 定义。
或者说,这块就是 Spring IoC 。
数据访问
对应图中,Data Access 。
该层提供与数据库交互的支持。它包含以下模块:
JDBC (Java DataBase Connectivity)
Spring 对 JDBC 的封装模块,提供了对关系数据库的访问。
ORM (Object Relational Mapping)
Spring ORM 模块,提供了对 hibernate5 和 JPA 的集成。
- hibernate5 是一个 ORM 框架。
- JPA 是一个 Java 持久化 API 。
OXM (Object XML Mappers)
Spring 提供了一套类似 ORM 的映射机制,用来将 Java 对象和 XML 文件进行映射。这就是 Spring 的对象 XML 映射功能,有时候也成为 XML 的序列化和反序列化。
用的比较少,胖友了解下即可。
Transaction
Spring 简单而强大的事务管理功能,包括声明式事务和编程式事务。
Web
该层提供了创建 Web 应用程序的支持。它包含以下模块:
WebMVC
MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
WebFlux
基于 Reactive 库的响应式的 Web 开发框架
不了解的胖友,可以看看 《使用 Spring 5 的 WebFlux 开发反应式 Web 应用》
WebSocket
Spring 4.0 的一个最大更新是增加了对 Websocket 的支持。
Websocket 提供了一个在 Web 应用中实现高效、双向通讯,需考虑客户端(浏览器)和服务端之间高频和低延时消息交换的机制。
一般的应用场景有:在线交易、网页聊天、游戏、协作、数据可视化等。
Portlet 已经废弃
AOP
该层支持面向切面编程。它包含以下模块:
AOP
通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。
Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
Aspects
该模块为与 AspectJ 的集成提供支持。
Instrumentation
该层为类检测和类加载器实现提供支持。
用的比较少,胖友了解下即可。
其它
JMS (Java Messaging Service)
提供了一个 JMS 集成框架,简化了 JMS API 的使用。
可能有胖友不太了解 JMS ,可以看看 《JMS(Java消息服务)入门教程》 。
Test
该模块为使用 JUnit 和 TestNG 进行测试提供支持。
Messaging
该模块为 STOMP 提供支持。它还支持注解编程模型,该模型用于从 WebSocket 客户端路由和处理 STOMP 消息。
下面列举了一些使用 Spring 框架带来的主要好处:
艿艿:注意,下文中加粗的内容,这是本句话的关键。
当然,Spring 代码优点的同时,一定会带来相应的缺点:
把很多 JavaEE 的东西封装了,在满足快速开发高质量程序的同时,隐藏了实现细节。
这就导致,很多工程师,离开了 Spring 之后,就不知道怎么工作了。从 Java 工程师,变成了 Spring 工程师。对于有追求的我们,还是需要知其然,知其所以然。
Spring 框架中使用到了大量的设计模式,下面列举了比较有代表性的:
当然,感兴趣的胖友,觉得不过瘾,可以看看艿艿基友知秋写的几篇文章:
下面,我们会将分成 IoC 和 Bean 两部分来分享 Spring 容器的内容。
- IoC ,侧重在于容器。
- Bean ,侧重在于被容器管理的 Bean 。
注意,正确的拼写是 IoC 。
Spring 框架的核心是 Spring IoC 容器。容器创建 Bean 对象,将它们装配在一起,配置它们并管理它们的完整生命周期。
艿艿,注意上面三段段话的加粗部分的内容。
在依赖注入中,你不必主动、手动创建对象,但必须描述如何创建它们。
另外,依赖注入的英文缩写是 Dependency Injection ,简称 DI 。
艿艿的吐槽,最怕这种概念题。下面引用知乎上的一个讨论:《IoC 和DI 有什么区别?》
IoC 是个更宽泛的概念,DI 是更具体的。引用郑烨的一篇博客,引用郑烨的一篇博客,我眼中的Spring
Dependency Injection
原来,它叫 IoC 。
Martin Flower 发话了,是个框架都有 IoC ,这不足以新生容器反转的“如何定位插件的具体实现”,于是,它有了个新名字,Dependency Injection 。其实,它就是一种将调用者与被调用者分离的思想,Uncle Bob 管它叫DIP(Dependency Inversion Principle),并把它归入OO设计原则。
同 Spring 相比,它更早进入我的大脑。一切都是那么朦胧,直至 Spring 出现。慢慢的,我知道了它还分为三种:
- Interface Injection(type 1)
- Setter Injection(type 2)
- Constructor Injection(type 3)。
Martin Flower那篇为它更名的大作让我心目关于它的一切趋于完整。
在 Spring 中,它是一切的基础。Spring 的种种优势随之而来。
于我而言,它为我带来更多的是思维方式的转变,恐怕以后我再也无法写出那种一大块的全功能程序了。
通常,依赖注入可以通过三种方式完成,即:
上面一个问题的三种方式的英文,下面是三种方式的中文。
目前,在 Spring Framework 中,仅使用构造函数和 setter 注入这两种方式。
那么这两种方式各有什么优缺点呢?胖友可以简单阅读 《Spring两种依赖注入方式的比较》,不用太较真。综述来说:
构造函数注入 | setter 注入 |
---|---|
没有部分注入 | 有部分注入 |
不会覆盖 setter 属性 | 会覆盖 setter 属性 |
任意修改都会创建一个新实例 | 任意修改不会创建一个新实例 |
适用于设置很多属性 | 适用于设置少量属性 |
Spring 提供了两种( 不是“个” ) IoC 容器,分别是 BeanFactory、ApplicationContext 。
BeanFactory
BeanFactory 在
spring-beans
项目提供。
BeanFactory ,就像一个包含 Bean 集合的工厂类。它会在客户端要求时实例化 Bean 对象。
ApplicationContext
ApplicationContext 在
spring-context
项目提供。
ApplicationContext 接口扩展了 BeanFactory 接口,它在 BeanFactory 基础上提供了一些额外的功能。内置如下功能:
另外,ApplicationContext 会自动初始化非懒加载的 Bean 对象们。
详细的内容,感兴趣的胖友,可以看看 《【死磕 Spring】—— ApplicationContext 相关接口架构分析》 一文。源码之前无秘密。简单总结下 BeanFactory 与 ApplicationContext 两者的差异:
艿艿:可能很多胖友没看过源码,所以会比较难。
BeanFactory | ApplicationContext |
---|---|
它使用懒加载 | 它使用即时加载 |
它使用语法显式提供资源对象 | 它自己创建和管理资源对象 |
不支持国际化 | 支持国际化 |
不支持基于依赖的注解 | 支持基于依赖的注解 |
另外,BeanFactory 也被称为低级容器,而 ApplicationContext 被称为高级容器。
BeanFactory 最常用的是 XmlBeanFactory 。它可以根据 XML 文件中定义的内容,创建相应的 Bean。
以下是三种较常见的 ApplicationContext 实现方式:
1、ClassPathXmlApplicationContext :从 ClassPath 的 XML 配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。示例代码如下:
ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);
|
2、FileSystemXmlApplicationContext :由文件系统中的XML配置文件读取上下文。示例代码如下:
ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);
|
3、XmlWebApplicationContext :由 Web 应用的XML文件读取上下文。例如我们在 Spring MVC 使用的情况。
当然,目前我们更多的是使用 Spring Boot 为主,所以使用的是第四种 ApplicationContext 容器,ConfigServletWebServerApplicationContext 。
简单来说,Spring 中的 IoC 的实现原理,就是工厂模式加反射机制。代码如下:
interface Fruit {
|
className
对应的 Fruit 对象。在基友 《面试问烂的 Spring IoC 过程》 的文章中,把 Spring IoC 相关的内容,讲的非常不错。
Spring 的 ApplicationContext 提供了支持事件和代码中监听器的功能。
我们可以创建 Bean 用来监听在 ApplicationContext 中发布的事件。如果一个 Bean 实现了 ApplicationListener 接口,当一个ApplicationEvent 被发布以后,Bean 会自动被通知。示例代码如下:
public class AllApplicationEventListener implements ApplicationListener<ApplicationEvent> {
|
Spring 提供了以下五种标准的事件:
#refresh()
方法时被触发。#start()
方法开始/重新开始容器时触发该事件。#stop()
方法停止容器时触发该事件。除了上面介绍的事件以外,还可以通过扩展 ApplicationEvent 类来开发自定义的事件。
① 示例自定义的事件的类,代码如下:
public class CustomApplicationEvent extends ApplicationEvent{
|
② 为了监听这个事件,还需要创建一个监听器。示例代码如下:
public class CustomEventListener implements ApplicationListener<CustomApplicationEvent> {
|
③ 之后通过 ApplicationContext 接口的 #publishEvent(Object event)
方法,来发布自定义事件。示例代码如下:
// 创建 CustomApplicationEvent 事件
|
这个问题,胖友可以在回过头看 「什么是 Spring IoC 容器?」 问题,相互对照。
单纯从 Spring Framework 提供的方式,一共有三种:
1、XML 配置文件。
Bean 所需的依赖项和服务在 XML 格式的配置文件中指定。这些配置文件通常包含许多 bean 定义和特定于应用程序的配置选项。它们通常以 bean 标签开头。例如:
<bean id="studentBean" class="org.edureka.firstSpring.StudentBean">
|
2、注解配置。
您可以通过在相关的类,方法或字段声明上使用注解,将 Bean 配置为组件类本身,而不是使用 XML 来描述 Bean 装配。默认情况下,Spring 容器中未打开注解装配。因此,您需要在使用它之前在 Spring 配置文件中启用它。例如:
<beans>
|
3、Java Config 配置。
Spring 的 Java 配置是通过使用 @Bean 和 @Configuration 来实现。
@Bean
注解扮演与 <bean />
元素相同的角色。@Configuration
类允许通过简单地调用同一个类中的其他 @Bean
方法来定义 Bean 间依赖关系。例如:
|
目前主要使用 Java Config 配置为主。当然,三种配置方式是可以混合使用的。例如说:
@RequestMapping
注解。另外,现在已经是 Spring Boot 的天下,所以更加是 Java Config 配置为主。
艿艿,这个是一个比较小众的题目,简单了解即可。
Spring Bean 支持 5 种 Scope ,分别如下:
另外,网络上很多文章说有 Global-session 级别,它是 Portlet 模块独有,目前已经废弃,在 Spring5 中是找不到的。
仅当用户使用支持 Web 的 ApplicationContext 时,最后三个才可用。
再补充一点,开发者是可以自定义 Bean Scope ,具体可参见 《Spring(10)—— Bean 作用范围(二)—— 自定义 Scope》 。
不错呢,还是那句话,这个题目简单了解下即可,实际常用的只有 Singleton 和 Prototype 两种级别,甚至说,只有 Singleton 级别。??
艿艿说:这是一个比较高级的 Spring 的面试题,非常常见,并且答对比较加分。当然,如果实际真正弄懂,需要对 Spring Bean 的源码,有比较好的理解,所以 《精尽 Spring 源码》 系列,该读还是读吧。
艿艿:要注意下面每段话,艿艿进行加粗的地方。
Spring Bean 的初始化流程如下:
实例化 Bean 对象
Spring 容器根据配置中的 Bean Definition(定义)中实例化 Bean 对象。
Bean Definition 可以通过 XML,Java 注解或 Java Config 代码提供。
Spring 使用依赖注入填充所有属性,如 Bean 中所定义的配置。
#setBeanName(String name)
方法。#setBeanFactory(BeanFactory beanFactory)
方法。#preProcessBeforeInitialization(Object bean, String beanName)
方法。#afterPropertiesSet()
方法。<bean />
的 init-method
属性),那么将调用该方法。#postProcessAfterInitialization(Object bean, String beanName)
方法。Spring Bean 的销毁流程如下:
#destroy()
方法。<bean />
的 destroy-method
属性),那么将调用该方法。整体如下图:
无意中,艿艿又翻到一张有趣的整体图,如下图:
只有将 Bean 仅用作另一个 Bean 的属性时,才能将 Bean 声明为内部 Bean。
<property>
或 <constructor-arg>
中提供了 <bean>
元素的使用。例如,假设我们有一个 Student 类,其中引用了 Person 类。这里我们将只创建一个 Person 类实例并在 Student 中使用它。示例代码如下:
// Student.java
|
<!-- bean.xml -->
|
当 Bean 在 Spring 容器中组合在一起时,它被称为装配或 Bean 装配。Spring 容器需要知道需要什么 Bean 以及容器应该如何使用依赖注入来将 Bean 绑定在一起,同时装配 Bean 。
装配,和上文提到的 DI 依赖注入,实际是一个东西。
自动装配有哪些方式?
Spring 容器能够自动装配 Bean 。也就是说,可以通过检查 BeanFactory 的内容让 Spring 自动解析 Bean 的协作者。
自动装配的不同模式:
自动装配有什么局限?
艿艿:这个题目,了解下即可,也不是很准确。
<constructor-arg>
和 <property>
设置指定依赖项,这将覆盖自动装配。基本元数据类型 - 简单属性(如原数据类型,字符串和类)无法自动装配。
这种,严格来说,也不能称为局限。因为可以通过配置文件来解决。
令人困惑的性质 - 总是喜欢使用明确的装配,因为自动装配不太精确。
默认情况下,容器启动之后会将所有作用域为单例的 Bean 都创建好,但是有的业务场景我们并不需要它提前都创建好。此时,我们可以在Bean 中设置 lzay-init = "true"
。
Spring 框架并没有对单例 Bean 进行任何多线程的封装处理。
当然,但实际上,大部分的 Spring Bean 并没有可变的状态(比如Serview 类和 DAO 类),所以在某种程度上说 Spring 的单例 Bean 是线程安全的。
如果你的 Bean 有多种状态的话,就需要自行保证线程安全。最浅显的解决办法,就是将多态 Bean 的作用域( Scope )由 Singleton 变更为 Prototype 。
艿艿说:能回答出这个问题的,一般是比较厉害的。
这是个比较复杂的问题,有能力的胖友,建议看下 《【死磕 Spring】—— IoC 之加载 Bean:创建 Bean(五)之循环依赖处理》
感觉,不通过源码,很难解释清楚这个问题。如果看不懂的胖友,可以在认真看完,在星球里,我们一起多交流下。好玩的。
这块内容,实际写在 「Spring Bean」 中比较合适,考虑到后续的问题,都是关于注解的,所以单独起一个大的章节。
不使用 XML 来描述 Bean 装配,开发人员通过在相关的类,方法或字段声明上使用注解将配置移动到组件类本身。它可以作为 XML 设置的替代方案。例如:
Spring 的 Java 配置是通过使用 @Bean
和 @Configuration
来实现。
@Bean
注解,扮演与 <bean />
元素相同的角色。@Configuration
注解的类,允许通过简单地调用同一个类中的其他 @Bean
方法来定义 Bean 间依赖关系。示例如下:
|
默认情况下,Spring 容器中未打开注解装配。因此,要使用基于注解装配,我们必须通过配置 <context:annotation-config />
元素在 Spring 配置文件中启用它。
当然,如果胖友是使用 Spring Boot ,默认情况下已经开启。
@Component
:它将 Java 类标记为 Bean 。它是任何 Spring 管理组件的通用构造型。@Controller
:它将一个类标记为 Spring Web MVC 控制器。@Service
:此注解是组件注解的特化。它不会对 @Component
注解提供任何其他行为。您可以在服务层类中使用 @Service 而不是 @Component
,因为它以更好的方式指定了意图。@Repository
:这个注解是具有类似用途和功能的 @Component
注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException 。@Required
注解,应用于 Bean 属性 setter 方法。
示例代码如下:
public class Employee {
|
@Autowired
注解,可以更准确地控制应该在何处以及如何进行自动装配。
示例代码如下:
public class EmpAccount {
|
当你创建多个相同类型的 Bean ,并希望仅使用属性装配其中一个 Bean 时,您可以使用 @Qualifier
注解和 @Autowired
通过指定 ID 应该装配哪个确切的 Bean 来消除歧义。
例如,应用中有两个类型为 Employee 的 Bean ID 为 "emp1"
和 "emp2"
,此处,我们希望 EmployeeAccount Bean 注入 "emp1"
对应的 Bean 对象。代码如下:
public class EmployeeAccount {
|
Spring AOP 的面试题中,大多数都是概念题,主要是对切面的理解。概念点主要有:
- AOP
- Aspect
- JoinPoint
- PointCut
- Advice
- Target
- AOP Proxy
- Weaving
非常推荐阅读如下两篇文章:
AOP(Aspect-Oriented Programming),即面向切面编程, 它与 OOP( Object-Oriented Programming, 面向对象编程) 相辅相成, 提供了与 OOP 不同的抽象软件结构的视角。
Aspect 由 PointCut 和 Advice 组成。
AOP 的工作重心在于如何将增强编织目标对象的连接点上, 这里包含两个工作:
可以简单地认为, 使用 @Aspect 注解的类就是切面
JoinPoint ,切点,程序运行中的一些时间点, 例如:
在 Spring AOP 中,JoinPoint 总是方法的执行点。
PointCut ,匹配 JoinPoint 的谓词(a predicate that matches join points)。
简单来说,PointCut 是匹配 JoinPoint 的条件。
Advice => PointCut => JoinPoint
。?? 是不是觉得有点绕,实际场景下,其实也不会弄的这么清楚~~
JoinPoint 和 PointCut 本质上就是两个不同纬度上的东西。
或者,我们在换一种说法:
Advice ,通知。
有哪些类型的 Advice?
@Before
注解标记进行配置。@AfterReturning
注解标记进行配置。@AfterThrowing
注解标记配置时执行。@After
注解标记进行配置。@Around
注解标记进行配置。?? 看起来,是不是和拦截器的执行时间,有几分相似。实际上,用于拦截效果的各种实现,大体都是类似的。
Target ,织入 Advice 的目标对象。目标对象也被称为 Advised Object 。
实现 AOP 的技术,主要分为两大类:
① 静态代理 - 指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强。
类加载时编织(特殊的类加载器实现)。
例如,SkyWalking 基于 Java Agent 机制,配置上 ByteBuddy 库,实现类加载时编织时增强,从而实现链路追踪的透明埋点。
感兴趣的胖友,可以看看 《SkyWalking 源码分析之 JavaAgent 工具 ByteBuddy 的应用》 。
② 动态代理 - 在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。目前 Spring 中使用了两种动态代理库:
那么 Spring 什么时候使用 JDK 动态代理,什么时候使用 CGLIB 呢?
// From 《Spring 源码深度解析》P172
|
或者,我们来换一个解答答案:
Spring AOP 中的动态代理主要有两种方式,
JDK 动态代理
JDK 动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是 InvocationHandler 接口和 Proxy 类。
CGLIB 动态代理
如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。当然,Spring 也支持配置,强制使用 CGLIB 动态代理。
CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final
,那么它是无法使用 CGLIB 做动态代理的。
Weaving ,编织。
在 Spring AOP 中,有两种方式配置 AOP 切面:
目前,主流喜欢使用 注解 方式。胖友可以看看 《彻底征服 Spring AOP 之实战篇》 。
非常推荐阅读如下文章:
事务就是对一系列的数据库操作(比如插入多条数据)进行统一的提交或回滚操作,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作。
这样可以防止出现脏数据,防止数据库数据出现问题。
指的是 ACID ,如下图所示:
目前 Spring 提供两种类型的事务管理:
实际场景下,我们一般使用 Spring Boot + 注解的声明式事务。具体的示例,胖友可以看看 《Spring Boot 事务注解详解》 。
另外,也推荐看看 《Spring 事务管理 - 编程式事务、声明式事务》 一文。
① 首先,我们先明确下,这里数据持久层框架,指的是 Spring JDBC、Hibernate、Spring JPA、MyBatis 等等。
② 然后,Spring 事务的管理,是通过 org.springframework.transaction.PlatformTransactionManager
进行管理,定义如下:
// PlatformTransactionManager.java
|
#getTransaction(TransactionDefinition definition)
方法,根据事务定义 TransactionDefinition ,获得 TransactionStatus 。
#commit(TransactionStatus status)
方法,根据 TransactionStatus 情况,提交事务。
@Transactional
注解的的 A 方法,会调用 @Transactional
注解的的 B 方法。
PlatformTransactionManager#commit(TransactionStatus status)
方法,此处事务是不能、也不会提交的。PlatformTransactionManager#commit(TransactionStatus status)
方法,提交事务。#rollback(TransactionStatus status)
方法,根据 TransactionStatus 情况,回滚事务。
#commit(TransactionStatus status)
方法。③ 再之后,PlatformTransactionManager 有抽象子类 org.springframework.transaction.support.AbstractPlatformTransactionManager
,基于 模板方法模式 ,实现事务整体逻辑的骨架,而抽象 #doCommit(DefaultTransactionStatus status)
、#doRollback(DefaultTransactionStatus status)
等等方法,交由子类类来实现。
前方高能,即将进入关键的 ④ 步骤。
④ 最后,不同的数据持久层框架,会有其对应的 PlatformTransactionManager 实现类,如下图所示:
如下,是一个比较常见的 XML 方式来配置的事务管理器,使用的是 DataSourceTransactionManager 。代码如下:
<!-- 事务管理器 -->
|
?? 是不是很有趣,更多详细的解析,可见如下几篇文章:
做过 Spring 多数据源的胖友,都会有个惨痛的经历,为什么在开启事务的 Service 层的方法中,无法切换数据源呢?因为,在 Spring 的事务管理中,所使用的数据库连接会和当前线程所绑定,即使我们设置了另外一个数据源,使用的还是当前的数据源连接。
另外,多个数据源且需要事务的场景,本身会带来多事务一致性的问题,暂时没有特别好的解决方案。
所以一般一个应用,推荐除非了读写分离所带来的多数据源,其它情况下,建议只有一个数据源。并且,随着微服务日益身形,一个服务对应一个 DB 是比较常见的架构选择。
@Transactional
注解的属性如下:
属性 | 类型 | 描述 |
---|---|---|
value | String | 可选的限定描述符,指定使用的事务管理器 |
propagation | enum: Propagation | 可选的事务传播行为设置 |
isolation | enum: Isolation | 可选的事务隔离级别设置 |
readOnly | boolean | 读写或只读事务,默认读写 |
timeout | int (in seconds granularity) | 事务超时时间设置 |
rollbackFor | Class对象数组,必须继承自Throwable | 导致事务回滚的异常类数组 |
rollbackForClassName | 类名数组,必须继承自Throwable | 导致事务回滚的异常类名字数组 |
noRollbackFor | Class对象数组,必须继承自Throwable | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | 类名数组,必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |
@Transactional
的所有属性默认值即可。具体用法如下:
@Transactional
可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public
方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。@Transactional
注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional
注解应该只被应用到 public
方法上,这是由 Spring AOP 的本质决定的。如果你在 protected
、private
或者默认可见性的方法上使用 @Transactional
注解,这将被忽略,也不会抛出任何异常。这一点,非常需要注意。下面,我们来简单说下源码相关的东西。
@Transactional
注解的属性,会解析成 org.springframework.transaction.TransactionDefinition
对象,即事务定义。TransactionDefinition 代码如下:
public interface TransactionDefinition {
|
@Transactional
注解的 rollbackFor
、rollbackForClassName
、noRollbackFor
、noRollbackForClassName
属性貌似没体现出来?它们提现在 TransactionDefinition 的实现类 RuleBasedTransactionAttribute 中。#getPropagationBehavior()
方法,返回事务的传播行为,该值是个枚举,在下面来说。#getIsolationLevel()
方法,返回事务的隔离级别,该值是个枚举,在下面来说。关于这个问题,涉及的内容会比较多,胖友直接看如下两篇文章:
另外,有一点非常重要,不同数据库对四个隔离级别的支持和实现略有不同。因为我们目前互联网主要使用 MySQL 为主,所以至少要搞懂 MySQL 对隔离级别的支持和实现情况。
在 TransactionDefinition 接口中,定义了“四种”的隔离级别枚举。代码如下:
// TransactionDefinition.java
|
事务的传播行为,指的是当前带有事务配置的方法,需要怎么处理事务。
有一点需要注意,事务的传播级别,并不是数据库事务规范中的名词,而是 Spring 自身所定义的。通过事务的传播级别,Spring 才知道如何处理事务,是创建一个新事务呢,还是继续使用当前的事务。
艿艿的自我吐槽:是不是有种背概念背的想哭
在 TransactionDefinition 接口中,定义了三类七种传播级别。代码如下:
// TransactionDefinition.java
|
PROPAGATION_REQUIRED
传播级别。PROPAGATION_NESTED
是 Spring 所特有的。
PROPAGATION_NESTED
启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。PROPAGATION_NESTED
文字很长,实际我们基本没用过。或者说,去掉基本,我们根本没用过。所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。
在 TransactionDefinition 中以 int
的值来表示超时时间,其单位是秒。
当然,这个属性,貌似我们基本也没用过。
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。
在 TransactionDefinition 中以 boolean
类型来表示该事务是否只读。
回滚规则,定义了哪些异常会导致事务回滚而哪些不会。
注意,事务的回滚规则,并不是数据库事务规范中的名词,而是 Spring 自身所定义的。
艿艿:这个可能不是一个面试题,主要满足下大家的好奇心。
TransactionStatus 接口,记录事务的状态,不仅仅包含事务本身,还包含事务的其它信息。代码如下:
// TransactionStatus.java
|
Object transaction
属性,表示事务对象。#isNewTransaction()
方法,表示是否是新创建的事务。有什么用呢?答案结合 「Spring 事务如何和不同的数据持久层框架做集成?」 问题,我们对 #commit(TransactionStatus status)
方法的解释。通过该方法,我们可以判断,当前事务是否当前方法所创建的,只有创建事务的方法,才能且应该真正的提交事务。从倾向上来说,艿艿比较喜欢注解 + 声明式事务。
艿艿:这块的问题,感觉面试问的不多,至少我很少问。哈哈哈。就当做下了解,万一问了呢。
可能会有胖友说,不是应该还有 Spring JDBC 吗。注意,Spring JDBC 不是 ORM 框架。
Spring 提供了 Spring JDBC 框架,方便我们使用 JDBC 。
对于开发者,只需要使用 JdbcTemplate 类,它提供了很多便利的方法解决诸如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。
没有使用过的胖友,可以看看 《Spring JDBC 访问关系型数据库》 文章。
通过使用 Spring 数据数据访问层,它统一了各个数据持久层框架的不同异常,统一进行提供 org.springframework.dao.DataAccessException
异常及其子类。如下图所示:
艿艿:这个问题很灵异,因为艿艿已经好久不使用 Hibernate 了,所以答案是直接复制的。
我们可以通过两种方式使用 Spring 访问 Hibernate:
艿艿:不过我记得,12 年我用过 Spring JPA 的方式,操作 Hibernate 。具体可参考 《一起来学 SpringBoot 2.x | 第六篇:整合 Spring Data JPA》 。
当然,我们可以再来看一道 《JPA 规范与 ORM 框架之间的关系是怎样的呢?》 。这个问题,我倒是问过面试的候选人,哈哈哈哈。
整理 Spring 面试题的过程中,又把 Spring 的知识点又复习了一遍。我突然有点想念,那本被我翻烂的 《Spring 实战》 。?? 我要买一本原版的!!!
参考与推荐如下文章:
标签:wing href 需要 junit 初始化 att class err 面试问题
原文地址:https://www.cnblogs.com/cxhfuujust/p/10883197.html