SpringBoot热门面试题
前言
本文主要是讲解SpringBoot比较热门、而且有点难理解的面试题,在此过程中,会依次按照参考答案、重要类和接口、以及追踪源码来进行讲解,这三者是紧密联系的,需要来回观看才能弄清楚其中真正的逻辑,希望本篇文章对各位即将成为大厂员工的小伙伴们有帮助,最后希望各位能点赞、收藏加关注,让我有更多的动力输出其他的好文章,最后,祝愿各位offer拿到手软!!
一、SpringBoot启动流程
(1) 参考答案
总: SpringBoot启动,其本质就是加载各种配置信息,然后初始化IOC容器并返回
分:在其启动的过程中会做这么几个事情
首先,当我们在启动类执行SpringApplication.run这行代码的时候,在它的方法内部其实会做两个事情:创建SpringApplication对象,执行run方法。其次,在创建SpringApplication对象的时候,在它的构造方法内部主要做3个事情。
1.确认web应用类型,一般情况下是Servlet类型,这种类型的应用,将来会自动启动一个tomcat
2.从spring.factories配置文件中,加载默认的ApplicationContextInitializer和ApplicationListener
3.记录当前应用的主启动类,将来做包扫描使用
最后,对象创建好了以后,再调用该对象的run方法,在run方法的内部主要做4个事情
- 准备Environment对象,它里面会封装一些当前应用运行环境的参数,比如环境变量等等
- 实例化容器,这里仅仅是创建ApplicationContext对象
- 容器创建好了以后,会为容器做一些准备工作,比如为容器设置Environment、BeanFactoryPostProcessor后置处理器,并且加载主类对应的BeanDefinition
- 刷新容器,就是我们常说的referesh,在这里会真正的创建Bean实例.
总结一下我刚说的,其实SpringBoot启动的时候核心就两步,创建SpringApplication对象以及run方法的调用,在run方法中会真正的实例化容器,并创建容器中需要的Bean实例,最终返回
(2) 追踪源码
(3) 重要类与接口(做了解即可)
SpringBoot框架在设计之初,为了有更好的兼容性,在不同的运行阶段,提供了非常多的扩展点, 可以让程序员根据自己的需求, 在整个Spring应用程序运行过程中执行程序员自定义的代码。
1. ApplicationContextInitializer
作用:
IOC容器对象创建完成后执行,可以对上下文环境做一些操作, 例如运行环境属性注册等
使用:
(1) 自定义类,实现ApplicationContextInitializer接口
(2) 在META-INF/spring.factories配置文件中配置自定义的类
代码:
(1) 自定义类,实现ApplicationContextInitializer接口
public class MyApplicationContextInitializer implements ApplicationContextInitializer {
//Ioc容器创建完毕后执行
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
//给上下文Context注入环境属性
//1.准备属性
//用hashmap方便存储多个环境属性
Map<String, Object> myMap =new HashMap<>();
myMap.put("applicationName","my-set-applicaiton");
//2.获得属性资源管理对象
//2.1 获取环境对象
ConfigurableEnvironment environment = applicationContext.getEnvironment();
//2.2 获得属性资源管理对象
MutablePropertySources propertySources = environment.getPropertySources();
//3.注册
//一个项目中的环境属性可以来自很多地方,可以来自yml,JVM,电脑环境变量
//这里可以通过addFirst()、addLast()、addAfter()、addBefore()来选择我们创造的环境属性插入的位置。
propertySources.addLast(new MapPropertySource("myMap",myMap));
}
}
(2) 在META-INF/spring.factories配置文件中配置自定义的类
这里根据自己的需要进行修改
#接口全路径名称=自定义类的全路径名称 ctrl+alt+空格自动补全
org.springframework.context.ApplicationContextInitializer=com.lk.initializer.MyApplicationContextInitializer
2. ApplicationListener
作用:
监听容器发布的事件, 允许程序员执行自己的代码,完成事件驱动开发, 它可以监听容器初始化完成、初始化失败等事件. 通常情况下可以使用监听器加载资源,开启定时任务等
使用(同上):
(1) 自定义类,实现ApplicationListener接口
(2) 在META-INF/spring.factories配置文件中配置自定义的类
代码:
-
自定义类,实现ApplicationListener接口
public class MyapplicationListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationEvent applicationEvent) { //ApplicationEvent applicationEvent 对应发布的事件,ApplicationEvent是个接口,会有很多的实现类 //所以需要看applicationEvent是什么类型的事件,初始化完成?加载失败? //初始化成功事件 if(applicationEvent instanceof ApplicationReadyEvent){ System.out.println("MyapplicationListener...容器初始化成功..."); } //初始化失败事件 if(applicationEvent instanceof ApplicationFailedEvent){ System.out.println("MyapplicationListener...初始化失败"); } } }
-
在META-INF/spring.factories配置文件中配置自定义的类
#接口全路径名称=自定义类的全路径名称 ctrl+alt+空格自动补全 org.springframework.context.ApplicationListener=com.lk.initializer.MyApplicationContextInitializer
二、IOC容器的初始化流程
(1) 参考答案
总: IOC容器的初始化,核心工作是在AbstractApplicationContext.refresh方法中完成的
分:在refresh方法中主要做了这么几件事
-
准备BeanFactory,在这一块需要给BeanFacory设置很多属性,比如类加载器(classLoader)、Environment等
-
执行BeanFactory后置处理器,这一阶段会扫描要放入到容器中的Bean信息,得到对应的BeanDefinition(注意,这里只扫描,不创建)
-
注册BeanPostProcesor,我们自定义的BeanPostProcessor就是在这一个阶段被加载的, 将来Bean对象实例化好后需要用到
-
启动tomcat
-
实例化容器中实例化非懒加载的单例Bean, 这里需要说的是,多例Bean和懒加载的Bean不会在这个阶段实例化,将来用到的时候再创建
-
当容器初始化完毕后,再做一些扫尾工作,比如清除缓存等
总:简单总结一下,在IOC容器初始化的的过程中,首先得准备并执行BeanFactory后置处理器,其次得注册Bean后置处理器,并启动tomcat,最后需要借助于BeanFactory完成Bean的实例化
(2) 追踪源码
(3) 重要类与接口(做了解即可)
1.BeanFactory
作用:
Bean容器的根接口, 提供Bean对象的创建、配置、依赖注入等功能.
BeanFactory提供了很多的实现类,因为spring里用了很多的设计模式,所以,实现类之间存在相互引用的关系和委托的关系
以ApplicationConfigServletServerApplicationContext为例子,ApplicationConfigServletServerApplicationContext会将getBean任务委托给DefaultListableBeanFactory来做。
备注:BeanFactory常见的两个实现为ApplicationConfigServletServerApplicationContext和DefaultListableBeanFactory
2. BeanDefinition(是个接口)
作用:用于描述Bean,包括Bean的名称,Bean的属性,Bean的行为,实现的接口,添加的注解等等,Spring中,Bean在创建之前,都需要封装成对应的BeanDefinition,然后根据BeanDefinition进一步创建Bean对象,我们常见的一些声明Bean注解在实现BeanDefinition接口会使用不同的实现类。如下所示:
3. BeanFactoryPostProcessor
作用:Bean工厂后置处理器,当BeanFactory准备好了后(Bean初始化之前),会调用该接口的postProcessBeanFactory方法,经常用于新增BeanDefinition
代码:
常见的BeanFactoryPostProcessor实现类和其作用:
4. BeanPostProcessor
作用:
Bean的后置处理器,当Bean对象初始化之前以及初始化之后,会回调该接口对应的方法
常用的两个方法:
postProcessBeforeInitialization: Bean对象初始化之前调用
postProcessAfterInitialization: Bean对象初始化之后
常见实现类:
三、Spring中Bean的生命周期
(1) 参考答案
总: Bean的生命周期总的来说有4个阶段,分别有创建对象,初始化对象,使用对象以及销毁对象,而且这些工作大部分是交给Bean工厂的doCreateBean方法完成的
分: 首先,在创建对象阶段,先调用构造方法实例化对象,对象有了后会填充该对象的内容,其实就是处理依赖注入
其次,对象创建完毕后,需要做一些初始化的操作,在这里涉及到几个扩展点。
1.执行Aware感知接口的回调方法
2.执行Bean后置处理器的postProcessBeforeInitialization方法
3.执行InitializingBean接口的回调,在这一步如果Bean中有标注了@PostConstruct注解的方法,会先执行它
4.执行Bean后置处理器的postProcessAfterInitialization
把这些扩展点都执行完,Bean的初始化就完成了,接下来,在使用阶段就是程序员从容器中获取该Bean使用即可
最后,在容器销毁之前,会先销毁对象,此时会执行DisposableBean接口的回调,这一步如果Bean中有标注了@PreDestroy接口的函数,会先执行它
总:简单总结一下,Bean的生命周期共包含四个阶段,其中初始化对象和销毁对象我们程序员可以通过一些扩展点执行自己的代码
(2) 追踪源码
(3) 重要类与接口(做了解即可)
1.Aware
作用:
感知接口,Spring提供的一种机制,通过实现该接口,重写方法,可以感知Spring应用程序执行过程中的一些变化。Spring会判断当前的Bean有没有实现Aware接口,如果实现了,会在特定的时机回调接口对应的方法
常见子接口:
自定义类实现接口代码:
2. InitializingBean/DisposableBean
作用:
初始化接口,当Bean被实例化好后,会回调里面的函数,经常用于做一些加载资源的工作
销毁接口,当Bean被销毁之前,会回调里面的函数,经常用于做一些释放资源的工作
四、Spring中Bean的循环依赖
(1) 参考答案
总: Bean的循环依赖指的是A依赖B,B又依赖A这样的依赖闭环问题,在Spring中,通过三个对象缓存区来解决循环依赖问题,这三个缓存区被定义到了DefaultSingletonBeanRegistry中,分别是singletonObjects用来存储创建完毕的Bean,earlySingletonObjecs用来存储未完成依赖注入的Bean,还有SingletonFactories用来存储创建Bean的ObjectFactory。假如说现在A依赖B,B依赖A,整个Bean的创建过程是这样的:
首先,调用A的构造方法实例化A,当前的A还没有处理依赖注入,暂且把它称为半成品,此时会把半成品A封装到一个ObjectFactory中,并存储到springFactories缓存区
接下来,要处理A的依赖注入了,由于此时还没有B,所以得先实例化一个B,同样的,半成品B也会被封装到ObjectFactory中,并存储到springFactory缓存区 紧接着,要处理B的依赖注入了,此时会找到springFactories中A对应的ObjecFactory, 调用它的getObject方法得到刚才实例化的半成品A(如果需要代理对象,则会自动创建代理对象,将来得到的就是代理对象),把得到的半成品A注入给B,并同时会把半成品A存入到earlySingletonObjects中,将来如果还有其他的类循环依赖了A,就可以直接从earlySingletonObjects中找到它了,那么此时springFactories中创建A的ObjectFactory也可以删除了
至此,B的依赖注入处理完了后,B就创建完毕了,就可以把B的对象存入到singletonObjects中了,并同时删除掉springFactories中创建B的ObjectFactory
B创建完毕后,就可以继续处理A的依赖注入了,把B注入给A,此时A也创建完毕了,就可以把A的对象存储到singletonObjects中,并同时删除掉earlySingletonObjects中的半成品A
截此为止,A和B对象全部创建完毕,并存储到了singletonObjects中,将来通过容器获取对象,都是从singletonObejcts中获取
总:总结起来还是一句话,借助于DefaultSingletonBeanRegistry的三个缓存区可以解决循环依赖问题
(2) 追踪源码
DefaultSingletonBeanRegistry:
protected void addSingletonFactory(……) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
//往SingletonFactories中注册创建当前对象的工厂
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
protected void addSingleton(……) {
synchronized (this.singletonObjects) {
//把完整品放入到singletonObjects中
this.singletonObjects.put(beanName, singletonObject);
//从springFactories中移除创建当前Bean的工厂
this.singletonFactories.remove(beanName);
//从ealySingletonObjects中移除半成品
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// 从singletonObjects中获取
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 从earlySingletonObects中获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
//从singletonFactories中获取创建该实例的工厂
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//通过工厂获取获取Object, 如果需要代理对象,这里会创建代理对象并返回
singletonObject = singletonFactory.getObject();
//把得到的半成品放入到ealySingletonObjects中
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
五、SpringMvc执行流程
(1) 参考答案
总: 使用了SpringMvc后,所有的请求都需要经过DispatcherServlet前端控制器,该类中提供了一个doDispatch方法,有关请求处理和结果响应的所有流程都在该方法中完成。
首先,借助于HandlerMapping处理器映射器得到处理器执行链,里面封装了HandlerMethod代表目标Controller的方法,同时还通过一个集合记录了要执行的拦截器
接下来,会根据HandlerMethod获取对应的HandlerAdapter处理器适配器,里面封装了参数解析器以及结果处理器 ,然后,执行拦截器的preHandle方法
接下来是核心,通过HandlerAdapter处理器适配器执行目标Controller的方法,在这个过程中会通过参数解析器和结果处理器分别解析浏览器提交的数据以及处理Controller方法返回的结果 ,然后,执行拦截器的postHandle方法,最后处理响应,在这个过程中如果有异常抛出,会执行异常的逻辑,这里还会执行全局异常处理器的逻辑,并通过视图解析器ViewResolver解析视图,再渲染视图,最后再执行拦截器的afterCompletion