IOC
1、什么是IOC?什么是DI?
IOC
lOC:控制反转,以前创建对象的时候都需要自己手动的new,而现在创建对象的任务全部交给spring来管理,降低了对象之间的耦合,当我们需要使用这个对象的时候直接从IOC中获取就行了。
DI
DI(依赖注入):指的是容器在实例化对象的时候把它依赖的类注入给它。
2、IOC的实现原理?
Spring的IoC的底层实现原理是工厂模式+反射+XML配置文件 。即先写一个接口,再写该接口的一个实现类,通过解析XML配置文件获取该实现类的配置属性,在工厂类中使用反射机制得到实现类相应的对象。
3、 说说BeanFactory和ApplicantContext?
可以这么形容,BeanFactory是Spring的“心脏”,ApplicantContext是完整的“身躯”。
- BeanFactory(Bean工厂)是Spring框架的基础设施,面向Spring本身。
- ApplicantContext(应用上下文)建立在BeanFactoty基础上,面向使用Spring框架的开发者。
BeanFactory 接口
BeanFactory是类的通用工厂,可以创建并管理各种类的对象。
ApplicationContext 接口
ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。可以这么说,使用BeanFactory就是手动档,使用ApplicationContext就是自动档。
4、Spring中的bean生命周期?
Spring IOC 中Bean的生命周期大致分为四个阶段:实例化、属性赋值、初始化、销毁。
创建过程:
- 实例化:第 1 步,实例化一个 Bean 对象;
- 属性赋值:第 2 步,为 Bean 设置相关属性和依赖;
- 初始化:3、4 步为在初始化前执行,5、6步是真正的初始化,第 7 步在初始化后执行,初始化完成之后,Bean就可以被使用了;
- 销毁:第 8~10步,第8步其实也可以算到销毁阶段,但不是真正意义上的销毁,而是先在使用前注册了销毁的相关调用接口,为了后面第9、10步真正销毁 Bean 时再执行相应的方法。
5、什么是依赖注入?依赖注入的方式有哪些?
依赖注入 :指的是容器在实例化对象的时候把它依赖的类注入给它。
依赖注入可以通过三种方式完成:
- 构造方法注入
- 如果只有一个有参数的构造方法并且参数类型与注入的bean的类型匹配,那就会注入到该构造方法中。
- Setter注入
- 在XML中写入,然后在set方法中注入。
- 基于注解的注入
- @Autowired(构造,接口,方法)
自动装配,默认根据类型注入 。
6、Spring有哪些自动装配的方式?
自动装配:
- Spring IOC容器知道所有Bean的配置信息,此外,通过Java反射机制还可以获知实现类的结构信息,如构造方法的结构、属性等信息。掌握所有Bean的这些信息后,Spring IOC容器就可以按照某种规则对容器中的Bean进行自动装配,而无须通过显式的方式进行依赖配置。
Spring提供了哪几种自动装配类型?
Spring提供了4种自动装配类型:
- byName:根据名称进行自动匹配;
- byType:根据类型进行自动匹配;
- constructor:与 byType类似, 只不过它是针对构造函数注入而言的。如果容器中没有找到和构造函数入参匹配类型的Bean,则Spring将抛出异常。
- autodetect:根据Bean的自省机制决定采用byType还是constructor进行自动装配,如果Bean提供了默认的构造函数,则采用byType,否则采用constructor。
7、Spring 中的 Bean 的作用域有哪些?
Spring的Bean主要支持五种作用域:
- singleton : 单例,在Spring容器仅存在一个Bean实例,Bean以单实例的方式存在,是Bean默认的作用域。
- prototype : 多例,每次从容器重调用Bean时,都会返回一个新的实例。
- request : 每一次HTTP请求都会产生一个新的Bean,该Bean仅在当前HTTP Request内有效。
- session : 同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean。
- globalSession:同一个全局Session共享一个Bean,只用于基于Protlet的Web应用,Spring5中已经不存在了。
8、Spring 中的单例 Bean 会存在线程安全问题吗?
Spring中的单例Bean不是线程安全的。
- 因为单例Bean,是全局只有一个Bean,所有线程共享。如果说单例Bean,是一个无状态的,也就是线程中的操作不会对Bean中的成员变量执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。
- 假如这个Bean是有状态的,也就是会对Bean中的成员变量进行写操作,那么可能就存在线程安全的问题。
单例Bean线程安全问题怎么解决呢? ⭐
- 将Bean中的成员变量保存在ThreadLocal中;
- 我们知道ThredLoca能保证多线程下变量的隔离,可以在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal里,这是推荐的一种方式。
9、循环依赖?
Spring 循环依赖:简单说就是自己依赖自己,或者和别的Bean相互依赖。
只有单例的Bean才存在循环依赖的情况,原型(Prototype)情况下,Spring会直接抛出异常。原因很简单,AB循环依赖,A实例化的时候,发现依赖B,创建B实例,创建B的时候发现需要A,创建A1实例……无限套娃,会直接把系统整垮。
10、Spring如何解决循环依赖?
我们都知道,单例Bean初始化完成,要经历三步:
注入就发生在第二步,属性赋值,结合这个过程,Spring 通过三级缓存解决了循环依赖:
- 一级缓存 : Map<String,Object> singletonObjects ,单例池,保存实例化、属性赋值(注入)、初始化完成的 bean 实例;
- 二级缓存 : Map<String,Object> earlySingletonObjects ,早期曝光对象,用于保存实例完成的 bean 实例;
- 三级缓存 : Map<String,ObjectFactory<?>> singletonFactories ,早期曝光对象工厂,用于保存 bean 创建工厂,以便于后面扩展有机会创建代理对象。
三级缓存解决循环依赖的过程:
- 当 A、B 两个类发生循环依赖时:
- A实例的初始化过程:
- 创建A实例,实例化的时候把A对象工厂放⼊三级缓存,表示A开始实例化了,虽然我这个对象还不完整,但是先曝光出来让大家知道;
- A注⼊属性时,发现依赖B,此时B还没有被创建出来,所以去实例化B;
- 同样,B注入属性时发现依赖A,它就会从缓存里找A对象。依次从⼀级到三级缓存查询A,从三级缓存通过对象工厂拿到A,发现A虽然不太完善,但是存在,把A放⼊二级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入⼀级缓存;
- 接着A继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除二级缓存中的A,同时把A放入⼀级缓存;
- 最后,⼀级缓存中保存着实例化、初始化都完成的A、B对象;
11、为什么要三级缓存?二级不行吗?
不行,主要是为了生成代理对象。如果是没有代理的情况下,使用二级缓存解决循环依赖也是OK的。但是如果存在代理,三级没有问题,二级就不行了。
- 因为三级缓存中放的是生成具体对象的匿名内部类,获取Object的时候,它可以生成代理对象,也可以返回普通对象。使用三级缓存主要是为了保证不管什么时候使用的都是⼀个对象。
- 假设只有二级缓存的情况,往二级缓存中放的显示⼀个普通的Bean对象,Bean初始化过程中,通过 BeanPostProcessor 去生成代理对象之后,覆盖掉二级缓存中的普通Bean对象,那么 可能就导致取到的Bean对象不⼀致了。