目录
1. 谈谈你对Spring的理解?
2. 详细讲解一下Spring的核心容器(spring context应用上下文) 模块
3. BeanFactory 和 ApplicationContext有什么区别?
4. 解释 Spring 支持的几种bean的作用域?
5. Spring 框架中的单例 bean是线程安全的吗?
6. 解释 Spring 框架中 bean 的生命周期
7. Spring 支持的事务管理类型
8. Spring事务的实现方式和实现原理
9. 事务的ACID是指什么?
10. Spring事务的五种事务隔离级别?
11. 什么是脏读、不可重复读、幻读?
12. Spring事务的7种传播级别?
13. Spring 框架的事务管理有哪些优点 ?
14. JDK动态代理和CGLIB动态代理的区别?
15. 解释一下Spring AOP里面的几个名词?
16. 什么是(Aspect)切面?
17. Spring 框架中都用到了哪些设计模式?
18. Spring如何处理线程并发问题?
19.Spring 中什么时候会出现循环依赖?
20.Spring 是如何解决 singleton 类型 Bean 循环依赖的?
21.为什么要三级缓存?二级缓存不能解决循环依赖吗?
22.如果 非要用 二级缓存来解决上面提到的两个问题,如何来实现?为什么 Spring 没有采用你说的这种方案?
23.AOP 代理对象提前放入了三级缓存,没有经过初始化和属性填充,这个代理又是如何保证依赖属性的注入的呢?
24.明明初始化的时候是A对象,那么 Spring 是在哪里将 A的代理对象放入到容器中 的呢?
25.回到 19 ,提到的两种方式中,为什么没有解决循环依赖问题?
Spring框架是一个轻量级的开源框架,主要是为了简化企业级应用的后台开发,降低耦合性。平时接触到最多的还是IoC和AOP两个特性。
IoC指的是控制反转,把对象的创建和依赖关系的维护交给Spring容器去管理。Spring通过工厂模式、反射机制等技术管理对象的作用域和生命周期。
AoP一般称为面向切面编程,是面向对象的一种补充,将程序中独立于其他功能的方法抽取出来,使Java开发模块化,仅需专注于主业务即可
这是基本的Spring模块,提供spring 框架的基础功能,BeanFactory 是任何以spring为基础的应用的核心。Spring 框架建立在此模块之上,它使Spring成为一个容器。
Bean 工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从真正的应用代码中分离。最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory ,它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。
二者都是 Spring 框架的两大核心接口,都可以当做 Spring 的容器。其中 ApplicationContext 是 BeanFactory 的子接口。
BeanFactory 是 Spring 里面最底层的接口,包含了各种 Bean 的定义,读取配置文档,管理 Bean 的加载、实例化,控制 Bean 的生命周期,维护对象之间的依赖关系等功能。
ApplicationContext 接口作为 BeanFactory 的派生,除了提供 BeanFactory 所具有的功能外,还提供了更完整的框架功能:
继承 MessageSource,支持国际化。
统一的资源文件访问方式。
提供在监听器中注册 Bean 的事件。
支持同时加载多个配置文件。
载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,如应用的 Web 层。
具体区别体现在以下三个方面:
加载方式不同
BeanFactroy 采用的懒加载方式注入 Bean,即只有在使用到某个 Bean 时才对该 Bean 实例化。这样,我们就不能在程序启动时发现一些存在的 Spring 的配置问题。
ApplicationContext 是在启动时一次性创建了所有的 Bean。
创建方式不同
BeanFactory 通常以编程的方式被创建,ApplicationContext 还能以声明的方式创建,如使用 ContextLoader。
注册方式不同
二者都支持 BeanPostProcessor、BeanFactoryPostProcessor 的使用,但 BeanFactory 需要手动注册,而 ApplicationContext 则是自动注册。
singleton作用域 : 是spring默认的作用域,bean 在每个 Spring ioc 容器中只有一个实例。
prototype作用域:一个 bean 的定义可以有多个实例,但该作用域谨慎使用,频繁创建和销毁会严重影响性能。
request作用域:每次 http 请求都会创建一个 bean, 该作用域仅在基于 web 的 Spring Application Context 情况下有效。
session作用域:在一个 HTTP Session 中,一个 bean 定义对应一个实例。该作用域仅在基于 web 的 Spring Application Context 情况下有效 。
global-session作用域:在一个全局的 HTTP Session 中,一个 bean 定义对应一个实例。该作用域仅在基于 web 的 Spring Application Context 情况下有效。
Spring 框架中的单例 bean 不是线程安全的,spring 中的 bean 默认是单例模式,Spring框架并没有对单例 bean 进行多线程的封装处理。实际上大部分时候 spring bean 是无状态的(比如 dao类),某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了。最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,就可以保证线程安全了。
Spring 容器从XML 文件中读取 bean 的定义,并实例化 bean。
Spring 根据 bean 的定义填充所有的属性。
如果 bean 实现了 BeanNameAware 接口.Spring 传递 bean 的ID到 setBeanName 方法。
如果 Bean 实现了 BeanFactoryAware 接口, Spring 传递 beanFactory 给 setBeanFactory 方法。
如果有任何与 bean 相关联的 BeanPostProcessors, Spring 会在 postProcesserBeforeInitialization() 方法内调用它们.
如果 bean 实现 IntializingBean 了,调用它的 afterPropertySet 方法,如果 bean 声明了初始化方法,调用此初始化方法。
如果有 BeanPostProcessors 和 bean 关联,这些 bean 的 postProcessAfterInitialization() 方法将被调用。
如果 bean 实现了 DisposableBean 它将调用 destroy() 方法。
编程式事务管理: 通过编程的方式管理事务,带来极大的灵活性,但是难以维护 。
声明式事务管理 :可以将业务代码和事务管理分离,只需用注解和 XML 配置来管理事务。
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务管理功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
原子性(Atomic):事务中各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事务的失败;
一致性(Consistent):事务结束后系统状态是一致的;
隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态;
持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难性的失败,通过日志和同步备份可以在故障发生后重建数据。
DEFAULT:是Spring事务默认的隔离级别,使用数据库默认的事务隔离机制
未提交读(read uncommited):是最低的事务隔离级别,它允许另外一个事务可以读取当前事务未提交的数据。脏读,不可重复读,幻读都有可能发生
已提交读(read commited): 保证一个事务提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。避免了脏读,但是不可重复读和幻读都有可能发生
可重复读(repeatable read):保证一个事务不会修改已经由另一个事务读取但未提交或者未回滚的数据,避免了脏读和不可重复读,但是幻读有可能发生
可串行化(serializable):最严格的事务隔离级别,支持事务串行执行,资源消耗最大,避免了脏读,不可重复读,幻读
脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如A事务执行过程中,B事务读取了A事务的修改。但是由于某些原因A事务没有完成提交,发生了回滚操作,则B事务所读取的数据是不正确的,这就是脏读。
不可重复读 :表示一个事务读到另一个事务已经提交的updated数据,导致多次查询结果不一致。比如B事务读取了两次数据,在这两次的读取过程中A事务修改了数据,导致B事务的这两次读取出来的数据不一样,这就是不可重复读
幻读 :表示一个事务读到另一个事务已经提交的insert数据,导致多次查询结果不一致。比如B事务读取了两次数据,在这两次的读取过程中A事务添加了数据,导致B事务的这两次读取出来的数据不一样,这就是幻读
PROPAGATION_REQUIRED级别: 默认的spring事务传播级别,如果已经存在一个事务,则支持当前事务。如果当前不存在事务,则开启一个新的事务。
PROPAGATION_SUPPORTS级别: 如果已经存在一个事务,则支持当前事务。如果当前不存在事务,则使用非事务的方式执行。
PROPAGATION_MANDATORY级别: 如果已经存在一个事务,则支持当前事务。如果当前不存在事务,则抛出异常。
PROPAGATION_REQUIRES_NEW级别: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。
PROPAGATION_NOT_SUPPORTED级别: 总是使用非事务的方式执行,并挂起任何存在的事务
PROPAGATION_NEVER级别: 使用非事务的方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED级别: 如果当前存在事务,则嵌套在事务内执行。如果当前不存在事务,则按PROPAGATION_REQUIRED属性执行。
它为不同的事务 API 提供一个不变的编程模式 。
它为编程式事务管理提供了一套简单的 API 而不是一些复杂的事务 API
它支持声明式事务管理。
它可以很好的集成 Spring 的各种数据访问抽象层。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
切面:是通知和切点的结合,通知和切点共同定义了切面的全部内容。 在Spring AOP中,切面可以使用通用类或者在普通类中以 @AspectJ 注解来实现。
连接点:指方法,在Spring AOP中,一个连接点总是代表一个方法的执行。
通知:在Spring AOP中,切面的工作被称为通知,通知是一个在方法执行前或执行后要做的动作。
切点:切点的定义会匹配通知所要织入的一个或多个连接点。通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定切点。
引入:引入允许向现有类添加新方法或属性。
目标对象: 被一个或者多个切面所通知的对象,它通常是一个代理对象。
织入:织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程,织入可以在编译时,加载时,或运行时完成。
AspectJ切面是AOP的核心,它将多个类的通用行为封装成可复用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面,根据需求的不同,一个应用程序可以有若干切面。在SpringAOP中,切面通过带有@Aspect注解的类实现。
工厂模式:BeanFactory是工厂模式的一个实现,用来创建对象的实例;
单例模式:Spring配置文件定义的Bean默认为单例模式。
代理模式:Spring的AOP功能的实现用到了代理模式;
模板方法模式:用来解决代码重复的问题。比如. RestTemplate模版类, JmsTemplate模版类,
JpaTemplate模版类。
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。
一般情况下,只有无状态的Bean才可以在多线程的环境下共享。在Spring中,绝大部分Bean都可以声明为singleton作用域。因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题,ThreadLocal采用了“空间换时间”的方式,而线程同步机制采用了“时间换空间”的方式。
ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
不能完成 Bean 的实例化,导致无法放入缓存中,进而导致循环依赖中,无法获取到其他依赖对象的情况。如全构造器循环依赖(该场景无法解决,官方建议使用 setter 注入方式代替构造器注入)、获取不到实例后的构造器与 setter 的注入方式。
部分类型 Bean 的循环注入,如全为 prototype 类型的循环、prototype 类型先去获取 singleton 的循环。
Spring 通过 提前曝光 机制,利用 三级缓存 解决循环依赖。
二级缓存配合也可以解决循环依赖,但三级缓存的使用,是为了完成 Spring AOP 中的后置处理 功能而提出的。
二级缓存架构下,为了获取到 AOP 代理后的对象,需要对 Bean A 在 实例化 后,完成代理,然后放入二级缓存提前曝光,保证 Bean B 在填充类属性时,可以从二级缓存获取到代理后的 Aop-A,以实现 Spring Aop 的代理功能以及解决 A<=>B 的循环依赖问题。
上述的逻辑并没有被 Spring 采用,是因为上述流程和 Spring Bean 生命周期的设计相互冲突了。 Bean 生命周期的最后一步才是完成 AOP 代理,而不是二级缓存架构中提到的在实例化后,就完成代理。
AOP 中的代理对象,保存着 traget 也就是是 原始bean 的引用,因此后续 原始bean 的完善,也就相当于 Spring AOP 中的 target 的完善,这样就保证了 AOP 的 属性填充 与 初始化 了!
在完成 Bean A 的初始化 后,Spring 又调用了一次 getSingleton 去缓存中获取对象,但这次是禁用三级缓存的获取方式(allowEarlyReference = false)。在这之前,为 Bean B 中注入 Bean A 时,已经将 A 从三级缓存中的工厂取出,并从工厂中获取到了Aop-A(半成品)放入到了二级缓存中。所以初始化完成后的 getSingleton 调用,拿到的是二级缓存中 Aop-A 对象,最后 将 Aop-A 移除二级缓存,放入一级缓存中。然后继续执行 Bena 接下来的流程。
对于使用了构造器的循环依赖,由于 无法通过构造器生成 向缓存中存入的 实例化后的 Bean ,所以 Spring 会抛出异常。
对于 prototype 作用域的 Bean ,Spring 容器没有设计缓存机制,因此也无法提前暴露一个创建中的 Bean。
原因也比较简单,prototype 作用域的 Bean 每次请求都会创建一个实例对象,如果使用缓存,在请求量比较大的场景下,对 GC 的压力会非常大,所以 Spring 直接抛出异常。
26、什么是循环依赖,如何解决?
Spring如何解决循环依赖 - 简书