Spring进阶面试总结

x33g5p2x  于2022-03-18 转载在 Spring  
字(5.0k)|赞(0)|评价(0)|浏览(258)

谈谈你对Spring IOC的理解,原理和实现?

总:
我感觉IOC有两个意思。一个是控制反转这个思想,另一个就是可以把IOC看成一个容器。

分:
控制反转(思想):
原来的对象都是由使用者来创建和控制的,有了Spring之后,可以把整个对象交给spring来帮助我们进行创建和管理。
而与控制反转相近的概念是依赖注入:即应用程序在运行时依赖IOC容器来动态注入对象需要的属性。

容器:
用于储存对象,使用map结构来储存,在Spring中存在三级缓存(勾引面试官提问循环依赖)。singletonObjects存储完整的bean对象。
bean的整个声明周期(勾引面试官提问bean生命周期),从创建到使用到销毁的过程都是由IOC容器来管理。

结束语:
具体的细节我记不太清了,但是Spring中的bean都是通过Java反射的方式生成的(勾引面试官问反射)。初次之外,ioc中重要的几点,就是bean的属性填充和生命周期了。

谈一下Spring IOC的底层实现?

Spring的bean都是通过Java反射机制来创建的。

  1. 首先通过createBeanFactory创建一个Bean工厂(DefaultListableBeanFactory)
  2. 然后开始循环创建对象,因为容器中的bean默认是单例的,所有优先通过getBean、toGetBean方法从容器中查找。
  3. 若找不到的话在通过createBean、toCreateBean方法,以反射的方式创建对象。一般使用的是无参的构造方法(getDeclaredConstructor、newInstance)来创建
  4. 使用populateBean方法进行对象的属性填充
  5. 然后进行其他的初始化操作

Spring中bean的加载过程?

首先从大的几个核心步骤来去说明,因为Spring中的具体加载过程和用到的类实在是太多了。

详细文章链接:https://www.cnblogs.com/lxiaojun/articles/13855948.html

  1. 首先是先从AbstractBeanFactory中去调用doGetBean(name, requiredType, final Object[] args, boolean typeCheckOnly【这个是判断进行创建bean还是仅仅用来做类型检查】)方法,然后第一步要做的就是先去对传入的参数name进行做转换,因为有可能传进来的name=“&XXX”之类,需要去除&符号
  2. 然后接着是去调用getSingleton()方法,其实在上一个面试题中已经提到了这个方法,这个方法就是利用“三级缓存” 来去避免循环依赖问题的出现的。【这里补充一下,只有在是单例的情况下才会去解决循环依赖问题】
  3. 对从缓存中拿到的bean其实是最原始的bean,还未长大,所以这里还需要调用getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd)方法去进行实例化。
  4. 然后会解决单例情况下尝试去解决循环依赖,如果isPrototypeCurrentlyInCreation(beanName)返回为true的话,会继续下一步,否则throw new BeanCurrentlyInCreationException(beanName);
  5. 因为第三步中缓存中如果没有数据的话,就直接去parentBeanFactory中去获取bean,然后判断containsBeanDefinition(beanName)中去检查已加载的XML文件中是否包含有这样的bean存在,不存在的话递归去getBean()获取,如果没有继续下一步
  6. 这一步是吧存储在XML配置文件中的GernericBeanDifinition转换为RootBeanDifinition对象。这里主要进行一个转换,如果父类的bean不为空的话,会一并合并父类的属性
  7. 这一步核心就是需要跟这个Bean有关的所有依赖的bean都要被加载进来,通过刚刚的那个RootBeanDifinition对象去拿到所有的beanName,然后通过registerDependentBean(dependsOnBean, beanName)注册bean的依赖
  8. 然后这一步就是会根据我们在定义bean的作用域的时候定义的作用域是什么,然后进行判断在进行不同的策略进行创建(比如isSingleton、isPrototype)
  9. 这个是最后一步的类型装换,会去检查根据需要的类型是否符合bean的实际类型去做一个类型转换。Spring中提供了许多的类型转换器

Spring中bean的生命周期?

粗粒度的分,生命周期大致为 实例化 -> 属性填充 -> 初始化 -> 销毁这几个阶段,
但是里面还夹杂一些重要的环节。

  1. 首先Spring通过反射机制来实例化bean对象
  2. 紧接着调用populateBean等相关方法为其填充属性。在这个阶段可能会出现循环依赖问题。
  3. 紧接着调用aware接口相关的方法aware的意义在于,方便通过bean对象来获取对应容器中的相关属性。例如实现BeanNameAware接口并实现setBeanName方法便可获取对应的bean名称;实现ApplicationContextAware接口并实现setApplicationContext方法便可通过该bean对象获取应用上下文信息,类似于只有的aware接口还有很多,就不一一说明了。
  4. 调用BeanPostProcesser中的前置处理方法
  5. 然后调用init-method方法。若实现InitializingBean接口,则调用对应的afterPropertiesSet方法。。
  6. 调用BeanPostProcesser中的后置处理方法。正常流程下Spring的AOP就是发生在这个阶段除非是发生了循环依赖现象,进行了提前AOP处理
  7. 最终把完整的对象放入到单例池中。
  8. 最后就是销毁阶段,执行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

  1. 其实刚开始并不会直接去创建A。因为在Spring创建bean之前,每次都是先从容器中去找该bean,若找不到再去创建。Spring会调用getBean、doGetBean、getSingleton等方法到一级缓存中去找,结果发现不存在这个bean。然后才会开始实例化。

  1. Spring利用反射机制实例化A对象。
  2. 然后把A对象放入到三级缓存中。实际上存放的是单例工厂函数对象若后面存在循环依赖现象才会使用该对象,否则不会使用,可以通过里面的getEarlyBeanReference方法获取实例化的A

  1. 开始为A对象注入属性C。
  2. 首先也是先到单例池中去找C,找不到再创建C。
  3. 对C进行实例化。(然后再把它放到三级缓存,和A一样)
  4. 开始为C对象注入属性A。
  5. 到单例池中去找A(getBean -> doGetBean -> getSingleton)。在执行到getSingleton方法的时候发现,A处于正在被创建的状态,此时就可以看出已经发生循环依赖现象了。(接下来的步骤就很关键了 )

  1. 开始从三级缓存中获取A这个半成品对象。 这里是调用工厂函数对象内部的getEarlyBeanReference方法提前暴露半成品对象(这也是解决循环依赖问题的关键)。然后返回这个半成品对象,并把这个对象放入到二级缓存中 并 从一级缓存中删除对应信息。这里若对象A存在AOP的话,该方法会让A提前进行AOP操作,然后返回代理对象,所以说返回的对象可能是初始对象 也可能是代理对象。

  1. 拿到A的半成品对象后,就为C对象成功注入a属性。然后完成了C这个bean的创建。
  2. 紧接着再为A这个bean成功注入c属性。
  3. A完成了bean的创建后,会把这个成品的bean对象放入到一级缓存中 并 删除二级缓存中对应的信息。
  4. 最终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时会先生成一个拦截器链

Spring中AOP的底层实现原理?

相关文章