Spring IOC面试总结

谈谈对 Spring 理解
让 Java 开发模块化,并且全面;
Spring 通过控制反转降低耦合性,一个对象的依赖通过被动注入的方式而非主动 new;
通过代理模式实现了面向切面编程;

Spring 支持的 Bean 作用域
Spring 容器中的 Bean 可以分为5个范围:

singleton:默认,每个容器中只有一个 Bean 的实例,单例的模式由 BeanFactory 自身来维护;
prototype:为每一个 Bean 请求提供一个实例;
request:为每一个网络请求创建一个实例,在请求完成以后,Bean 会失效并被垃圾回收器回收;
session:与 request 范围类似,确保每个 session 中有一个 Bean 实例,在 session 过期后,Bean 会随之失效;
global-session:全局作用域,global-session 和 Portlet 应用相关;当你的应用部署在 Portlet 容器中工作时,它包含很多 Portlet;如果你想要声明让所有的 Portlet 共用全局的存储变量的话,那么这全局变量需要存储在 global-session中;全局作用域与 Servlet 中的 session 作用域效果相同;

Spring 中的单例 Beans 是否线程安全?
Spring 框架并没有对单例 Bean 进行任何多线程的封装处理,关于单例 Bean 的线程安全和并发问题需要开发者自行去搞定,但实际上,大部分的 Spring Bean 并没有可变的状态(比如 Service 类和 DAO 类),所以在某种程度上说 Spring 的单例 Bean 是线程安全的;如果你的 Bean 有多种状态的话(比如 View Model 对象),就需要自行保证线程安全,最浅显的解决办法就是将多态 Bean 作用域由 “singleton” 变更为 “prototype”;

Spring 处理线程并发问题
在一般情况下,只有无状态的 Bean 才可以在多线程环境下共享,在 Spring 中,绝大部分 Bean 都可以声明为 singleton 作用域,因为 Spring 对一些 Bean 中非线程安全状态采用 ThreadLocal 进行处理,解决线程安全问题;
ThreadLocal 和线程同步机制都是为了解决多线程中相同变量的访问冲突问题,同步机制采用了 “时间换空间” 的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队,而 ThreadLocal 采用了 “空间换时间” 的方式;
ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突,因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了;
ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal;

Spring 生命周期
实例化 Bean:对于 BeanFactory 容器,当客户向容器请求一个尚未初始化的 Bean 时,或初始化 Bean 的时候需要注入另一个尚未初始化的依赖时,容器就会调用 createBean 进行实例化,对于 ApplicationContext 容器,当容器启动结束后,通过获取 BeanDefinition 对象中的信息,实例化所有的 Bean;
设置对象属性(依赖注入):实例化后的对象被封装在 BeanWrapper 对象中,Spring 根据 BeanDefinition 中的信息以及通过 BeanWrapper 提供的设置属性的接口完成依赖注入;
处理 Aware 接口:接着,Spring 会检测该对象是否实现 xxxAware 接口,并将相关的 xxxAware 实例注入给 Bean:
如果 Bean 已经实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String beanId) 方法,此处传递的就是 Spring 配置文件中 Bean 的 id 值;
如果 Bean 已经实现了BeanFactoryAware 接口,会调用它实现的 setBeanFactory() 方法,传递的是 Spring 工厂自身;
如果 Bean 已经实现 ApplicationContextAware 接口,会调用 setApplicationContext(ApplicationContext) 方法,传入 Spring 上下文;
BeanPostProcessor:如果想对 Bean 进行一些自定义的处理,那么可以让 Bean 实现 BeanPostProcessor 接口,那将会调用 postProcessBeforeInitialization(Object obj, String s) 方法;
InitializingBean 与 init-method:如果 Bean 在 Spring 配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法;
如果 Bean 实现了 BeanPostProcessor 接口,将会调用 postProcessAfterInitialization(Object obj, String s) 方法;由于方法是在 Bean 初始化结束时调用的,所以可以被应用于内存或缓存技术;
以上几个步骤完成后,Bean 就已经被正确创建了,之后就可以使用 Bean 了;
DisposableBean:当 Bean不再需要时,会经过清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调用其实现的 destroy() 方法;
destroy-method:最后,如果这个 Bean 的 Spring 配置中配置了 destroy-method 属性,会自动调用其配置的销毁方法;

Spring 事件类型
Spring 提供了以下5种标准的事件:

上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。
如果 Bean 实现了 ApplicationListener 接口,当一个 ApplicationEvent 被发布以后,Bean 会自动被通知;

