【备战秋招】30 道 Spring IOC 经典面试题,kafka 消息中间件原理
Spring Bean 初始化完成阶段,在所有的 Bean(不是抽象、单例模式、不是懒加载方式)初始化后,Spring 会再次遍历所有初始化好的单例 Bean 对象,如果是 SmartInitializingSingleton 类型则调用其 afterSingletonsInstantiated() 方法,这里也属于 Spring 提供的一个扩展点
Spring Bean 销毁阶段,当 Spring 应用上下文关闭或者你主动销毁某个 Bean 时则进入 Spring Bean 的销毁阶段,执行顺序:@PreDestroy 注解的销毁动作、实现了 DisposableBean 接口的 Bean 的回调、destroy-method 自定义的销毁方法。这里也有一个销毁前阶段,也属于 Spring 提供的一个扩展点,@PreDestroy 就是基于这个实现的
Spring 垃圾收集(GC)
总结:
上面?
1
、2
、3
?属于 BeanDefinition 配置元信息阶段,算是 Spring Bean 的前身,想要生成一个 Bean 对象,需要将这个 Bean 的所有信息都定义好;其中?
4
、5
?属于实例化阶段,想要生成一个 Java Bean 对象,那么肯定需要根据 Bean 的元信息先实例化一个对象;接下来的?
6
?属于属性赋值阶段,实例化后的对象还是一个空对象,我们需要根据 Bean 的元信息对该对象的所有属性进行赋值;后面的?
7
、8
?、9
?属于初始化阶段,在 Java Bean 对象生成后,可能需要对这个对象进行相关初始化工作才予以使用;最后面的?
10
、11
?属于销毁阶段,当 Spring 应用上下文关闭或者主动销毁某个 Bean 时,可能需要对这个对象进行相关销毁工作,最后等待 JVM 进行回收。
[](
)11. BeanDefinition 是什么?
BeanDefinition 是 Spring Bean 的“前身”,其内部包含了初始化一个 Bean 的所有元信息,在 Spring 初始化一个 Bean 的过程中需要根据该对象生成一个 Bean 对象并进行一系列的初始化工作。
[](
)12. Spring 内建的 Bean 作用域有哪些?
| 来源 | 说明 |
| --- | --- |
| singleton | 默认 Spring Bean 作用域,一个 BeanFactory 有且仅有一个实例 |
| prototype | 原型作用域,每次依赖查找和依赖注入生成新 Bean 对象 |
| request | 将 Spring Bean 存储在 ServletRequest 上下文中 |
| session | 将 Spring Bean 存储在 HttpSession 中 |
| application | 将 Spring Bean 存储在 ServletContext 中 |
[](
)13. BeanPostProcessor 与 BeanFactoryPostProcessor 的区别?
BeanPostProcessor 提供 Spring Bean 初始化前和初始化后的生命周期回调,允许对关心的 Bean 进行扩展,甚至是替换,其相关子类也提供 Spring Bean 生命周期中其他阶段的回调。
BeanFactoryPostProcessor 提供 Spring BeanFactory(底层 IoC 容器)的生命周期的回调,用于扩展 BeanFactory(实际为 ConfigurableListableBeanFactory),BeanFactoryPostProcessor 必须由 Spring ApplicationContext 执行,BeanFactory 无法与其直接交互。
[](
)14. 依赖注入和依赖查找的来源是否相同?
否,依赖查找的来源仅限于 Spring BeanDefinition 以及单例对象,而依赖注入的来源还包括 Resolvable Dependency(Spring 应用上下文定义的可已处理的注入对象,例如注入 BeanFactory 注入的是 ApplicationContext 对象)以及 @Value 所标注的外部化配置
[](
)15. 如何基于 Extensible XML authoring 扩展 Spring XML 元素?
Spring XML 扩展
编写 XML Schema 文件(XSD 文件):定义 XML 结构
自定义 NamespaceHandler 实现:定义命名空间的处理器
自定义 BeanDefinitionParser 实现:绑定命名空间下不同的 XML 元素与其对应的解析器
注册 XML 扩展(
META-INF/spring.handlers
?文件):命名空间与命名空间处理器的映射编写 Spring Schema 资源映射文件(
META-INF/spring.schemas
?文件):XML Schema 文件通常定义为网络的形式,在无网的情况下无法访问,所以一般在本地的也有一个 XSD 文件,可通过编写?spring.schemas
?文件,将网络形式的 XSD 文件与本地的 XSD 文件进行映射,这样会优先从本地获取对应的 XSD 文件
Mybatis 对 Spring 的集成项目中的?<mybatis:scan />
?标签就是这样实现的,可以参考:[NamespaceHandler](
)、[MapperScannerBeanDefinitionParser](
)、[XSD 等文件](
)
具体实现逻辑参考后续**《解析自定义标签(XML 文件)》**一文
[](
)16. Java 泛型擦写发生在编译时还是运行时?
运行时。编译时,泛型参数类型还是存在的,运行时会忽略。
[](
)17. 简述 Spring 事件机制原理?
主要有以下几个角色:
Spring 事件 - org.springframework.context.ApplicationEvent,实现了 java.util.EventListener 接口
Spring 事件监听器 - org.springframework.context.ApplicationListener,实现了 java.util.EventObject 类
Spring 事件发布器 - org.springframework.context.ApplicationEventPublisher
Spring 事件广播器 - org.springframework.context.event.ApplicationEventMulticaster
Spring 内建的事件:
ContextRefreshedEvent:Spring 应用上下文就绪事件
ContextStartedEvent:Spring 应用上下文启动事件
ContextStoppedEvent:Spring 应用上下文停止事件
ContextClosedEvent:Spring 应用上下文关闭事件
Spring 应用上下文就是一个 ApplicationEventPublisher 事件发布器,其内部有一个 ApplicationEventMulticaster 事件广播器(被观察者),里面保存了所有的 ApplicationListener 事件监听器(观察者)。Spring 应用上下文发布一个事件后会通过 ApplicationEventMulticaster 事件广播器进行广播,能够处理该事件类型的 ApplicationListener 事件监听器则进行处理。
[](
)18. @EventListener 的工作原理?
@EventListener 用于标注在方法上面,该方法则可以用来处理 Spring 的相关事件。
Spring 内部有一个处理器 EventListenerMethodProcessor,它实现了 SmartInitializingSingleton 接口,在所有的 Bean(不是抽象、单例模式、不是懒加载方式)初始化后,Spring 会再次遍历所有初始化好的单例 Bean 对象时会执行该处理器对该 Bean 进行处理。在 EventListenerMethodProcessor 中会对标注了 @EventListener 注解的方法进行解析,如果符合条件则生成一个 ApplicationListener 事件监听器并注册。
[](
)19. Spring 提供的注解有哪些?
核心注解有以下:
Spring 模式注解
| Spring 注解 | 场景说明 | 起始版本 |
| --- | --- | --- |
| @Repository | 数据仓储模式注解 | 2.0 |
| @Component | 通用组件模式注解 | 2.5 |
| @Service | 服务模式注解 | 2.5 |
| @Controller | Web 控制器模式注解 | 2.5 |
| @Configuration | 配置类模式注解 | 3.0 |
Spring 模式注解都是 @Component 的派生注解,Spring 为什么会提供这么多派生注解?
@Component 注解是一个通用组件注解,标注这个注解后表明你需要将其作为一个 Spring Bean 进行使用,而其他注解都有各自的作用,例如 @Controller 及其派生注解用于 Web 场景下处理 HTTP 请求,@Configuration 注解通常会将这个 Spring Bean 作为一个配置类,也会被 CGLIB 提供,帮助实现 AOP 特性。这也是领域驱动设计中的一种思想。
领域驱动设计:Domain-Driven Design,简称 DDD。过去系统分析和系统设计都是分离的,这样割裂的结果导致需求分析的结果无法直接进行设计编程,而能够进行编程运行的代码却扭曲需求,导致客户运行软件后才发现很多功能不是自己想要的,而且软件不能快速跟随需求变化。DDD 则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。
装配注解
| Spring 注解 | 场景说明 | 起始版本 |
| --- | --- | --- |
| @ImportResource | 替换 XML 元素?<import>
| 2.5 |
| @Import | 导入 Configuration 类 | 2.5 |
| @ComponentScan | 扫描指定 package 下标注 Spring 模式注解的类 | 3.1 |
依赖注入注解
| Spring 注解 | 场景说明 | 起始版本 |
| --- | --- | --- |
| @Autowired | Bean 依赖注入,支持多中依赖查找方式 | 2.5 |
| @Qualifier | 细粒度的 @Autowired 依赖查找 | 2.5 |
@Enable 模块驱动
| Spring 注解 | 场景说明 | 起始版本 |
| --- | --- | --- |
| @EnableWebMvc | 启动整个 Web MVC 模块 | 3.1 |
| @EnableTransactionManagement | 启动整个事务管理模块 | 3.1 |
| @EnableCaching | 启动整个缓存模块 | 3.1 |
| @EnableAsync | 启动整个异步处理模块 | 3.1 |
@Enable 模块驱动是以 @Enable 为前缀的注解驱动编程模型。所谓“模块”是指具备相同领域的功能组件集合,组合所形成一个独立的单元。比如 Web MVC 模块、AspectJ 代理模块、Caching(缓存)模块、JMX(Java 管理扩展)模块、Async(异步处理)模块等。
这类注解底层原理就是通过 @Import 注解导入相关类(Configuration Class、 ImportSelector 接口实现、ImportBeanDefinitionRegistrar 接口实现),来实现引入某个模块或功能。
条件注解
| Spring 注解 | 场景说明 | 起始版本 |
| --- | --- | --- |
| @Conditional | 条件限定,引入某个 Bean | 4.0 |
| @Profile | 从 Spring 4.0 开始,@Profile 基于 @Conditional 实现,限定 Bean 的 Spring 应用环境 | 4.0 |
[](
)20. 简述 Spring Environment ?
统一 Spring 配置属性的存储,用于占位符处理和类型转换,还支持更丰富的配置属性源(PropertySource);
通过 Environment Profiles 信息,帮助 Spring 容器提供条件化地装配 Bean。
[](
)21. Environment 完整的生命周期是怎样的?
在 Spring 应用上下文进入刷新阶段之前,可以通过 setEnvironment(Environment) 方法提前设置 Environment 对象,在刷新阶段如果没有 Environment 对象则会创建一个新的 Environment 对象
[](
)22. Spring 应用上下文的生命周期?
Spring 应用上下文就是 ApplicationContext,生命周期主要体现在 org.springframework.context.support.AbstractApplicationContext#refresh() 方法中,大致如下:
Spring 应用上下文启动准备阶段,设置相关属性,例如启动时间、状态标识、Environment 对象
BeanFactory 初始化阶段,初始化一个 BeanFactory 对象,加载出 BeanDefinition 们;设置相关组件,例如 ClassLoader 类加载器、表达式语言处理器、属性编辑器,并添加几个 BeanPostProcessor 处理器
BeanFactory 后置处理阶段,主要是执行 BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor 的处理,对 BeanFactory 和 BeanDefinitionRegistry 进行后置处理,这里属于 Spring 应用上下文的一个扩展点
BeanFactory 注册 BeanPostProcessor 阶段,主要初始化 BeanPostProcessor 类型的 Bean(依赖查找),在 Spring Bean 生命周期的许多节点都能见到该类型的处理器
初始化内建 Bean,初始化当前 Spring 应用上下文的 MessageSource 对象(国际化文案相关)、ApplicationEventMulticaster 事件广播器对象、ThemeSource 对象
Spring 事件监听器注册阶段,主要获取到所有的 ApplicationListener 事件监听器进行注册,并广播早期事件
BeanFactory 初始化完成阶段,主要是初始化所有还未初始化的 Bean(不是抽象、单例模式、不是懒加载方式)
Spring 应用上下文刷新完成阶段,清除当前 Spring 应用上下文中的缓存,例如通过 ASM(Java 字节码操作和分析框架)扫描出来的元数据,并发布上下文刷新事件
Spring 应用上下文启动阶段,需要主动调用 AbstractApplicationContext#start() 方法,会调用所有 Lifecycle 的 start() 方法,最后会发布上下文启动事件
Spring 应用上下文停止阶段,需要主动调用 AbstractApplicationContext#stop() 方法,会调用所有 Lifecycle 的 stop() 方法,最后会发布上下文停止事件
Spring 应用上下文关闭阶段,发布当前 Spring 应用上下文关闭事件,销毁所有的单例 Bean,关闭底层 BeanFactory 容器;注意这里会有一个钩子函数(Spring 向 JVM 注册的一个关闭当前 Spring 应用上下文的线程),当 JVM “关闭” 时,会触发这个线程的运行
总结:
上面的?
1
、2
、3
、4
、5
、6
、7
、8
?都属于 Sping 应用上下文的刷新阶段,完成了 Spring 应用上下文一系列的初始化工作;9
?属于 Spring 应用上下文启动阶段,和 Lifecycle 生命周期对象相关,会调用这些对象的 start() 方法,最后发布上下文启动事件;10
?属于 Spring 应用上下文停止阶段,和 Lifecycle 生命周期对象相关,会调用这些对象的 stop() 方法,最后发布上下文停止事件;11
?属于 Spring 应用上下文关闭阶段,发布上下文关闭事件,销毁所有的单例 Bean,关闭底层 BeanFactory 容器。
[](
)23. Spring 应用上下文生命周期有哪些阶段?
参考 Spring 应用上下文的生命周期:
刷新阶段 - ConfigurableApplicationContext#refresh()
启动阶段 - ConfigurableApplicationContext#start()
停止阶段 - ConfigurableApplicationContext#stop()
关闭阶段 - ConfigurableApplicationContext#close()
[](
)24. 简述 ObjectFactory?
ObjectFactory(或 ObjectProvider) 可关联某一类型的 Bean,仅提供一个 getObject() 方法用于返回目标 Bean 对象,ObjectFactory 对象被依赖注入或依赖查找时并未实时查找到关联类型的目标 Bean 对象,在调用 getObject() 方法才会依赖查找到目标 Bean 对象。
根据 ObjectFactory 的特性,可以说它提供的是延迟依赖查找。通过这一特性在 Spring 处理循环依赖(字段注入)的过程中就使用到了 ObjectFactory,在某个 Bean 还没有完全初始化好的时候,会先缓存一个 ObjectFactory 对象(调用其 getObject() 方法可返回当前正在初始化的 Bean 对象),如果初始化的过程中依赖的对象又依赖于当前 Bean,会先通过缓存的 ObjectFactory 对象获取到当前正在初始化的 Bean,这样一来就解决了循环依赖的问题。
注意这里是延迟依赖查找而不是延迟初始化,ObjectFactory 无法决定是否延迟初始化,而需要通过配置 Bean 的 lazy 属性来决定这个 Bean 对象是否需要延迟初始化,非延迟初始化的 Bean 在 Spring 应用上下文刷新过程中就会初始化。
提示:如果是 ObjectFactory(或 ObjectProvider)类型的 Bean,在被依赖注入或依赖查找时返回的是 DefaultListableBeanFactory#DependencyObjectProvider 私有内部类,实现了?ObjectProvider<T>
?接口,关联的类型为 Object。
[](
)25. 简述 FactoryBean?
FactoryBean 关联一个 Bean 对象,提供了一个 getObject() 方法用于返回这个目标 Bean 对象,FactoryBean 对象在被依赖注入或依赖查找时,实际得到的 Bean 就是通过 getObject() 方法获取到的目标类型的 Bean 对象。如果想要获取 FactoryBean 本身这个对象,在 beanName 前面添加?&
?即可获取。
我们可以通过 FactoryBean 帮助实现复杂的初始化逻辑,例如在 Spring 继集成 MyBatis 的项目中,Mapper 接口没有实现类是如何被注入的?其实 Mapper 接口就是一个 FactoryBean 对象,当你注入该接口时,实际的到的就是其 getObject() 方法返回的一个代理对象,关于数据库的操作都是通过该代理对象来完成。
[](
)26. ObjectFactory、FactoryBean 和 BeanFactory 的区别?
根据其名称可以知道其字面意思分别是:对象工厂,工厂 Bean
ObjectFactory、FactoryBean 和 BeanFactory 均提供依赖查找的能力。
ObjectFactory 提供的是延迟依赖查找,想要获取某一类型的 Bean,需要调用其 getObject() 方法才能依赖查找到目标 Bean 对象。ObjectFactory 就是一个对象工厂,想要获取该类型的对象,需要调用其 getObject() 方法生产一个对象。
FactoryBean 不提供延迟性,在被依赖注入或依赖查找时,得到的就是通过 getObject() 方法拿到的实际对象。FactoryBean 关联着某个 Bean,可以说在 Spring 中它就是某个 Bean 对象,无需我们主动去调用 getObject() 方法,如果想要获取 FactoryBean 本身这个对象,在 beanName 前面添加?
&
?即可获取。BeanFactory 则是 Spring 底层 IoC 容器,里面保存了所有的单例 Bean,ObjectFactory 和 FactoryBean 自身不具备依赖查找的能力,能力由 BeanFactory 输出。
[](
)27. @Bean 的处理流程是怎样的?
Spring 应用上下文生命周期,在 BeanDefinition(@Component 注解、XML 配置)的加载完后,会执行所有 BeanDefinitionRegistryPostProcessor 类型的处理器,Spring 内部有一个 ConfigurationClassPostProcessor 处理器,它会对所有的*
*配置类**进行处理,解析其内部的注解(@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean),其中 @Bean 注解标注的方法会生成对应的 BeanDefinition 对象并注册。
[](
)28. BeanFactory 是如何处理循环依赖?
前言,下面的“循环依赖”换成“循环依赖注入”比较合适,在 Spring 中通过?
depends-on
?配置的依赖对象如果出现循环依赖会抛出异常
说明:这里的循环依赖指的是单例模式下的 Bean?字段注入时出现的循环依赖。构造器注入对于 Spring 无法自动解决(应该考虑代码设计是否有问题),可通过延迟初始化来处理。Spring 只解决单例模式下的循环依赖。
在 Spring 底层 IoC 容器 BeanFactory 中处理循环依赖的方法主要借助于以下?3
?个 Map 集合:
singletonObjects
(一级 Map),里面保存了所有已经初始化好的单例 Bean,也就是会保存 Spring IoC 容器中所有单例的 Spring Bean;earlySingletonObjects
(二级 Map),里面会保存从?三级 Map?获取到的正在初始化的 BeansingletonFactories
(三级 Map),里面保存了正在初始化的 Bean 对应的 ObjectFactory 实现类,调用其 getObject() 方法返回正在初始化的 Bean 对象(仅实例化还没完全初始化好),如果存在则将获取到的 Bean 对象并保存至?二级 Map,同时从当前?三级 Map?移除该 ObjectFactory 实现类。
当通过 getBean 依赖查找时会首先依次从上面三个 Map 获取,存在则返回,不存在则进行初始化,这三个 Map 是处理循环依赖的关键。
评论