面试题五:Spring
Spring IoC
-
什么是IoC?
容器创建Bean对象,将他们装配在一起,配置并且管理它们的完整生命周期。
- Spring容器使用依赖注入来管理组成应用程序的Bean对象;
- 容器通过提供的配置元数据Bean Defination来接收对象进行实例化、配置和组装的指令;
- 配置元数据可以通过XML、注解以及java config的方式提供;
-
什么是依赖注入(DI)?
在依赖注入中,你不必主动、手动创建对象,但是必须描述如何创建他们;
- 不是在代码中直接将组件和服务连接到一起,而是描述配置文件中那些组件需要哪些服务;
- 然后,通过IoC容器将它们装配到一起;
-
IoC和DI的区别?
IoC是更宽泛的概念,DI是具体的实现。
IoC是指对对象的控制权由程序代码本身反转到了外部容器,把对象的创建、初始化、销毁等操作交给Spring容器,由Spring容器控制对象的声明周期;
DI是指在程序运行过程中,如需要调用另一个对象协助时,不需要在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。DI是目前最优秀的解耦方式,DI让Spring的Bean之间通过配置文件的方式交织在一起,而不是硬编码的方式耦合在一起。
-
依赖注入的方式?
- 构造函数注入
- setter注入
- 接口注入
目前咋Spring Framework中使用的是构造函数和setter注入,实际使用中主要以setter为主,两者的比较如下所示:
构造函数注入 setter注入 没有部分注入 支持部分注入 不会覆盖setter属性 会覆盖setter属性 任意修改都会创建一个新实例 修改不会创建新实例 使用于设置很多属性 适用于设置较少属性 -
Spring中有多少种IoC容器?
-
BeanFactory低级容器
就像一个包含bean集合的工厂类,在客户端需要时实例化Bean对象
-
ApplicationContext高级容器
ApplicationContext在BeanFactory的基础上进行了一系列的扩展,并且自动初始化非懒加载的Bean对象,如下所示:
- 国际化
- 事件发布
- 多资源加载
- 系统Environmet相关
- 管理声明周期
- 关闭、释放资源
- 自定义初始化
- 设置beanName的Aware接口
-
两者比较:
BeanFactory ApplicationContext 支持懒加载 立即加载 使用语法显示提供资源对象 自己创建和管理资源对象 不支持国际化 支持国际化 不支持基于依赖的注解 支持基于依赖的注解
-
-
常用的BeanFactory容器?
XmlBeanFactory,根据XML文件中定义的内容,创建对应的Bean。
-
常用的ApplicationContext容器?
- ClassPathXmlApplicationContext
- FileSystemXmlApplicationContext
- XmlWebApplicationContext
- ConfigServletWebApplicationContext(SpringBoot使用)
-
IoC的好处?
- 最小化应用程序中的代码量;
- 最小的影响和最少的侵入实现松耦合;
- 支持即时的实例化和延迟加载Bean对象;
- 应用程序易于测试,因为不需要单元测试用例中的任何单例或者JNDI查找机制;
-
IoC的实现机制?
简单来说就是工厂模式+反射机制
如果仅仅是IoC,只使用低级容器就可以完成IoC功能,步骤如下:
- 加载配置文件,解析成Bean Defination放到一个Map中;
- 调用
getBean
时,从Bean Defination对应的Map里,拿出Class对象进行实例化,如果有依赖关系,就递归调用getBean
方法完成依赖注入。
对于高级容器来说,它包含了低级容器的功能,当执行其
refresh
方法时,会回调低级容器的refreshBeanFactory
方法使低级容器加载所有BeanDefinition到容器中,低级容器加载成功后,高级容器开始处理一些回调,比如bean后置处理器、注册监听器、发布事件、实例化单例Bean等等功能。 -
Spring框架中有哪些不同类型的事件?
Spring提供的5种标准事件:
- 上下文更新事件:该事件会在ApplicationContext 被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的
#refresh()
方法时被触发; - 上下文开始事件:当容器调用ConfigurableApplicationContext 的
#start()
方法开始/重新开始容器时触发该事件; - 上下文停止事件:当容器调用 ConfigurableApplicationContext 的
#stop()
方法停止容器时触发该事件; - 上下文关闭事件:当ApplicationContext 被关闭时触发该事件。容器被关闭时,其管理的所有单例 Bean 都被销毁;
- 请求处理事件: 在 We b应用中,当一个HTTP 请求(request)结束触发该事件。
实现监听事件的两种方式:
-
实现ApplicationListener接口
public class AllApplicationEventListener implements ApplicationListener<ApplicationEvent> { @Override public void onApplicationEvent(ApplicationEvent applicationEvent) { // process event } }
-
使用@EventListener注解
@Configuration public class Config { @EventListener(classes={ApplicationEvent.class}) public void listen(ApplicationEvent event){ System.out.println("事件触发:"+event.getClass().getName()); } }
- 上下文更新事件:该事件会在ApplicationContext 被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的
-
Spring中如何自定义事件?
-
扩展ApplicationEvent自定义事件
public class CustomApplicationEvent extends ApplicationEvent{ public CustomApplicationEvent(Object source, final String msg) { super(source); } }
-
创建一个监听器,监听自定义的事件
public class CustomEventListener implements ApplicationListener<CustomApplicationEvent> { @Override public void onApplicationEvent(CustomApplicationEvent applicationEvent) { // handle event } }
-
发布自定义事件
// 创建 CustomApplicationEvent 事件 CustomApplicationEvent customEvent = new CustomApplicationEvent(applicationContext, "Test message"); // 发布事件 applicationContext.publishEvent(customEvent);
-
Spring Bean
-
什么是Spring Bean?
Bean由IoC容器实例化、配置、装配和管理;
Bean由用户提供给IoC容器的配置元数据Bean Definition创建;
-
Spring有哪些配置方式?
三种方式:
-
XML方式
<bean id="studentBean" class="org.edureka.firstSpring.StudentBean"> <property name="name" value="Edureka"></property> </bean>
-
java注解方式
默认情况下,注解方式是不启用的,需要在配置文件中打开启用它。
<beans> <context:annotation-config/> <!-- bean definitions go here --> </beans>
-
java config方式
@Configuration public class StudentConfig { @Bean public StudentBean myStudent() { return new StudentBean(); } }
-
-
Spring支持几种Bean Scope?
支持5种scope,最常用的是singleton和prototype,只用当用户使用支持web的ApplicationContext时后面三种才有效。
- singleton
- prototype
- session
- request
- application
-
Spring Bean在容器中的生命周期?
-
bean的初始化流程
-
1.1 实例化bean对象
Spring容器根据配置的Bean Defination(可以通过XML、java注解或者java config提供)实例化bean对象;
-
1.2 Aware相关的属性注入到bean对象
- 如果bean实现BeanNameAware接口,则工厂通过传递bean的beanName来调用
setBeanName(String name)
方法; - 如果bean事项BeanFactoryAware接口,则工厂通过传递自身的实例来调用
setBeanFactory(BeanFactory beanFactory)
方法;
- 如果bean实现BeanNameAware接口,则工厂通过传递bean的beanName来调用
-
1.3 调用相应的方法,进一步初始化bean对象
- 如果存在与bean管理的BeanPostProcessor们,则调用
preProcessBeforeInitialization(Object bean,String beanName)
方法; - 如果Bean实现InitializingBean接口,则会调用
afterPropertiesSet()
方法; - 如果bean指定了init方法,那么就会执行该方法;
- 如果存在与bean管理的BeanPostProcessor们,则调用
postProcessAfterInitialization(Object bean,String beanName)
方法。
- 如果存在与bean管理的BeanPostProcessor们,则调用
-
-
bean的销毁流程
- 2.1 如果Bean实现了DisposableBean接口,那么当spring容器关闭时,会调用
destroy()
方法; - 2.2 如果为bean指定了destroy方法,那么将调用该方法;
- 2.1 如果Bean实现了DisposableBean接口,那么当spring容器关闭时,会调用
-
流程图
-
-
Spring的内部Bean?
只有将Bean用作另一个Bean的属性时,才能将Bean声明为内部Bean。
- 内部Bean总是匿名的,并且总是作为原型prototype。
- 为了定义 Bean,Spring 提供基于 XML 的配置元数据在
<property>
或<constructor-arg>
中提供了<bean>
元素的使用
// Student.java public class Student { private Person person; // ... Setters and Getters } // Person.java public class Person { private String name; private String address; // ... Setters and Getters }
<!-- bean.xml --> <bean id=“StudentBean" class="com.edureka.Student"> <property name="person"> <!--This is inner bean --> <bean class="com.edureka.Person"> <property name="name" value=“Scott"></property> <property name="address" value=“Bangalore"></property> </bean> </property> </bean>
-
什么是Spring装配?
当Bean在spring容器中组合到一起时,被称为装配或者Bean装配。
Bean装配和上文的DI是一个东西。
-
自动装配有哪些方式?
- no:默认设置,表示没有自动装配,需要使用显示Bean引用进行装配;
- byName:根据Bean的名称注入依赖项。
- byType:根据Bean的类型注入依赖项;
- 构造函数:通过调用类的构造函数来注入依赖项,支持大量的参数。
- autodetect:首先容器尝试通过构造函数使用autowire装配,如果不能,则通过byType自动装配。
-
自动装配的局限性?
- 覆盖的可能性;
- 基本元数据类型只能通过配置文件装配;
- 总是使用明确的装配,自动装配不够精确;
-
什么是延迟加载?
默认情况下,容器启动时会将作用域为单例的Bean都创建好,但是某些情况下我们不需要它提前创建好,此时可以在Bean的配置中设置
lazy-ini=true
- 此时,容器启动时,作用域为单例的bean就不再创建
- 只有在获得该bean时才会被创建
-
单例bean是线程安全的吗?
Spring框架并没有对单例Bean做多线程的封装处理,因此严格来说单例Bean不是线程安全的,并发问题和安全性需要使用者自行处理。因为这个本来也不是spring的管理范围,spring只需要根据配置创建单例bean或者多例bean。
但是大部分情况下,spring Bean都没有可变的状态,例如service或者dao,因此在某种程度上来说,单例bean是线程安全的。
如果bean有多种状态,就需要自行保证线程安全,最简单的办法就是多态bean的作用域由singleton改为prototype。
-
怎么解决循环依赖问题?
Spring循环依赖的场景有2种:
1.构造器的循环依赖;
2.field属性的循环依赖;
对于构造器的循环依赖,spring无法解决,直接抛出BeanCurrentlyInCreationException 表示无法处理
spring只解决作用域为singleton的循环依赖,作用域是prototype的bean,无法解决循环依赖,直接抛出BeanCurrentlyInCreationException 异常
解决循环依赖的方法是依赖三级缓存:
// DefaultSingletonBeanRegistry.java /** * Cache of singleton objects: bean name to bean instance. * * 一级缓存,存放的是单例 bean 的映射。 * * 注意,这里的 bean 是已经创建完成的。 * * 对应关系为 bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** * Cache of early singleton objects: bean name to bean instance. * * 二级缓存,存放的是早期半成品(未初始化完)的 bean,对应关系也是 bean name --> bean instance。 * * 它与 {@link #singletonObjects} 区别在于, 它自己存放的 bean 不一定是完整。 * * 这个 Map 也是【循环依赖】的关键所在。 */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); /** * Cache of singleton factories: bean name to ObjectFactory. * * 三级缓存,存放的是 ObjectFactory,可以理解为创建早期半成品(未初始化完)的 bean 的 factory ,最终添加到二级缓存 {@link #earlySingletonObjects} 中 * * 对应关系是 bean name --> ObjectFactory * * 这个 Map 也是【循环依赖】的关键所在。 */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
ABC三个Bean循环依赖的解决步骤:
- 首先 A 完成初始化第一步并将自己提前曝光出来(通过 ObjectFactory 将自己提前曝光),在初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建出来;
- 然后 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来;
- 这个时候 C 又开始初始化进程,但是在初始化的过程中发现自己依赖 A,于是尝试 get(A),这个时候由于 A 已经添加至缓存中(一般都是添加至三级缓存
singletonFactories
),通过 ObjectFactory 提前曝光,所以可以通过ObjectFactory#getObject()
方法来拿到 A 对象,C 拿到 A 对象后顺利完成初始化,然后将自己添加到一级缓存中; - 回到 B ,B 也可以拿到 C 对象,完成初始化,A 可以顺利拿到 B 完成初始化。到这里整个链路就已经完成了初始化过程了
-
解决循环依赖时为什么需要二级缓存?为什么需要三级缓存?
二级缓存用来保存未实例化完成的半成品bean,半成品的bean是创建完成,但是未注入属性和初始化,因此二级缓存最主要的作用就是缓存三级缓存中的执行结果,提前曝光单例Bean对象。
三级缓存是为了解决在spring循环依赖的情况下Bean存在动态代理的情况,虽然直接去掉三级缓存直接使用二级缓存也可以,但是这就意味着bean在构建完成后就创建了代理,这样违背了spring设计原则。
Spring结合AOP跟Bean的生命周期,是在
Bean创建完全
之后通过AnnotationAwareAspectJAutoProxyCreator
这个后置处理器来完成的,在这个后置处理的postProcessAfterInitialization
方法中对初始化后的Bean完成AOP代理。如果出现了循环依赖
,那没有办法,只有给Bean先创建代理,但是没有出现循环依赖的情况下,设计之初就是让Bean在生命周期的最后一步完成代理而不是在实例化后就立马完成代理
Spring注解
-
什么是基于注解的容器配置?
不适用XML来描述bean装配,开发人员通过在相关的类、方法或者字段上添加相应的注解将配置移动到组件类本身。
Spring的Java配置是通过
@Bean
和@Configuration
两个注解实现的:@Bean注解,作用和
作用一致; @Configuration
注解的类,允许通过简单的调用同一个类中的使用@Bean
注解标注的其他方法来定义bean之间的关系。 -
如何启动注解装配?
Spring中默认注解装配是关闭的,需要在Spring的配置文件中开启,开启代码:
<context:annotation-config />
如果使用的是SpringBoot,该功能是默认开启的
-
@Component、@Controller、@Service、@Repository区别?
- @Component,将java类标记为bean,是Spring管理组件的通用构造型;
- @Controller,将java类标记为Spring Web MVC的一个控制器;
- @Service,本质上和@Component一样,都是作为Spring的一个组件,但是在业务层中使用@Service而不是@Component是因为更清晰的指明了使用意图;
- @Repository,本质上和@Component一样,都是作为Spring的一个组件,但是该注解将DAO导入到IoC容器中,使得未经检查的异常有资格转换为Spring DataAccessException。
-
@Required注解作用?
该注解主要用在setter方法上,表示该setter方法的属性值必须在配置时注入值,否则会抛出
BeanInitializationException
异常。 -
@Autowired注解作用?
默认情况下,@Autowired注解是类型驱动的注入;
@Autowired注解可以标注到setter方法,构造方法,具有任意名称的属性或者多个参数的方法上自动装配Bean;
总结来说就是,@Autowired注解可以更准确的控制在何处以及如何进行自动装配;
-
@Qualifier注解作用?
@Autowired注解默认是类型驱动的注入,但是同一类型的bean存在多个时,使用@Qualifier注解指定确切的bean以消除歧义。
public class EmployeeAccount { @Autowired @Qualifier(emp1) private Employee emp; }
-
@Autowired、@Resource、@Inject注解区别?
-
@Autowired
- 1.1 Spring引入的注解,使用时需要导入spring相关的包;
- 1.2 可以使用的位置:构造器、setter方法、方法参数、变量域和注解上面;
- 1.3 Spring容器解析@Autowired注解时,使用的是
AutowiredAnnotationBeanPostProcessor
后置处理器; - 1.4 @Autowired注解有一个required属性,其他两个都没有;
- 1.5 默认优先按照类型去容器中查找对应的组件,如果在容器中找不到,且required=true,那么就会出现异常;
- 1.6 支持
@Primary
注解,让Spring进行bean的装配时,默认使用首选的bean;
-
@Resource
-
2.1 JDK自带的注解,不需要引入额外的jar包;
-
2.2 可以使用的位置:TYPE表示标注在接口、类、枚举上,Field变量域以及METHOD方法上
@Target({TYPE, FIELD, METHOD}) @Retention(RUNTIME) public @interface Resource { String name() default ""; String lookup() default ""; Class<?> type() default java.lang.Object.class; enum AuthenticationType { CONTAINER, APPLICATION } AuthenticationType authenticationType() default AuthenticationType.CONTAINER; boolean shareable() default true; String mappedName() default ""; String description() default ""; }
-
2.3 Spring容器解析@Resource注解时,使用的是
CommonAnnotationBeanPostProcessor
后置处理器; -
2.4 默认优先按照组件名称去容器中查找对应的组件;
-
-
@Inject
- 3.1 JSR330规范提供的注解,需要导入javax.inject包才能使用;
- 3.2 可以标注的位置:方法、构造器和变量域;
- 3.3 Spring容器解析@Inject注解时,使用的是
AutowiredAnnotationBeanPostProcessor
后置处理器,与@Autowired注解一样; - 3.4 由于@Inject注解没有属性,因此在加载所需bean失败时,会报错。
-
Spring AOP
-
什么是AOP?
AOP即面向切面编程,它与OOP相辅相成,提供了与OOP不同的抽象软件结构的视角。
在OOP中,以类为基本单元;
在AOP中,以切面(Aspect)为基本单元;
-
什么是Aspect?
可以简单的认为带有
@Aspect
注解的类就是切面。Aspect由PointCut和Advice组成。
- 既包含了横切逻辑的定义,也包含了连接点的定义;
- Spring AOP就是用来负责实施切面的框架,它将切面所定义的横切逻辑编织到切面所指定的连接点中;
AOP的工作重心在于如何将增强织入目标对象的连接点上,这里包含两项工作:
- 如何通过
advice
和pointcut
定位到特定的joinpoint
上; - 如何在
advice
中编写切面代码;
-
什么是JoinPoint?
JoinPoint
是程序运行中的一些时间点,可以是一个方法的执行,也可以是一个异常的处理。在Spring AOP中,
JoinPoint
总是方法的执行点。 -
什么是Pointcut?
个人理解:一组寻找JoinPoint的规则。
简单来说,
Pointcut
是匹配JoinPoint
的条件。advice
和特定的Pointcut
关联,并且在Pointcut
相匹配的JoinPoint
中执行;- 在Spring中,所有的方法都可以认为是一个
JoinPoint
,但是我们并不想在所有的方法中都执行advice
。而Pointcut
的作用就是提供一组规则(使用AspectJ Pointcut expression Language来描述)来匹配JoinPoint
,给满足规则的JoinPoint
执行advice
;
-
JoinPoint和Pointcut的区别?
个人理解:
Pointcut
是条件,JoinPoint
是结果,根据Pointcut
提供的规则匹配到相应的JoinPoint
,从而执行advice
。Pointcut
和JoinPoint
本质上是不同维度的东西。- 在Spring AOP中,所有的方法执行都是
JoinPoint
,而Pointcut
是一个描述信息,它修饰的是JoinPoint
,通过Pointcut
,我们可以知道哪些JoinPoint
可以被织入advice
; advice
是在JoinPoint
上执行的,而Pointcut
规定了哪些JoinPoint
可以执行哪些advice
;
- 在Spring AOP中,所有的方法执行都是
-
什么是Advice ?
- 特定
JoinPoint
处的Aspect所采取的动作称为advice
; - Spring AOP将
advice
模拟成一个拦截器,并且在JoinPoint
上维护多个advice
进行层层拦截;
- 特定
-
有哪些类型的Advice ?
- Before - 使用
@Before
注解标注,advice
在JoinPoint
方法执行之前执行; - After Finally - 使用
@After
注解标注,advice
在JoinPoint
方法之后执行,不论JoinPoint
方法是正常退出还是异常退出都会执行; - After Returning - 使用
@AfterReturning
注解标注,advice
在JoinPoint
方法正常执行后执行; - After Throwing - 使用
@AfterThrowing
注解标注,advice
在JoinPoint
方法抛出异常退出时执行; - Around - 使用
@Around
注解标注,advice
在JoinPoint
方法之前之后执行;
- Before - 使用
-
什么是Target ?
target是目标对象,也就是织入
advice
的目标对象,目标对象也被称为Adviced Object。因为Spring AOP使用运行时动态代理的方式来实现Aspect,因此Adviced Object总是一个代理对象。
advice + Target Object = Adviced Object = Proxy
-
AOP的实现方式?
AOP的实现技术主要分为两类,分别是静态代理和动态代理:
静态代理:
- 编译时编织 - 特殊编译器实现
- 类加载时编织 - 特殊类加载器实现
动态代理:
-
JDK动态代理
JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。
JDK动态代理的核心是
InvocationHandler
接口和Proxy
类 -
CGlib
如果目标类没有实现任何接口,那么Spring AOP就使用CGlib来实现动态代理,当然也支持配置强制使用CGlib。
但是有一点要注意,CGlib使用继承的方式做的动态代理,如果一个类使用了
final
关键字,是不能使用CGlib做动态代理的。
-
Spring AOP and AspectJ AOP 有什么区别?
- 实现方式不同
- 1.1 Spring AOP使用动态代理实现;
- 1.2 AspectJ AOP使用静态代理实现;
- 支持范围不同
- 2.1 Spring AOP只支持方法级别的
PointCut
; - 2.2 AspectJ AOP 提供了完全的AOP支持,不仅支持方法级别的
PointCut
还支持属性级别的PointCut
;
- 2.1 Spring AOP只支持方法级别的
- 实现方式不同
-
什么是编织(Weaving)?
在Spring AOP中,编织在运行时执行,也就是动态代理;
为了创建一个Advice Object而链接一个
aspect
和其他应用类型或对象,这个过程被称为编织。 -
如何使用 AOP 切面?
-
基于XML方式的切面实现;
-
基于注解的切面实现;
目前主要使用注解的方式实现AOP切面。
-
Spring事务
-
什么是事务?
事务就是对一系列的数据库操作,例如插入、删除或者修改,进行统一的提交或者回滚操作,成功则全部成功,失败则全部回滚。
-
事务的特性?
事务四大特性:
-
原子性(A)
一个事务中的所有操作,要么全部完成,要么全部不完成,不会存在中间状态;
-
一致性(C)
在事务开始之前和事务结束之后,数据库的完整性没有遭到破坏;
-
隔离性(I)
数据库允许并发事务同时对其数据新增或者修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
-
持久性(D)
事务结束以后,对数据的修改就是永久性的,即使系统故障也不会丢失。
-
-
Spring支持的事务管理类型?
-
声明式事务
通过使用XML或者注解的方式配置的事务,从而使事务管理和业务代码进行分离;
-
编程式事务
通过编码的方式实现事务管理,需要在代码中显示的调用事务的获得、提交以及回滚,虽然编码灵活性较高,但是后期的维护成本较大。
目前使用SpringBoot的大环境下,一般使用基于注解的声明式事务。
-
-
Spring事务如何和不同的数据持久层框架做集成?
-
Spring事务的管理,是通过
org.springframework.transaction.PlatformTransactionManager
接口实现的// PlatformTransactionManager.java public interface PlatformTransactionManager { // 根据事务定义 TransactionDefinition ,获得 TransactionStatus 。 TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; // 根据情况,提交事务 void commit(TransactionStatus status) throws TransactionException; // 根据情况,回滚事务 void rollback(TransactionStatus status) throws TransactionException; }
getTransaction
为什么不是创建事务呢?因为如果当前已经有事务,则不会进行创建,一般会跟当前线程绑定,如果不存在事务则创建新的事务;- 为什么返回的是
TransactionStatus
对象?在TransactionStatus
中不仅包含事务属性,还包含事务的其他信息,如是否只读,是否为新创建的事务等。该接口用来记录事务的状态,该接口定义的一组方法,用来判断或者获取事务的相应的状态信息 TransactionDefinition
是什么?这个类定义了事务的一些基本属性,包含5个方面,分别是隔离级别、传播行为、回滚规则、是否只读、事务超时
-
PlatformTransactionManager
接口有抽象实现类org.springframework.transaction.support.AbstractPlatformTransactionManager
,基于模板方法模式,实现整体事务逻辑的骨架,而抽象doCommit(DefaultTransactionStatus status)
、doRollback(DefaultTransactionStatus status)
等方法交给子类来实现。 -
不同的持久层框架会有其对应的
PlatformTransactionManager
实现类,所有实现类都继承AbstractPlatformTransactionManager
这个骨架类
-
-
为什么在事务中不能切换数据源?
在Spring的事务管理中,所使用的数据库连接会和当前线程绑定,因此即使我们设置了额外的数据源,使用的还是当前的数据源连接。
-
@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方法将都具有该类型的事务属性,可以通过在方法上标注该注解覆盖类上的事务属性;
- Spring不建议在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时才会生效;
@Transactional
只能用在public方法上,这是AOP的本质决定的,当标注在private或者protected方法上时,不生效,但是也不抛出异常;
-
什么是事务的隔离级别?有哪些隔离级别?
// TransactionDefinition.java /** * 【Spring 独有】使用后端数据库默认的隔离级别 * * MySQL 默认采用的 REPEATABLE_READ隔离级别 * Oracle 默认采用的 READ_COMMITTED隔离级别 */ int ISOLATION_DEFAULT = -1; /** * 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 */ int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED; /** * 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 */ int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; /** * 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 */ int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; /** * 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。 * * 但是这将严重影响程序的性能。通常情况下也不会用到该级别。 */ int ISOLATION_SERIALIZABLE = Connecton.TRANSACTION_SERIALIZABLE;
-
什么是事务的传播级别?有哪些传播级别?
事务的传播行为是指带当前带有事务配置的方法,需要怎么处理事务。
在
TransactionDefinition
接口中,定义了三类七种事务// TransactionDefinition.java // ========== 支持当前事务的情况 ========== /** * 如果当前存在事务,则使用该事务。 * 如果当前没有事务,则创建一个新的事务。 */ int PROPAGATION_REQUIRED = 0; /** * 如果当前存在事务,则使用该事务。 * 如果当前没有事务,则以非事务的方式继续运行。 */ int PROPAGATION_SUPPORTS = 1; /** * 如果当前存在事务,则使用该事务。 * 如果当前没有事务,则抛出异常。 */ int PROPAGATION_MANDATORY = 2; // ========== 不支持当前事务的情况 ========== /** * 创建一个新的事务。 * 如果当前存在事务,则把当前事务挂起。 */ int PROPAGATION_REQUIRES_NEW = 3; /** * 以非事务方式运行。 * 如果当前存在事务,则把当前事务挂起。 */ int PROPAGATION_NOT_SUPPORTED = 4; /** * 以非事务方式运行。 * 如果当前存在事务,则抛出异常。 */ int PROPAGATION_NEVER = 5; // ========== 其他情况 ========== /** * 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。 * 如果当前没有事务,则等价于 {@link TransactionDefinition#PROPAGATION_REQUIRED} */ int PROPAGATION_NESTED = 6;
对于绝大多数场景,使用
PROPAGATION_REQUIRED
就可以了。 -
什么是事务的超时属性?
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制事务还没有完成,则自动回滚事务。
-
什么是事务的只读属性?
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。
- 所谓事务性资源就是指那些被事务管理的资源,比如数据源、JMS 资源,以及自定义的事务性资源等等;
- 如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能;
-
什么是事务的回滚规则?
回滚规则,定义了哪些异常会导致事务回滚而哪些不会。
- 默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚;
- 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
-
使用Spring事务有哪些优点?
- 通过 PlatformTransactionManager ,为不同的数据层持久框架提供统一的 API ,无需关心到底是原生 JDBC、Spring JDBC、JPA、Hibernate 还是 MyBatis 。
- 通过使用声明式事务,使业务代码和事务管理的逻辑分离,更加清晰。
-
举例说明什么情况下事务不生效?
- @Transactional 应用在非 public 修饰的方法上;
- @Transactional 注解属性 propagation 设置错误;
- @Transactional 注解属性 rollbackFor 设置错误;
- 同一个类中方法调用,导致@Transactional失效;
- 异常被你的 catch“吃了”导致@Transactional失效;
- 数据库引擎不支持事务