Spring 框架的设计模式
工厂模式:Spring 使用工厂模式通过 BeanFactory、ApplicationContext 创建 Bean 对象;
代理模式:Spring AOP 功能的实现;
单例模式:Spring 中的 Bean 默认都是单例的;
模板模式:Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式;
包装器模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库,这种模式让我们可以根据客户的需求能够动态切换不同的数据源;
观察者模式:Spring 事件驱动模型就是观察者模式很经典的一个应用;
适配器模式:Spring AOP 的增强或通知(Advice)使用到了适配器模式、Spring MVC 中也是用到了适配器模式适配 Controller;

Spring 循环依赖 ⭐
Bean A 依赖于另一个 Bean B 时,Bean B 依赖于 Bean A:当 Spring 上下文加载所有 Bean 时,它会尝试按照它们完全工作所需的顺序创建 Bean ,例如,如果我们没有循环依赖:Bean A→Bean B→Bean C:Spring 将创建 bean C,然后创建 bean B(并将 bean 注入其中),然后创建 bean A(并将 bean B 注入其中);但具有循环依赖时,Spring 无法决定应该首先创建哪个 bean,因为它们彼此依赖;
以 Setter 方式构成的循环依赖,Spring 可以自动解决,以构造器方式构成的循环依赖,Spring 无法自动解决:Setter 注入和构造器注入的区别就在于创建 Bean 的过程中,Setter 注入可以先用无参数构造方法返回 Bean 实例,再注入依赖的属性,使用到了 Spring 的三级缓存,而 constructor 方式无法先返回 Bean 的实例,必须先实例化它所依赖的属性,这样一来就会造成死循环所以会失败;
解决方案:我们使用比较多的在属性上 @Autowired 的方式,在 Spring 内部的处理中,与 Setter 方法不太一样,但用此种方式循环依赖也可以自动解决,原理同上,只要能先实例化出来,提前暴露出来,就可以解决循环依赖的问题;

Spring 解决循环依赖 ⭐
Spring 使用了三级缓存解决了循环依赖的问题:在 populateBean() 给属性赋值阶段里面 Spring 会解析属性,并且赋值,当发现,A 对象里面依赖了 B,此时又会走 getBean 方法,但这个时候,你去缓存中是可以拿的到的;因为在对 createBeanInstance 对象创建完成以后已经放入了缓存当中,所以创建 B 的时候发现依赖 A,直接就从缓存中去拿,此时 B 创建完,A 也创建完;至此 Bean 的创建完成,最后将创建好的 Bean 放入单例缓存池中;

Bean 创建的核心三个方法
createBeanInstance:实例化,调用对象的构造方法实例化对象;
populateBean:填充属性,主要是对 Bean 的依赖属性进行注入(@Autowired);
initializeBean:回到一些形如 initMethod、InitializingBean 等方法;
从对单例 Bean 的初始化可以看出,循环依赖主要发生在 populateBean,也就是 field 属性注入的处理;

三级缓存
singletonObjects:第一级单例缓存池,用于存放完全初始化好的 Bean,从该缓存中取出的 Bean 可以直接使用;
earlySingletonObjects:第二级,提前曝光的单例对象的 Cache,存放原始的 Bean 对象(尚未填充属性的 Bean);
singletonFactories:第三级单例对象工厂缓存 ,单例对象工厂的 Cache,存放 Bean 工厂对象;

单例 Bean 的创建过程
先从一级缓存 singletonObjects 中去获取(如果获取到就直接 return);
如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存 earlySingletonObjects 中获取(如果获取到就直接 return);
如果还是获取不到,且允许 singletonFactories(allowEarlyReference=true)通过 getObject() 获取,就从三级缓存singletonFactory.getObject() 获取(如果获取到了就把这个 Bean 从 singletonFactories 中移除,并且放进 earlySingletonObjects,其实也就是从三级缓存移动(剪切)到了二级缓存);

Spring 解决 A、B 循环依赖流程
使用 context.getBean(A.class),为了获取容器内的单例 A,若 A 不存在,就走 A 这个 Bean 的创建流程,显然初次获取 A 是不存在的,所以开始创建 A;
开始实例化 A(createBeanInstance,注意此处仅仅是实例化),并将它放进缓存(此时 A 已经实例化完成,已经可以被引用了);
开始准备初始化 A(populateBean):解析 A 的依赖发现依赖注入了 B(此时需要去容器内获取 B);
此时开始实例化 B,到了依赖注入 B 时,会通过 getBean(B) 去容器内找 B;
实例化 B,并将其放入缓存(此时 B 也能够被引用了);
开始准备初始化 B,发现依赖注入 A(此时需要去容器内获取 A);
初始化 B 时会调用 getBean(A) 去容器内找到 A,此时候因为 A 已经实例化完成了并且放进了缓存里,所以此时去缓存里看是已经存在 A 的引用,所以 getBean(A) 能够正常返回;
B初始化成功,return(此处 return 相当于是返回最上面的 getBean(B) ,回到了初始化 A 的流程中);
因为 B 实例已经成功返回,因此最终 A 也初始化成功;
B 持有的已经是初始化完成的 A,A 持有的也是初始化完成的 B;

