死磕Spring之IoC篇 - 深入了解Spring IoC(面试题)
本文内容纲要:
-1.什么是SpringFramework?
-2.SpringFramework的优势和不足?
-3.你对IoC的理解?
-4.为什么需要IoC?
-5.IoC和DI的区别?
-6.IoC容器的职责?
-7.什么是SpringIoC容器?
-8.构造器注入和Setter注入
-9.BeanFactory和ApplicationContext谁才是SpringIoC容器?
-10.SpringBean的生命周期?
-11.BeanDefinition是什么?
-12.Spring内建的Bean作用域有哪些?
-13.BeanPostProcessor与BeanFactoryPostProcessor的区别?
-14.依赖注入和依赖查找的来源是否相同?
-15.如何基于ExtensibleXMLauthoring扩展SpringXML元素?
-16.Java泛型擦写发生在编译时还是运行时?
-17.简述Spring事件机制原理?
-18.@EventListener的工作原理?
-19.Spring提供的注解有哪些?
-20.简述SpringEnvironment?
-21.Environment完整的生命周期是怎样的?
-22.Spring应用上下文的生命周期?
-23.Spring应用上下文生命周期有哪些阶段?
-24.简述ObjectFactory?
-25.简述FactoryBean?
-26.ObjectFactory、FactoryBean和BeanFactory的区别?
-27.@Bean的处理流程是怎样的?
-28.BeanFactory是如何处理循环依赖?
-29.Spring中几种初始化方法的执行顺序?
-30.通过@Bean注解定义在方法上面注入一个SpringBean,每次调用该方法所属的Bean的这个方法,得到的是同一个对象吗?
该系列文章是本人在学习Spring的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释Spring源码分析GitHub地址进行阅读
Spring版本:5.1.14.RELEASE
该系列其他文章请查看:《死磕Spring之IoC篇-文章导读》
目录
-
- 什么是SpringFramework?
-
- SpringFramework的优势和不足?
-
- 你对IoC的理解?
-
- 为什么需要IoC?
-
- IoC和DI的区别?
-
- IoC容器的职责?
-
- 什么是SpringIoC容器?
-
- 构造器注入和Setter注入
-
- BeanFactory和ApplicationContext谁才是SpringIoC容器?
-
- SpringBean的生命周期?
-
- BeanDefinition是什么?
-
- Spring内建的Bean作用域有哪些?
-
- BeanPostProcessor与BeanFactoryPostProcessor的区别?
-
- 依赖注入和依赖查找的来源是否相同?
-
- 如何基于ExtensibleXMLauthoring扩展SpringXML元素?
-
- Java泛型擦写发生在编译时还是运行时?
-
- 简述Spring事件机制原理?
-
- @EventListener的工作原理?
-
- Spring提供的注解有哪些?
-
- 简述SpringEnvironment?
-
- Environment完整的生命周期是怎样的?
-
- Spring应用上下文的生命周期?
-
- Spring应用上下文生命周期有哪些阶段?
-
- 简述ObjectFactory?
-
- 简述FactoryBean?
-
- ObjectFactory、FactoryBean和BeanFactory的区别?
-
- @Bean的处理流程是怎样的?
-
- BeanFactory是如何处理循环依赖?
-
- Spring中几种初始化方法的执行顺序?
-
- 通过@Bean注解定义在方法上面注入一个SpringBean,每次调用该方法所属的Bean的这个方法,得到的是同一个对象吗?
1.什么是SpringFramework?
官方文档:
SpringmakesiteasytocreateJavaenterpriseapplications.ItprovideseverythingyouneedtoembracetheJavalanguageinanenterpriseenvironment,withsupportforGroovyandKotlinasalternativelanguagesontheJVM,andwiththeflexibilitytocreatemanykindsofarchitecturesdependingonanapplication’sneeds.
这个问题很难回答,在Spring官方文档中的描述也很抽象,答案在于你对Spring是如何理解的,想必每个人都有自己的回答方式,以下是我个人对于Spring的理解:
整个Spring生态在涉及到Java的项目中被广泛应用,它提供了非常多的组件,能够让你在开发Java应用的过程变得更加容易,弹性地支持其他软件框架,可以比作一个“排插座”,其他软件框架简单地“插上”即可结合Spring一起使用,给开发人员带来了非常多的便利。Spring底层IoC容器的设计实现也是非常完美的,在整个Spring应用上下文的生命周期和SpringBean的生命周期的许多阶段提供了相应的扩展点,供开发者自行扩展,使得框架非常的灵活。
2.SpringFramework的优势和不足?
优势:Spring面向模块进行开发,根据不同的功能进行划分,根据需求引入对应的模块即可,对于开发人员非常友好。例如SpringIoC容器,将我们的Java对象作为SpringBean进行管理,管理着Bean的整个生命周期;SpringMVC提供“模型-视图-控制器”(Model-View-Controller)架构和随时可用的组件,用于开发灵活且松散耦合的Web应用程序;SpringAOP提供面向切面编程的接口,可以很方便的使用;还有许多其他的功能模块,就不一一讲述了。
不足:整个Spring体系比较复杂,对于开发人员需要一定的学习成本,遇到相关问题时需要对底层实现有充分的了解,这也就需要开发人员投入更多的时间和精力去学习。当然,如今Spring体系整合了Java生态非常多的东西,为开发人员带来的便利远大于这些不足,我觉得是有必要对Spring进行充分的学习,去了解Spring的贡献者们的设计思路,对自身也会有很大的提升,从中可以学习到许多的东西。
3.你对IoC的理解?
InversionofControl(IoC)是面向对象中的一种编程思想或原则。可以先回到传统方式,当我依赖一个对象,我需要主动去创建它并进行属性赋值,然后我才能去使用这个对象。对于IoC这种方式来说,它使得对象或者组件的创建更为透明,你不需要过多地关注细节,如创建对象、属性赋值,这些工作交都由IoC容器来完成,已达到解耦的目的。
IoC控制反转,简单来理解其实就是把获取依赖对象的方式,交由IoC容器来实现,由“主动拉取”变为“被动获取”。
4.为什么需要IoC?
实际上,IoC是为了屏蔽构造细节。例如new出来的对象的生命周期中的所有细节对于使用端都是知道的,如果在没有IoC容器的前提下,IoC是没有存在的必要,不过在复杂的系统中,我们的应用更应该关注的是对象的运用,而非它的构造和初始化等细节。
5.IoC和DI的区别?
DI依赖注入不完全等同于IoC,更应该说DI依赖注入是IoC的一种实现方式或策略。
依赖查找和依赖注入都是IoC的实现策略。依赖查找就是在应用程序里面主动调用IoC容器提供的接口去获取对应的Bean对象,而依赖注入是在IoC容器启动或者初始化的时候,通过构造器、字段、setter方法或者接口等方式注入依赖。依赖查找相比于依赖注入对于开发者而言更加繁琐,具有一定的代码入侵性,需要借助IoC容器提供的接口,所以我们总是强调后者。依赖注入在IoC容器中的实现也是调用相关的接口获取Bean对象,只不过这些工作都是在IoC容器启动时由容器帮你实现了,在应用程序中我们通常很少主动去调用接口获取Bean对象。
6.IoC容器的职责?
主要有以下职责:
- 依赖处理,通过依赖查找或者依赖注入
- 管理托管的资源(JavaBean或其他资源)的生命周期
- 管理配置(容器配置、外部化配置、托管的资源的配置)
IoC容器有非常多,例如JDK的JavaBeans,JavaEE的EJB,ApacheAvalon,Googleguice,Spring,其中Spring是最成功的的一个,目前被广泛应用。
其中Spring借鉴了JDK的JavaBeans设计思想,也使用到其中相关类(例如java.beans.PropertyEditor属性编辑器),开发过IDE的GUI界面的伙伴应该对JavaBeans比较熟悉。
7.什么是SpringIoC容器?
Spring框架是一个IoC容器的实现,DI依赖注入是它的实现的一个原则,提供依赖查找和依赖注入两种依赖处理,管理着Bean的生命周期。Spring还提供了AOP抽象、事件抽象、事件监听机制、SPI机制、强大的第三方整合、易测试性等其他特性。
8.构造器注入和Setter注入
构造器注入:通过构造器的参数注入相关依赖对象
Setter注入:通过Setter方法注入依赖对象,也可以理解为字段注入
对于两种注入方式的看法:
- 构造器注入可以避免一些尴尬的问题,比如说状态不确定性地被修改,在初始化该对象时才会注入依赖对象,一定程度上保证了Bean初始化后就是不变的对象,这样对于我们的程序和维护性都会带来更多的便利;
- 构造器注入不允许出现循环依赖,因为它要求被注入的对象都是成熟态,保证能够实例化,而Setter注入或字段注入没有这样的要求;
- 构造器注入可以保证依赖的对象能够有序的被注入,而Setter注入或字段注入底层是通过反射机制进行注入,无法完全保证注入的顺序;
- 如果构造器注入出现比较多的依赖导致代码不够优雅,我们应该考虑自身代码的设计是否存在问题,是否需要重构代码结构。
除了上面的注入方式外,Spring还提供了接口回调注入,通过实现Aware接口(例如BeanNameAware、ApplicationContextAware)可以注入相关对象,Spring在初始化这类Bean时会调用其setXxx方法注入对象,例如注入beanName、ApplicationContext
9.BeanFactory和ApplicationContext谁才是SpringIoC容器?
BeanFactory是Spring底层IoC容器,ApplicationContext是BeanFactory的子接口,是BeanFactory的一个超集,提供IoC容器以外更多的功能。ApplicationContext除了扮演IoC容器角色,还提供了这些企业特性:面向切面(AOP)、配置元信息、资源管理、事件机制、国际化、注解、Environment抽象等。我们一般称ApplicationContext是Spring应用上下文,BeanFactory为Spring底层IoC容器。
10.SpringBean的生命周期?
生命周期:
- SpringBean元信息配置阶段,可以通过面向资源(XML或Properties)、面向注解、面向API进行配置
- SpringBean元信息解析阶段,对上一步的配置元信息进行解析,解析成BeanDefinition对象,该对象包含定义Bean的所有信息,用于实例化一个SpringBean
- SpringBean元信息注册阶段,将BeanDefinition配置元信息保存至BeanDefinitionRegistry的ConcurrentHashMap集合中
- SpringBeanDefinition合并阶段,定义的Bean可能存在层次性关系,则需要将它们进行合并,存在相同配置则覆盖父属性,最终生成一个RootBeanDefinition对象
- SpringBean的实例化阶段,首先的通过类加载器加载出一个Class对象,通过这个Class对象的构造器创建一个实例对象,构造器注入在此处会完成。在实例化阶段Spring提供了实例化前后两个扩展点(InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation、postProcessAfterInstantiation方法)
- SpringBean属性赋值阶段,在Spring实例化后,需要对其相关属性进行赋值,注入依赖的对象。首先获取该对象所有属性与属性值的映射,可能已定义,也可能需要注入,在这里都会进行赋值(反射机制)。提示一下,依赖注入的实现通过CommonAnnotationBeanPostProcessor(@Resource、@PostConstruct、@PreDestroy)和AutowiredAnnotationBeanPostProcessor(@Autowired、@Value)两个处理器实现的。
- Aware接口回调阶段,如果SpringBean是Spring提供的Aware接口类型(例如BeanNameAware、ApplicationContextAware),这里会进行接口的回调,注入相关对象(例如beanName、ApplicationContext)
- SpringBean初始化阶段,这里会调用SpringBean配置的初始化方法,执行顺序:@PostConstruct标注方法、实现InitializingBean接口的afterPropertiesSet()方法、自定义初始化方法。在初始化阶段Spring提供了初始化前后两个扩展点(BeanPostProcessor的postProcessBeforeInitialization、postProcessAfterInitialization方法)
- SpringBean初始化完成阶段,在所有的Bean(不是抽象、单例模式、不是懒加载方式)初始化后,Spring会再次遍历所有初始化好的单例Bean对象,如果是SmartInitializingSingleton类型则调用其afterSingletonsInstantiated()方法,这里也属于Spring提供的一个扩展点
- SpringBean销毁阶段,当Spring应用上下文关闭或者你主动销毁某个Bean时则进入SpringBean的销毁阶段,执行顺序:@PreDestroy注解的销毁动作、实现了DisposableBean接口的Bean的回调、destroy-method自定义的销毁方法。这里也有一个销毁前阶段,也属于Spring提供的一个扩展点,@PreDestroy就是基于这个实现的
- Spring垃圾收集(GC)
总结:
- 上面
1
、2
、3
属于BeanDefinition配置元信息阶段,算是SpringBean的前身,想要生成一个Bean对象,需要将这个Bean的所有信息都定义好; - 其中
4
、5
属于实例化阶段,想要生成一个JavaBean对象,那么肯定需要根据Bean的元信息先实例化一个对象; - 接下来的
6
属于属性赋值阶段,实例化后的对象还是一个空对象,我们需要根据Bean的元信息对该对象的所有属性进行赋值; - 后面的
7
、8
、9
属于初始化阶段,在JavaBean对象生成后,可能需要对这个对象进行相关初始化工作才予以使用; - 最后面的
10
、11
属于销毁阶段,当Spring应用上下文关闭或者主动销毁某个Bean时,可能需要对这个对象进行相关销毁工作,最后等待JVM进行回收。
11.BeanDefinition是什么?
BeanDefinition是SpringBean的“前身”,其内部包含了初始化一个Bean的所有元信息,在Spring初始化一个Bean的过程中需要根据该对象生成一个Bean对象并进行一系列的初始化工作。
12.Spring内建的Bean作用域有哪些?
来源 | 说明 |
---|---|
singleton | 默认SpringBean作用域,一个BeanFactory有且仅有一个实例 |
prototype | 原型作用域,每次依赖查找和依赖注入生成新Bean对象 |
request | 将SpringBean存储在ServletRequest上下文中 |
session | 将SpringBean存储在HttpSession中 |
application | 将SpringBean存储在ServletContext中 |
13.BeanPostProcessor与BeanFactoryPostProcessor的区别?
BeanPostProcessor提供SpringBean初始化前和初始化后的生命周期回调,允许对关心的Bean进行扩展,甚至是替换,其相关子类也提供SpringBean生命周期中其他阶段的回调。
BeanFactoryPostProcessor提供SpringBeanFactory(底层IoC容器)的生命周期的回调,用于扩展BeanFactory(实际为ConfigurableListableBeanFactory),BeanFactoryPostProcessor必须由SpringApplicationContext执行,BeanFactory无法与其直接交互。
14.依赖注入和依赖查找的来源是否相同?
否,依赖查找的来源仅限于SpringBeanDefinition以及单例对象,而依赖注入的来源还包括ResolvableDependency(Spring应用上下文定义的可已处理的注入对象,例如注入BeanFactory注入的是ApplicationContext对象)以及@Value所标注的外部化配置
15.如何基于ExtensibleXMLauthoring扩展SpringXML元素?
SpringXML扩展
- 编写XMLSchema文件(XSD文件):定义XML结构
- 自定义NamespaceHandler实现:定义命名空间的处理器
- 自定义BeanDefinitionParser实现:绑定命名空间下不同的XML元素与其对应的解析器
- 注册XML扩展(
META-INF/spring.handlers
文件):命名空间与命名空间处理器的映射 - 编写SpringSchema资源映射文件(
META-INF/spring.schemas
文件):XMLSchema文件通常定义为网络的形式,在无网的情况下无法访问,所以一般在本地的也有一个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注解是一个通用组件注解,标注这个注解后表明你需要将其作为一个SpringBean进行使用,而其他注解都有各自的作用,例如@Controller及其派生注解用于Web场景下处理HTTP请求,@Configuration注解通常会将这个SpringBean作为一个配置类,也会被CGLIB提供,帮助实现AOP特性。这也是领域驱动设计中的一种思想。
领域驱动设计:Domain-DrivenDesign,简称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 | 启动整个WebMVC模块 | 3.1 |
@EnableTransactionManagement | 启动整个事务管理模块 | 3.1 |
@EnableCaching | 启动整个缓存模块 | 3.1 |
@EnableAsync | 启动整个异步处理模块 | 3.1 |
@Enable模块驱动是以@Enable为前缀的注解驱动编程模型。所谓“模块”是指具备相同领域的功能组件集合,组合所形成一个独立的单元。比如WebMVC模块、AspectJ代理模块、Caching(缓存)模块、JMX(Java管理扩展)模块、Async(异步处理)模块等。
这类注解底层原理就是通过@Import注解导入相关类(ConfigurationClass、ImportSelector接口实现、ImportBeanDefinitionRegistrar接口实现),来实现引入某个模块或功能。
- 条件注解
Spring注解 | 场景说明 | 起始版本 |
---|---|---|
@Conditional | 条件限定,引入某个Bean | 4.0 |
@Profile | 从Spring4.0开始,@Profile基于@Conditional实现,限定Bean的Spring应用环境 | 4.0 |
20.简述SpringEnvironment?
统一Spring配置属性的存储,用于占位符处理和类型转换,还支持更丰富的配置属性源(PropertySource);
通过EnvironmentProfiles信息,帮助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(依赖查找),在SpringBean生命周期的许多节点都能见到该类型的处理器
- 初始化内建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对象并注册。详细步骤可查看后续文章:《死磕Spring之IoC篇-@Bean等注解的实现原理》
28.BeanFactory是如何处理循环依赖?
前言,下面的“循环依赖”换成“循环依赖注入”比较合适,在Spring中通过
depends-on
配置的依赖对象如果出现循环依赖会抛出异常
说明:这里的循环依赖指的是单例模式下的Bean字段注入时出现的循环依赖。构造器注入对于Spring无法自动解决(应该考虑代码设计是否有问题),可通过延迟初始化来处理。Spring只解决单例模式下的循环依赖。
在Spring底层IoC容器BeanFactory中处理循环依赖的方法主要借助于以下3
个Map集合:
singletonObjects
(一级Map),里面保存了所有已经初始化好的单例Bean,也就是会保存SpringIoC容器中所有单例的SpringBean;earlySingletonObjects
(二级Map),里面会保存从三级Map获取到的正在初始化的BeansingletonFactories
(三级Map),里面保存了正在初始化的Bean对应的ObjectFactory实现类,调用其getObject()方法返回正在初始化的Bean对象(仅实例化还没完全初始化好),如果存在则将获取到的Bean对象并保存至二级Map,同时从当前三级Map移除该ObjectFactory实现类。
当通过getBean依赖查找时会首先依次从上面三个Map获取,存在则返回,不存在则进行初始化,这三个Map是处理循环依赖的关键。
例如两个Bean出现循环依赖,A依赖B,B依赖A;当我们去依赖查找A,在实例化后初始化前会先生成一个ObjectFactory对象(可获取当前正在初始化A)保存在上面的singletonFactories
中,初始化的过程需注入B;接下来去查找B,初始B的时候又要去注入A,又去查找A,由于可以通过singletonFactories
直接拿到正在初始化的A,那么就可以完成B的初始化,最后也完成A的初始化,这样就避免出现循环依赖。
问题一:为什么需要上面的二级Map?
因为通过三级Map获取Bean会有相关SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(..)的处理,避免重复处理,处理后返回的可能是一个代理对象
例如在循环依赖中一个Bean可能被多个Bean依赖,A->B(也依赖A)->C->A,当你获取A这个Bean时,后续B和C都要注入A,没有上面的二级Map的话,三级Map保存的ObjectFactory实现类会被调用两次,会重复处理,可能出现问题,这样做在性能上也有所提升
问题二:为什么不直接调用这个ObjectFactory#getObject()方法放入二级Map中,而需要上面的三级Map?
对于不涉及到AOP的Bean确实可以不需要
singletonFactories
(三级Map),但是SpringAOP就是Spring体系中的一员,如果没有singletonFactories
(三级Map),意味着Bean在实例化后就要完成AOP代理,这样违背了Spring的设计原则。Spring是通过AnnotationAwareAspectJAutoProxyCreator
这个后置处理器在完全创建好Bean后来完成AOP代理,而不是在实例化后就立马进行AOP代理。如果出现了循环依赖,那没有办法,只有给Bean先创建代理对象,但是在没有出现循环依赖的情况下,设计之初就是让Bean在完全创建好后才完成AOP代理。
提示:
AnnotationAwareAspectJAutoProxyCreator
是一个SmartInstantiationAwareBeanPostProcessor
后置处理器,在它的getEarlyBeanReference(..)方法中可以创建代理对象。所以说对于上面的问题二,如果出现了循环依赖,如果是一个AOP代理对象,那只能给Bean先创建代理对象,设计之初就是让Bean在完全创建好后才完成AOP代理。
为什么Spring的设计是让Bean在完全创建好后才完成AOP代理?
因为创建的代理对象需要关联目标对象,在拦截处理的过程中需要根据目标对象执行被拦截的方法,所以这个目标对象最好是一个“成熟态”,而不是仅实例化还未初始化的一个对象。
29.Spring中几种初始化方法的执行顺序?
有以下初始化方式:
- Aware接口:实现了Spring提供的相关XxxAware接口,例如BeanNameAware、ApplicationContextAware,其setXxx方法会被回调,可以注入相关对象
- @PostConstruct注解:该注解是JSR-250的标准注解,Spring会调用该注解标注的方法
- InitializingBean接口:实现了该接口,Spring会调用其afterPropertiesSet()方法
- 自定义初始化方法:通过init-method指定的方法会被调用
在Spring初始Bean的过程中上面的初始化方式的执行顺序如下:
Aware
接口的回调- JSR-250
@PostConstruct
标注的方法的调用 InitializingBean#afterPropertiesSet
方法的回调init-method
初始化方法的调用
30.通过@Bean注解定义在方法上面注入一个SpringBean,每次调用该方法所属的Bean的这个方法,得到的是同一个对象吗?
举个例子,大多数情况下,@ConfigurationClass会通过@Bean注解为Bean定义,比如@BeanUseruser(){returnnewUser();},那这样是不是每次主动调用这个方法都会返回一个新的User对象呢?
不是的,@ConfigurationClass在得到CGLIB提升后,会设置一个拦截器专门对@Bean方法进行拦截处理,通过依赖查找的方式从IoC容器中获取Bean对象,如果是单例Bean,那么每次都是返回同一个对象,所以当主动调用这个方法时获取到的都是同一个User对象。
本文内容总结:1.什么是SpringFramework?,2.SpringFramework的优势和不足?,3.你对IoC的理解?,4.为什么需要IoC?,5.IoC和DI的区别?,6.IoC容器的职责?,7.什么是SpringIoC容器?,8.构造器注入和Setter注入,9.BeanFactory和ApplicationContext谁才是SpringIoC容器?,10.SpringBean的生命周期?,11.BeanDefinition是什么?,12.Spring内建的Bean作用域有哪些?,13.BeanPostProcessor与BeanFactoryPostProcessor的区别?,14.依赖注入和依赖查找的来源是否相同?,15.如何基于ExtensibleXMLauthoring扩展SpringXML元素?,16.Java泛型擦写发生在编译时还是运行时?,17.简述Spring事件机制原理?,18.@EventListener的工作原理?,19.Spring提供的注解有哪些?,20.简述SpringEnvironment?,21.Environment完整的生命周期是怎样的?,22.Spring应用上下文的生命周期?,23.Spring应用上下文生命周期有哪些阶段?,24.简述ObjectFactory?,25.简述FactoryBean?,26.ObjectFactory、FactoryBean和BeanFactory的区别?,27.@Bean的处理流程是怎样的?,28.BeanFactory是如何处理循环依赖?,29.Spring中几种初始化方法的执行顺序?,30.通过@Bean注解定义在方法上面注入一个SpringBean,每次调用该方法所属的Bean的这个方法,得到的是同一个对象吗?,
原文链接:https://www.cnblogs.com/lifullmoon/p/14422101.html