Spring进阶面试总结
谈谈你对Spring IOC的理解,原理和实现?
总:
我感觉IOC有两个意思。一个是控制反转这个思想,另一个就是可以把IOC看成一个容器。
分:
控制反转(思想):
原来的对象都是由使用者来创建和控制的,有了Spring之后,可以把整个对象交给spring来帮助我们进行创建和管理。
而与控制反转相近的概念是依赖注入:即应用程序在运行时依赖IOC容器来动态注入对象需要的属性。
容器:
用于储存对象,使用map结构来储存,在Spring中存在三级缓存(勾引面试官提问循环依赖)。singletonObjects存储完整的bean对象。
bean的整个声明周期(勾引面试官提问bean生命周期),从创建到使用到销毁的过程都是由IOC容器来管理。
结束语:
具体的细节我记不太清了,但是Spring中的bean都是通过Java反射的方式生成的(勾引面试官问反射)。初次之外,ioc中重要的几点,就是bean的属性填充和生命周期了。
谈一下Spring IOC的底层实现?
Spring的bean都是通过Java反射机制来创建的。
- 首先通过createBeanFactory创建一个Bean工厂(DefaultListableBeanFactory)
- 然后开始循环创建对象,因为容器中的bean默认是单例的,所有优先通过getBean、toGetBean方法从容器中查找。
- 若找不到的话在通过createBean、toCreateBean方法,以反射的方式创建对象。一般使用的是无参的构造方法(getDeclaredConstructor、newInstance)来创建
- 使用populateBean方法进行对象的属性填充
- 然后进行其他的初始化操作
Spring中bean的加载过程?
首先从大的几个核心步骤来去说明,因为Spring中的具体加载过程和用到的类实在是太多了。
详细文章链接:https://www.cnblogs.com/lxiaojun/articles/13855948.html
- 首先是先从AbstractBeanFactory中去调用doGetBean(name, requiredType, final Object[] args, boolean typeCheckOnly【这个是判断进行创建bean还是仅仅用来做类型检查】)方法,然后第一步要做的就是先去对传入的参数name进行做转换,因为有可能传进来的name=“&XXX”之类,需要去除&符号
- 然后接着是去调用getSingleton()方法,其实在上一个面试题中已经提到了这个方法,这个方法就是利用“三级缓存” 来去避免循环依赖问题的出现的。【这里补充一下,只有在是单例的情况下才会去解决循环依赖问题】
- 对从缓存中拿到的bean其实是最原始的bean,还未长大,所以这里还需要调用getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd)方法去进行实例化。
- 然后会解决单例情况下尝试去解决循环依赖,如果isPrototypeCurrentlyInCreation(beanName)返回为true的话,会继续下一步,否则throw new BeanCurrentlyInCreationException(beanName);
- 因为第三步中缓存中如果没有数据的话,就直接去parentBeanFactory中去获取bean,然后判断containsBeanDefinition(beanName)中去检查已加载的XML文件中是否包含有这样的bean存在,不存在的话递归去getBean()获取,如果没有继续下一步
- 这一步是吧存储在XML配置文件中的GernericBeanDifinition转换为RootBeanDifinition对象。这里主要进行一个转换,如果父类的bean不为空的话,会一并合并父类的属性
- 这一步核心就是需要跟这个Bean有关的所有依赖的bean都要被加载进来,通过刚刚的那个RootBeanDifinition对象去拿到所有的beanName,然后通过registerDependentBean(dependsOnBean, beanName)注册bean的依赖
- 然后这一步就是会根据我们在定义bean的作用域的时候定义的作用域是什么,然后进行判断在进行不同的策略进行创建(比如isSingleton、isPrototype)
- 这个是最后一步的类型装换,会去检查根据需要的类型是否符合bean的实际类型去做一个类型转换。Spring中提供了许多的类型转换器
Spring中bean的生命周期?
粗粒度的分,生命周期大致为 实例化 -> 属性填充 -> 初始化 -> 销毁
这几个阶段,
但是里面还夹杂一些重要的环节。
- 首先Spring通过反射机制来实例化bean对象
- 紧接着调用
populateBean
等相关方法为其填充属性。在这个阶段可能会出现循环依赖问题。 - 紧接着调用aware接口相关的方法。aware的意义在于,方便通过bean对象来获取对应容器中的相关属性。例如实现BeanNameAware接口并实现setBeanName方法便可获取对应的bean名称;实现ApplicationContextAware接口并实现setApplicationContext方法便可通过该bean对象获取应用上下文信息,类似于只有的aware接口还有很多,就不一一说明了。
- 调用BeanPostProcesser中的前置处理方法。
- 然后调用init-method方法。若实现InitializingBean接口,则调用对应的afterPropertiesSet方法。。
- 调用BeanPostProcesser中的后置处理方法。正常流程下Spring的AOP就是发生在这个阶段,除非是发生了循环依赖现象,进行了提前AOP处理。
- 最终把完整的对象放入到单例池中。
- 最后就是销毁阶段,执行DisposableBean接口的destory方法、执行自己配置的destory-method方法。
Spring中Bean初始化的三种方法
通过实现 InitializingBean/DisposableBean 接口来定制初始化之后/销毁之前的操作方法;
通过 元素的 init-method/destroy-method属性指定初始化之后 /销毁之前调用的操作方法;
在指定方法上加上@PostConstruct 或@PreDestroy注解来制定该方法是在初始化之后还是销毁之前调用。
什么是循环依赖?
循环依赖B站上视频链接:
https://www.bilibili.com/video/BV1U3411t7cy?p=11(源码讲解)
https://www.bilibili.com/video/BV1ET4y1N7Sp(为什么要用三级缓存?)
提一嘴:1、只有单例情况下回出现循环依赖问题。 2、若bean的属性填充是用的构造方法,则无法解决循环依赖问题
简单举个例子:A对象依赖B对象,B对象依赖A对象。
那么循环依赖是个问题吗?
如果不考虑spring,单纯的在java中循环依赖并不会出现问题,因为对象之间相互依赖是很正常的事情
但是,在Spring中若不进行特别处理,循环依赖就会成为一个问题,为什么?
因为在Spring中,一个对象并不是简单的new出来可以了。它是一个bean,存在一系列的生命周期:bean先进行实例化,然后属性填充紧随其后,然后进行初始化等操作。
就是因为bean的生命周期所以才会出现循环依赖问题。
描述一下Spring是如何解决循环依赖问题的?
先解释 什么是循环依赖?
注意要提到这几点:1、三级缓存 2、提前暴露对象 3、AOP
解决循环依赖涉及到三级缓存,我先介绍一下这三级缓存是啥:
- 一级缓存:singletonObjects,也可以称之为单例池,保存完成初始化的成品单例bean。
- 二级缓存:earlySingletonObjects,保存提前暴露的单例bean对象(可以叫做半成品对象);它的存在就是用来解决bean创建过程中的循环依赖现象。
- 三级缓存:singletonFactories,保存
单例工厂函数对象
;它的存在主要用来处理存在循环依赖现象 且 bean存在AOP的情况。
接下来我简单叙述一下Spring解决循环依赖的过程:
以A和C两个bean相互依赖为例,假如先加载A后加载C
-
其实刚开始并不会直接去创建A。因为在Spring创建bean之前,每次都是先从容器中去找该bean,若找不到再去创建。Spring会调用
getBean、doGetBean、getSingleton
等方法到一级缓存中去找,结果发现不存在这个bean。然后才会开始实例化。
-
Spring利用反射机制实例化A对象。
-
然后把A对象放入到三级缓存中。实际上存放的是
单例工厂函数对象
(若后面存在循环依赖现象才会使用该对象,否则不会使用,可以通过里面的getEarlyBeanReference
方法获取实例化的A)
-
开始为A对象注入属性C。
-
首先也是先到单例池中去找C,找不到再创建C。
-
对C进行实例化。(然后再把它放到三级缓存,和A一样)
-
开始为C对象注入属性A。
-
到单例池中去找A(getBean -> doGetBean -> getSingleton)。在执行到
getSingleton
方法的时候发现,A处于正在被创建的状态,此时就可以看出已经发生循环依赖现象了。(接下来的步骤就很关键了 )
-
开始从三级缓存中获取A这个半成品对象。 这里是调用工厂函数对象内部的
getEarlyBeanReference
方法提前暴露半成品对象(这也是解决循环依赖问题的关键)。然后返回这个半成品对象,并把这个对象放入到二级缓存中 并 从三级缓存中删除对应信息。这里若对象A存在AOP的话,该方法会让A提前进行AOP操作,然后返回代理对象,所以说返回的对象可能是初始对象 也可能是代理对象。
-
拿到A的半成品对象后,就为C对象成功注入a属性。然后完成了C这个bean的创建。
-
紧接着再为A这个bean成功注入c属性。
-
A完成了bean的创建后,会把这个成品的bean对象放入到一级缓存中 并 删除二级缓存中对应的信息。
-
最终A和C这两个bean都成功被创建。
可不可以不使用三级缓存,只使用二级缓存?或者三级缓存的作用?
其实若不存在AOP,用两级缓存也是可以解决循环依赖问题的。
但若bean需要AOP处理 且 存在循环依赖现象 。我们实际想要的是代理对象、单例池中存放的应该是代理对象。
若没有三级缓存,也就不会有提前AOP这种处理。那么在二级缓存中存放的就是原始对象、也就是没有经过AOP处理的,最终放入到一级缓存在的也是初始对象、而不是代理对象。这样就出现问题了。
因此我们需要通过三级缓存中单例工厂函数对象
中的getEarlyBeanReference
方法来判断是否存在AOP,是否要进行提前AOP处理,最后把代理对象暴露出去。所以说三级缓存是有必要的。
BeanFactory 和 FactoryBean有什么区别?
相同点: 他俩都是接口
不同点:
- BeanFactory是Spring容器的顶级接⼝,给具体的IOC容器的实现提供了规范。FactoryBean也是接⼝,为IOC容器中Bean的实现提供了更加灵活的⽅式,FactoryBean在IOC容器的基础上给Bean的实现加上了⼀个简单⼯⼚模式和装饰模式。
- 按照传统的方式创建bean对象时,需要遵循严格的生命周期流程,过程太复杂了。 如果想要简单的自定义某个对象的创建,同时把创建完成的对象交给Spring来管理,那么就需要实现FactoryBean接口。
需要实现如下几个方法:
- isSingleTon:是否是单例对象
- getObjectType:获取返回对象的类型
- getObject:自定义创建对象的过程
Spring中用到的设计模式?
单例模式:bean默认是单例的
工厂模式:BeanFactory
代理模式:AOP动态代理
装饰者模式:BeanWrapper
适配器模式:Adapter
原型模式:指定作用域为prototype
责任链模式:使用AOP时会先生成一个拦截器链