Spring面试题目
–
1、Spring是如何简化开发的?
简化开发 |
---|
① 基于POJO的轻量级和最小侵入性编程 |
② 通过依赖注入和面向接口实现松耦合 |
③ 基于切面和惯列进行声明式编程 |
④ 通过切面和模板减少样板式代码 |
2、谈一谈Spring的理解/Spring的优势?
步骤 | 描述 |
---|---|
总 | ① Spring是一个 框架 ,同时它为我们提供了一个 容器 ,用于装载Bean对象。之前我们需要自己去new,而现在对象的创建、销毁均由容器控制,并帮助我们维护对象的整体生命周期。② 同时,在Spring是一个** 基石 **,例如SpringBoot、SpringCloud等等框架,这些框架都是以Spring框架作为【基石】进行扩展开发的。③ 最后,Spring还是一个 生态 。有一个构建在Spring之上的庞大生态圈,它将Spring扩展到不同的领域,如Web服务、移动开发以及NoSQL等领域。 |
分 | Spring中有两个非常重要的特性,分别是 IOC(控制反转) 和 AOP(面向切面编程) |
特性 | 承接上边的分 |
---|---|
IOC | ① IOC(控制反转)是一种设计思想。 在传统的正转中,我们需要手动的new对象为其设置属性依赖 。② 而控制反转后, 对象的创建、销毁以及生命周期的维护 均由IOC容器替我们控制 。 创建过程中,容器可以帮我们查找并注入依赖对象,对象只是被动的接受依赖对象。不需要我们再去主动的创建依赖。③ IOC主要是通过依赖注入(DI)实现的,它是基于 依赖倒置原则 设计的一种思想。所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的"控制反转"。 当我们在new目标对象时,它会从最上层开始向下寻找依赖关系,到达最底层后,将底层类作为参数传入上层类,向上递归的new依赖,直到目标对象被创建出来。④ 同时IOC还是一个容器 。它是一个存储所有单例Bean对象的Map集合(SingletonObjects),此Map集合被称为IOC容器。 实际上,Spring中有三个Map结构,也就是三级缓存,它主要是用于解决循环依赖问题的。 - Bean对象: 交给Spring容器管理的类被称为Bean对象。Bean对象的创建、销毁均由Spring容器进行控制; |
AOP | ① AOP(面向切面编程) 是IOC的一个扩展功能。它是在Bean的生命周期中对象初始化完成后,BeanPostProcessor的后置处理方法 阶段实现的。 ② AOP的作用主要是 解耦! 它可以将一些如日志、事务、权限等非核心业务逻辑的代码抽取出来作为一个切面,在运行时通过 动态代理技术 切入到目标方法(连接点)中去,可以在不修改核心业务逻辑的前提下,织入额外的处理逻辑,达到扩展功能的目的 。③ AOP主要是由 切面 (织入的额外逻辑) + 切入点 (被增强的方法) + 通知 (增强的位置)组成。它的原理是基于 Jdk动态代理(基于反射 + 公共接口 + 处理类) 或者 Cglib动态代理(基于ASM字节码生成框架 + 反射 + 子类继承拦截父类方法调用) 实现的,其中Jdk是默认的代理方式,当代理的对象不是接口而是某个类时,Spring就默认切换为Cglib动态代理,当然也可以强制的直接使用Cglib动态代理。 |
引申知识点:Bean的生命周期、Spring的循环依赖、IOC容器的创建过程(底层实现)
3、谈一谈对IOC的理解?
-
注意: 针对IOC问题,一定要说明两点:IOC是控制反转,同时也是个容器。
概念 IOC定义 lOC 控制反转
,它是一种设计思想,IOC是将你设计好的对象交给Spring控制(IOC容器),而不是传统的我们手动控制控制反转 ① 正转: 在传统的应用程序中,我们是在对象中 主动new
操作来获取依赖对象。
② 反转: 是由Spring容器来帮忙创建及注入依赖对象
,在这个过程中,我们只需要配置好配置文件或者注解,即可由容器帮我们查找并注入依赖对象,对象只是被动的接受依赖对象
。不需要我们再去主动的创建依赖。实现原理 ① 控制反转是由 依赖注入 (DI)
实现的,是基于依赖倒置原则
设计的一种思想。所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的"控制反转"。
② 当我们在new目标对象时,它会从最上层开始向下寻找依赖关系
,到达最底层后,`将底层类作为参数传入上层类,向上递归的new依赖,直到目标对象被创建出来。IOC容器 ① IOC容器是一个 存储所有Bean的Map集合
,此Map集合被称为IOC容器
。在Spring中有三级map缓存结构,用于解决循环依赖问题
② SingletonObjects存放完整的Bean对象。 - Bean对象: 交给Spring容器管理的类被称为Bean对象。Bean对象的创建、销毁均由Spring容器进行控制。 -
举例说明应用IOC的优点:
优点 描述 无需知道new依赖的细节
在实际项目中,我们需要创建的目标类有成千上百个内部类,之前我们需要一个个的先把内部类创建出来,然后在设置给目标类。这样做太麻烦了,而控制反转IOC即可帮我们解决这个问题,直接一步到位的创建出目标类。 当底层类需要修改时,只需要修改底层类,上层类不用修改
在实际项目中,有时候我们需要去修改一个类的构造器,比如这个构造器新加了一个属性形参。那么没有控制反转IOC之前,当我们改了这一个类的构造器函数。那么所有依赖于这个类的其它类都需要修改。而控制反转后,由于我们是将底层类作为参数向上层类传递的,因此此时若更改,我们只需要修改这一个类的构造器即可,不需要修改其它类。
1)重中之重!:IOC容器的创建过程(底层实现)/ 或者说 Spring的生命周期?
-
底层实现: Map数据结构 + 工厂模式 + 依赖注入(DI) + 反射
-
实现IOC容器的步骤:
阶段 执行(背诵) 延伸点 Step1 通过 createBeanFactory()
创建BeanFactory工厂类容器
用于存放BeanDefinition、Bean等对象 Step2 向BeanFactory中添加一些参数信息,提供扩展点
例如,BeanPostProcessor、Aware接口的子类等属性
Step3 将 xml
或者注解
解析成BeanDefinition对象
(封装类的相关信息)BeanDefinition存储的有: @Scope、@Lazy、@DependsOn、属性等信息 Step4 BeanFactoryPostProcessor的后置处理
,处理后才算得到完整的BeanDefinition对象
工厂类的扩展点,例如对 占位符处理${jdbc.name}
等Step5 注册扩展接口BeanPostProcessor
,方便后Bean初始化的扩展
AOP Step6 Bean的创建—使用—销毁
过程Bean的生命周期
2)重点!Bean创建过程(Bean的生命周期):
-
创建Bean对象
阶段 执行(背诵) 延伸点 Step1 调用 createBeanInstance()
,基于反射将BeanDefinition实例化Bean对象
反射 Step2 调用 populateBean()
,完成自定义属性的依赖注入循环依赖(三级缓存
)Step3 调用 invokeAwareMethod()
,完成容器属性的依赖注入
,例如BeanName、BeanFactory、BeanClassLoaderAware 是BeanFactory初始化时设置的 Step4 调用 BeanPostProcessor() 前置处理方法
,完成对象初始化前的相关处理工作,例如ApplicationContextPostPrecessor(),用于设置ApplicationContext、Enviroment、ResourceLoader等对象BeanPostProcessor() 是BeanFactory初始化时设置的,扩展点 Step5 检查是否实现 initializingBean接口
。如果有,则调用afterPropertiesSet()
完成属性设置后的工作扩展点 Step6 调用 invokeInitmethod()
完成对象初始化
的相关工作初始化 Step7 调用 BeanPostProcessor() 后置处理方法
,完成对象初始化后的扩展工作AOP
是在此阶段实现的Step8 注册必要的 Destruction回调接口
,给Bean对象提供销毁方法
销毁接口 Step9 调用 getBean()
从IOC容器中获取对象使用Bean Step10 如果实现了DispoableBean接口,则调用 destroyMethod()
方法销毁。否则调用自定义销毁方法
销毁操作 面试官,这就是我对IOC的整体理解,包含了一些详细的处理过程,您看一下还有什么问题,可以指点我以下。
Bean生命周期的流程图:
4、谈一谈对AOP的理解
AOP | 描述 |
---|---|
概念 | AOP 是IOC的一个扩展功能 。它是在Bean的生命周期中对象初始化之后,BeanPostProcessor的后置处理方法阶段实现 |
代理对象组成 | AOP(面向切面编程) = 切面(织入的额外逻辑) + 切入点(被增强的方法) + 通知(增强的位置) |
原理 | ① 基于Jdk动态代理 :基于反射 + 公共接口 + 处理类 ② Cglib动态代理 :基于ASM字节码生成框架 + 反射 + 子类继承拦截父类方法调用 |
作用 | 解耦! 将一些如日志、事务、权限等非核心业务逻辑的代码抽取出来作为一个切面,在运行时通过动态代理技术切入到目标方法(连接点)中去,可以在不修改原目标对象的前提下,织入额外的逻辑,扩展目标对象的功能。 |
应用场景 | 处理日志、Spring事务(可举例)、权限 |
- AOP代理过程:
步骤 | 描述 |
---|---|
Step1 | 代理对象的创建前,明确 (通知 + 切面 + 接入点) |
Step2 | 通过Jdk(默认,有接口时使用) 或者 Cglib动态代理(代理对象没有公共接口时用) 的方式来实现AOP代理 |
Step3 | 在执行代理时,会调用InvaocationHandle; |
Step4 | 从拦截器中依次获取每一个通知开始执行切入; |
1)AOP应用举例:Spring声明式事务的实现步骤
阶段 | 执行(背诵) |
---|---|
Step1 | 配置数据源DataSource 。设置 事务管理器 为 数据库事务管理器 |
Step2 | 配置 "切面"+ "通知advice" + ''切点表达式'' ,以及事务传播机制 和隔离级别 |
Step3 | AOP事务采用的实际是 环绕通知 ,通过注解@Transcational 实现的 具体步骤如下: |
Step4 | 前置通知@Before :获取数据库连接,并关闭事务自动提交功能 |
Step5 | 后置通知@After :执行过程中无异常,则提交事务,并关闭数据库连接。提交的具体逻辑是通过doCommit()实现的。 |
Step6 | 异常通知@AfterThrowing ,操作失败则事务回滚,回滚的具体逻辑是通过doRollBack()方法实现。 |
Step7 | 当事务执行完毕,清除相关的事务信息 。至此,事务结束 |
- Spring事务源码(简略)
public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; try { //1、前置通知@Before:获取数据库连接,并关闭事务自动提交功能 conn = JDBCUtil.getConnection(); conn.setAutoCommit(false); //2、预编译SQL并执行:此处表示接入点(被代理的方法) ps = conn.prepareStatement("update bank set money = money - 100 where id = 1"); ps.executeUpdate(); //3、后置通知@After:无异常,手动提交事务 conn.commit(); } catch (Exception e) { e.printStackTrace(); try { //5、异常通知@AfterThrowing,事务回滚 conn.rollback(); } catch (SQLException throwables) { throwables.printStackTrace(); } } finally { try { //6、后置通知@After:关闭数据库连接 JDBCUtil.close(ps, conn); } catch (SQLException e) { e.printStackTrace(); } } }
2)Spring对AOP的支持
-
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
1) 默认使用Jdk动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
2) 当需要代理的类不是代理接口的时候,Spring会切换为使用Cglib代理,也可强制使用Cglib
AOP编程只需要参与三个部分:
AOP代理编程 | 描述 |
---|---|
① | 定义普通业务组件 |
② | 定义切入点,一个切入点可能横切多个业务组件 |
③ | 定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作 |
所以进行AOP编程的关键:就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法 = 增强处理 + 被代理对象的方法。
5、Spring是如何解决循环依赖的? 超级高频!
1)循环依赖问题
A类属性有B类,B类属性有A类,相互依赖。导致死循环。
class A {
private B b;
public A() {
}
public void setB(B b) {
this.b = b;
}
public B getB() {
return b;
}
}
-----------------------------
class B {
private A a;
public B() {
}
public void setA(A a) {
this.a = a;
}
public A getA() {
return a;
}
}
2)循环依赖类型
类型 | 解决方式 |
---|---|
构造器的循环依赖(无set方法) | 无法解决,会直接抛出异常 |
非单例模式下的循环依赖 | 无法解决 |
单例模式下的setter循环依赖 | 三级缓存机制 |
3)三级缓存机制:原理:将对象实例化与初始化的两个过程分开
分开的方式就是设定3个Map集合,查找顺序是一二三
缓存 | map | 存储对象 | (key,value) |
---|---|---|---|
一级缓存 | SingletonObjects | 实例化与初始化后,成品Bean对象。用于存放我们可以直接getBean()获得的对象。 | (beanName,成品bean) |
二级缓存 | EarlySingletonObjects | 循环依赖,用于存放从第三级缓存中获取的半成品bean。若存在代理,则从三级缓存中最终获取的是aop代理对象 | (beanName,半成品bean) |
三级缓存 | SingletonFactories | 存放的是lambda表达式(getEarlyBeanReference()方法),函数式接口。寻找依赖时,当一级缓存和二级缓存中都为null,此时若三级缓存 singletonFactory != null,那么会调用singletonFactory.getObject(),此时就触发回调函数lambda表达式,即真正的执行getEarlyBeanReference()方法,此方法的作用是:返回目前所需要的依赖的bean半成品对象。并判断,若存在AOP代理,则返回代理对象。否则,返回原对象。 | (beanName,lambda表达式) |
注意:二级缓存和三级缓存不能同时存在相同的Bean对象。
//一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
以A与B为例,循环依赖加载过程如下:
一、创建A
阶段 | 执行(背诵) |
---|---|
Step1 | 首先按顺序访问一二三级缓存,尝试从容器中获取A对象。获取不到,将A放到正在创建的 beanName 列表中 |
Step2 | 实例化A对象(通过反射),A a = new A(); 此时 a 对象未初始化,属于半成品 |
Step3 | 将"创建a对象的回调函数式接口lambda表达式 "放入第三级缓存中。等待发生A发生循环依赖被调用时,三级缓存才会调用singletonFactory.getObject()生成半成品a对象 ;也就是当被调用的那一刻,才创建了A对象。 |
Step4 | 尝试初始化a对象populateBean() 。此时发现设置属性需要B对象,则按顺序访问一二三级缓存 ,尝试从容器中获取B对象。获取不到,将B放到正在创建的beanName列表中,开始创建b对象 |
二、创建B
阶段 | 执行(背诵) |
---|---|
Step5 | 实例化B对象(通过反射 ),B b = new B(); 此时 b 对象未初始化,属于半成品 |
Step6 | 将"创建b对象的回调函数式接口lambda表达式(作用等同于createBean())"放入第三级缓存中 。等待发生B发生循环依赖被调用时,三级缓存才会调用singletonFactory.getObject()生成半成品b对象 ;也就是当被调用的那一刻,才创建了B对象 |
Step7 | 尝试初始化b对象populateBean() 。此时发现设置属性需要B对象。则顺序访问一二三级缓存,最后在第三级缓存找到了用于创建半成品A对象的lambda表达式 。那么调用 singletonFactory.getObject() ,生成半成品a对象(注意:此时若a被代理,则此方法返回的是a的代理对象 ),接着将a传入二级缓存,并从第三级缓存中移除a的lambda表达式 |
Step8 | b拿到了半成品a对象,初始化完成。接着b对象(成品bean)传入一级缓存,并从三级缓存中移除b的lambda表达式。至此,B的创建过程结束。 |
三、继续创建A
阶段 | 执行(背诵) |
---|---|
Step9 | 上接 Step4。b被创建完毕,此时a可以从一级缓存中getBean() 获得b对象。则a对象完成b的属性填充,初始化工作完成; |
Step10 | 将成品a传入一级缓存中,并从二级缓存中移除。至此,a和b均创建完成。 |
① 如果只有1个Map结构,能解决循环依赖吗?
-
如果不存在AOP代理,那么一个Map理论上能解决循环依赖问题
解决循环依赖问题的本质就是将依赖对象的实例化与初始化过程分开,具体到对象上,就是
将半成品bean和成品bean区分开
,通常用两个Map结构分别储存。但实际上,我们也可以通过设置标识符号
,存到一个Map中进行存储,但是没必要,操作比较麻烦
。因此,用两个Map分别存储即可。
② 如果只有2个Map结构,能解决循环依赖吗?
-
如果不存在AOP代理,那么2个Map可以解决循环依赖,原因同上。
这里重点说明为什么存在AOP代理时,两个Map解决不了的问题。
-
在对象的创建过程中,
原始的对象可能需要生成代理对象
,代理对象的创建是在初始化过程的扩展阶段(Bean的生命周期:执行init-method对象初始化之后)。因此我们在属性赋值时判断是否需要生成代理对象。因此引入了三级缓存,三级缓存中存放的是一个lambda表达式。
-
那为什么要使用lambda表达式(ObjectFactory<?>)呢?
因为半成品Bean是不能随便对外暴露的,而对象在什么时候被暴露(即何时被引用)是无法提前确定好的,因此只有在调用的那一刻才能进行原始对象还是代理对象的判断。
因此引入了一个lambda表达式
,它类似于一个回调机制,不暴露的时候就不需要调用执行,当出现循环依赖的时候,才会真正的执行lambda表达式。其作用是:判断应该返回的是原始对象还是代理对象。
1、若没有三级缓存,只用一二级缓存时 此时若根据类名直接由二级缓存中获取对象的话,获取的是原始对象。而我们想要的是获取代理对象,此时就出现了最终Bean对象不一致问题。所以Spring在类加载过程中,我们 将lambda表达式放入三级缓存中
,从三级缓存中获取类对象的时候,判断类是否被代理,若被代理则返回代理对象。
从而解决AOP代理 + 循环依赖下Bean不一致的问题;2、若没有二级缓存,只用一三级缓存时 由于三级缓存的ObjectFactory<?>的lambda表达式,singletonFactory.getObject()能够解决AOP动态代理下的循环依赖问题,但是如果没有二级缓存,那么就无法保证bean的单例模式了,这是因为singletonFactory.getObject()每次都会生成一个新的半成品对象,如果不放在二级缓存中;那么在多线程下,A线程和B线程都需要对象C的半成品对象,那如果二级缓存中没有,若此时C还没有进入一级缓存,那么A线程与B线程都会调用三级缓存的singletonFactory.getObject(),从而生成两个半成品C对象,从而破坏了单例模式;因此,二级缓存必须存在!
③ 三级缓存解决的是什么问题?
-
三级缓存解决的是存在AOP代理+循环依赖下最终获取的bean不一致的问题
。1、若没有三级缓存,只用一二级缓存时:
此时若根据类名直接由二级缓存中获取对象的话,获取的是原始对象。而我们想要的是获取代理对象,此时就出现了最终Bean对象不一致问题。所以Spring在类加载过程中,我们
将lambda表达式放入三级缓存中
,从三级缓存中获取类对象的时候,判断类是否被代理,若被代理则返回代理对象。
从而解决AOP代理 + 循环依赖下Bean不一致的问题;2、若没有二级缓存,只用一三级缓存时:
④ 三级缓存核心源码
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//1、根据BeanName从一级缓存(singletonObjects)中查找bean对象,此一级缓存中都是单例对象。即我们熟知的单例bean工厂
Object singletonObject = this.singletonObjects.get(beanName);
//2、一级缓存(singletonObjects)中没有,判断当前beanName是不是正在创建中。
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//加锁,双重校验,保证bean的半成品也是单例的;
synchronized (this.singletonObjects) {
//3、尝试从二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
//4、二级缓存中没有并且允许暴露
if (singletonObject == null && allowEarlyReference) {
//5、从三级缓存中获取lambda表达式(函数式接口)
ObjectFactory<?> singletonFactory=this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//6、调用getObject()方法,真正的执行lambda表达式中的方法Object earlyBean = getEarlyBeanReference()
//此方法会判断是否存在AOP代理,并返回代理半成品对象或者原始半成品对象
singletonObject = singletonFactory.getObject();
//7、将半成品bean放入二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
//8、将半成品bean从三级缓存中删除
this.singletonFactories.remove(beanName);
}
}
}
}
//返回bean对象
return singletonObject;
}
⑤ 从二级缓存中获取到了,就删掉二级缓存,传入一级缓存
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
//一级缓存中没有
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
//从二级缓存中删掉,并放入一级缓存
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
⑥ 三级缓存调用lambda表达式生成半成品Bean对象
当三级缓存调用,singletonObject = singletonFactory.getObject()
时执行
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
//1、将原始半成品bean暴露出来
Object exposedObject = bean;
//2、判断是否实现了Aware接口以及一些扩展点
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
//3、检查是否存在AOP代理
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
//4、将暴露的原始半成品bean替换成经过代理的半成品bean
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
//返回给三级缓存,即本文的上一个方法
return exposedObject;
}
每一级缓存的放置时间和删除时间
缓存 | 放置 | 删除 |
---|---|---|
三级缓存 | 实例化createBeanInstance之后 | 第一次从三级缓存调用getObject(),触发函数式接口时 |
二级缓存 | 三级缓存删除时 | Bean对象实例化和初始化完成后 |
一级缓存 | 二级缓存删除时 | 直到Bean被销毁,否则不删除 |
6、BeanFactory 与 FactoryBean 有什么区别
类型 | 作用 | 区别 |
---|---|---|
BeanFactory | 创建Bean对象 | 使用BeanFactory创建对象的时候,必须要遵循严格的生命周期流程; |
FactoryBean | 创建Bean对象 | 简单的自定义某个对象的创建,同时创建完成的对象想交给spring来管理,那么就需要实现FactroyBean接口 FactroyBean中有3个方法: ① isSingleton():是否是单例对象; ② getObjectType():获取返回对象的类型; ③ getObject():自定义创建对象的过程(new,反射,动态代理) |
7、谈一下Spring事务?
-
问题: 某一个事务嵌套另外一个事务的时候怎么办?A方法调用B方法,他们都有事务并且传播特性不同。那么A如果有异常,B怎么办?B有异常,A怎么办?
答: Spring事务有7种传播特性。主要分为三类,
支持当前事务、不支持当前事务、嵌套事务
事务类型 传播级别 支持当前事务 Mandatory、Required、Support 不支持当前事务 Never、Required_New、Not_Support 嵌套事务 Nested -
具体的处理逻辑是这样的:首先判断内外两层的事务是不是同一个?
是: 那么异常统一在外层处理
不是: 内层也许会影响到外层。但是外层不会影响内层。内外两层是相互独立的。
1)NESTED和REQUIRED_NEW的区别:
类型 | 区别 |
---|---|
REQUIRED_NEW | REQUIRED_NEW是新建一个事务并且新开始的这个事务与原有事务无关。 原事务回滚,新开启的事务不受影响,不会回滚。 |
NESTED | NESTED则是当前存在事务时会开启一个嵌套事务。父事务回滚时,子事务也会回滚。 |
2)NESTED和REQUIRED的区别:
类型 | 区别 |
---|---|
REQUIRED | REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一个事务 ,那么被调用方出现异常时,由于共用一个事务,所以无论是否catch异常,事务都会回滚。 |
NESTED | 被调用方发生异常时,调用方可以catch其异常 ,这样只有子事务回滚,父事务不会回滚。 |
-
支持当前事务的三种:Mandatory、Required、Support
-
不支持当前事务的三种:Never、Required_New、Not_Support
3) Spring事务的隔离级别有哪些?
隔离级别 | 问题 | 概述 | 原理 |
---|---|---|---|
读未提交 | 脏读、幻读、不可重复读、第二类更新丢失 | 可以读到其它事务没有提交的执行结果 | - |
读已提交 | 幻读、不可重复读、第二类更新丢失 | 只能读到其它事务提交后的执行结果 | MVCC机制: 每次快照读都会创建一个新的Read View |
可重复读 | 幻读、第二类更新丢失 | 支持读已提交,可以重复读取相同的数据(Mysql默认) | MVCC机制: Read View只会在第一次快照读的时候创建,后续的快照读都是读的第一个Read View |
可串行化 | 无 | 支持可重复读,最高隔离级别(InnoDB分布式事务下默认) | 事务排序 + 共享锁,解决幻读 |
在进行配置的时候,如果数据库和spring代码中的隔离级别不同,那么以spring的配置为主
2)Spring事务失效的几种情况?
情况 | 描述 |
---|---|
情况1 | Bean对象没有被spring容器管理 |
情况2 | 方法的访问修饰符不是public |
情况3 | 异常被捕获,@Transactional感知不到异常就不会回滚,而是提交 |
情况4 | 自身调用问题,只有通过代理模式调用方法,事务才会生效 。在本方法内调用是不会走事务的 |
8、Spring中的Bean对象的作用域
作用域 | 描述 | 是否线程安全 |
---|---|---|
Singleton 单例模式 | 指的是每个Spring Ioc容器中只有一个实例 | 线程不安全的 ;① 如果Bean无状态的,则无需考虑线程安全(同步)问题; ② 若Bean是有状态的(存储数据),那么就是线程不安全的,需要考虑线程同步问题; |
Prototype 原型模式 | 每个线程请求Spring Ioc容器都会产生一个新的Bean实例对象。对于每个线程来说,这个Bean是唯一的(单例)。 | 线程安全。因为每个线程请求容器时,都会相当于new Bean(); |
Request | 每一次Http请求都会产生一个新的Bean实例,且此Bean仅在当前Http连接内有效; | 线程安全 |
Session | 每一次Http请求都会产生一个新的Bean实例,且此Bean仅在当前Http Session内有效; | 线程安全 |
Global Session | 类似标准的Http Session作用域 | 线程安全 |
9、Spring是如何解决Singleton的线程不安全问题的?
- 首先要明确的是,Singleton模式下无状态的Bean是不会有线程安全问题的;
- 线程安全只在有状态的Bean下需要考虑;所谓有状态的Bean,就是Bean中有存储数据的功能;
解决方法 | 描述 |
---|---|
加锁 | 在Singleton模式下,由于Ioc容器中只有一个Bean对象。 |
ThreadLock |
10、Spring用到了哪些设计模式?
设计模式 | 描述 |
---|---|
单例模式 | Ioc容器中的Bean对象都是单例的 |
工厂模式 | 通过BeanFactory工厂类可以创建Bean对象 |
代理模式 | Spring中的AOP的实现原理就是基于Jdk/Cglib动态代理 |
模板模式 | Spring中的jdbcTemplate等以Template字段结尾的对数据库操作的类,都使用了模板模式 |
观察者模式 | Spring中的事件驱动模型,就是观察者模式的一个经典应用 |