Spring IOC 容器
Spring IOC 理解
IOC 就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到 Spring 容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。
而 DI 依赖注入,和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖 IOC 容器来动态注入对象需要的外部资源;
最直观的表达就是,IOC 让对象的创建不用去 new,可以由 Spring 自动生产,使用 Java 的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的;
Spring IOC 有三种注入方式 :构造器注入、Setter 方法注入、注解注入;
IOC 让相互协作的组件保持松散的耦合,而 AOP 编程允许把遍布于应用各层的功能分离出来形成可重用的功能组件;

IOC 实现机制 ⭐
实现原理就是工厂模式加反射机制:
Spring 容器在启动的时候,先会保存所有注册进来的 Bean 的定义信息, 注册到 BeanFactory 中;注册也只是将这些信息都保存到了注册中心(说到底核心是一个 BeanName-> BeanDefinition 的 Map);
设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 Bean,如 Environment、SystemProperties;
如果有 Bean 实现了 BeanFactoryPostProcessor 接口,Spring 会负责调用里面的 postProcessBeanFactory 方法,这是一个扩展方法;
注册 BeanPostProcessor 的实现类,这是在 Bean 初始化前后执行的方法;
初始化国际化,事件广播器的模块,注册事件监听器;
然后 Spring 容器就会创建这些单例 Bean
用到这个 Bean 的时候;利用 getBean 创建 Bean,创建好以后保存在容器中;
统一创建剩下所有的 Bean 的时候;调用 finishBeanFactoryInitialization() 初始化所有剩下的单例 Bean;
最后广播事件,ApplicationContext 初始化 / 刷新完成;

Spring 的后置处理器
BeanPostProcessor:Bean 的后置处理器,主要在 Bean 初始化前后工作;
InstantiationAwareBeanPostProcessor:
继承于 BeanPostProcessor,主要在实例化 Bean 前后工作,AOP 创建代理对象就是通过该接口实现;
BeanFactoryPostProcessor:Bean 工厂的后置处理器,在 Bean 定义(Bean definitions)加载完成后,Bean 尚未初始化前执行;
BeanDefinitionRegistryPostProcessor:继承于 BeanFactoryPostProcessor,其自定义的方法 postProcessBeanDefinitionRegistry 会在 Bean 定义(Bean definitions)将要加载,Bean 尚未初始化前执行,即在 BeanFactoryPostProcessor的postProcessBeanFactory 方法前被调用;
Spring 自动装配 Bean 有哪些方式?
Spring 容器负责创建应用程序中的 Bean,同时通过 ID 来协调这些对象之间的关系,只需要告诉 Spring 要创建哪些 Bean 并且如何将其装配到一起;
Spring 中 Bean 装配有两种方式:
属性注解;
属性自动装配(XML 配置):
no:默认的方式是不进行自动装配的,通过手工设置 ref 属性来进行装配 Bean;
byName:通过 Bean 的名称进行自动装配,如果一个 Bean 的 Property 与另一 Bean Name 相同,就进行自动装配;
byType:通过参数的数据类型进行自动装配;
constructor:利用构造函数进行装配,并且构造函数的参数通过 byType 进行装配;
autodetect:自动探测,如果有构造方法,通过 construct 方式自动装配,否则使用 byType 的方式自动装配;

@Autowired 和 @Resource 区别 ⭐
@Autowired:默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置 required 属性为 false);使用名称装配可以结合 @Qualifier 注解进行使用;
@Resource:默认是按照名称来装配注入的,只有当找不到与名称匹配的 Bean 才会按照类型来装配注入;

ApplicationContext 和 BeanFactory 区别 ⭐

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver {}
BeanFactory:可以理解为就是个 HashMap,Key 是 BeanName,Value 是 Bean 实例,通常只提供实例化对象和获取这两个功能,BeanFactory 在启动的时候不会去实例化 Bean,从容器中拿 Bean 时才会去实例化;
ApplicationContext(应用上下文):可以称之为 “高级容器”,因为比 BeanFactory 多了更多的功能,继承了多个接口,因此具备了更多的功能,如国际化,访问资源,载入多个(有继承关系)上下文 ,消息发送、响应机制,AOP 等;

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值