SpringBoot | 面试题

基础知识

ApplicationContextInitializer 如何使用 ?
  1. IOC 容器对象创建完成后执行 , 常用于环境属性注册
  2. 自定义类 , 实现 ApplicationContextInitializer 接口
initialize 方法什么时候执行 ?
  1. 在 META-INF/spring.factories 配置文件中配置自定义的类
ApplicationListener 如何使用 ?
  1. IOC 容器发布事件之后执行 , 通常用于资源加载 , 定时任务发布等
  2. 自定义类 , 实现 ApplicationListener 接口
onApplicationEvent 方法什么时候执行 ?
  1. 在 META-INF/spring.factories 配置文件中配置自定义的类
BeanFactory 的作用 ?
  1. ApplicationConfigServletServerApplicationContext
  2. DefaultListableBeanFactory
BeanFactory 常见的两个实现 ?
  1. Bean 容器的根接口 , 提供 Bean 对象的创建、配置、依赖注入等功能

面试题

Q:SpringBoot 启动流程

答:SpringBoot 启动,其本质就是加载各种配置信息,然后初始化 IOC 容器并返回。

在其启动的过程中会做这么几个事情

首先,当我们在启动类执行 SpringApplication.run 这行代码的时候,在它的方法内部其实会做两个事情

    1. 创建 SpringApplication 对象;
    1. 执行 run 方法。

其次,在创建 SpringApplication 对象的时候,在它的构造方法内部主要做 3 个事情。

    1. 确认 web 应用类型,一般情况下是 Servlet 类型,这种类型的应用,将来会自动启动一个 tomcat
    1. 从 spring.factories 配置文件中,加载默认的 ApplicationContextInitializer 和 ApplicationListener
    1. 记录当前应用的主启动类,将来做包扫描使用

最后,对象创建好了以后,再调用该对象的 run 方法,在 run 方法的内部主要做 4 个事情

    1. 准备 Environment 对象,它里面会封装一些当前应用运行环境的参数,比如环境变量等等
    1. 实例化容器,这里仅仅是创建 ApplicationContext 对象
    1. 容器创建好了以后,会为容器做一些准备工作,比如为容器设置 Environment 、 BeanFactoryPostProcessor 后置处理器,并且加载主类对应的 Definition
    1. 刷新容器,就是我们常说的 referesh ,在这里会真正的创建 Bean 实例

总:其实 SpringBoot 启动的时候核心就两步,创建 SpringApplication 对象以及 run 方法的调用,在run 方法中会真正的实例化容器,并创建容器中需要的 Bean 实例,最终返回

Q:IOC 容器的初始化流程

答 : IOC 容器的初始化,核心工作是在 AbstractApplicationContext.refresh 方法中完成的。

在 refresh 方法中主要做了这么几件事

    1. 准备 BeanFactory ,在这一块需要给 BeanFacory 设置很多属性,比如类加载器、 Environment 等
    1. 执行 BeanFactory 后置处理器,这一阶段会扫描要放入到容器中的 Bean 信息,得到对应的 BeanDefinition (注意,这里只扫描,不创建)
    1. 是注册 BeanPostProcesor ,我们自定义的 BeanPostProcessor 就是在这一个阶段被加载的 , 将来 Bean 对象实例化好后需要用到
    1. 启动 tomcat
    1. 实例化容器中实例化非懒加载的单例 Bean, 这里需要说的是,多例 Bean 和懒加载的 Bean 不会在这个阶段实例化,将来用到的时候再创建
    1. 当容器初始化完毕后,再做一些扫尾工作,比如清除缓存等

总:在 IOC 容器初始化的的过程中,首先得准备并执行 BeanFactory 后置处理器,其次得注册 Bean 后置处理器 , 并启动 tomcat ,最后需要借助于 BeanFactory 完成 Bean 的实例化

Q:Bean 生命周期

答:Bean 的生命周期总的来说有 4 个阶段,分别有创建对象,初始化对象,使用对象以及销毁对象,而且这些工作大部分是交给 Bean 工厂的 doCreateBean 方法完成的。

首先,在创建对象阶段,先调用构造方法实例化对象,对象有了后会填充该对象的内容,其实就是处理依赖注入

其次,对象创建完毕后,需要做一些初始化的操作,在这里涉及到几个扩展点。

    1. 执行 Aware 感知接口的回调方法
    1. 执行 Bean 后置处理器的 postProcessBeforeInitialization 方法
    1. 执行 InitializingBean 接口的回调,在这一步如果 Bean 中有标注了 @PostConstruct 注解的方法,会先执行它
    1. 执行 Bean 后置处理器的 postProcessAfterInitialization

把这些扩展点都执行完, Bean 的初始化就完成了

接下来,在使用阶段就是程序员从容器中获取该 Bean 使用即可

最后,在容器销毁之前,会先销毁对象,此时会执行 DisposableBean 接口的回调,这一步如果 Bean 中有标注了@PreDestroy 接口的函数,会先执行它

总:简单总结一下, Bean 的生命周期共包含四个阶段(创建对象,初始化对象,使用对象以及销毁对象),其中初始化对象和销毁对象我们程序员可以通过一些扩展点执行自己的代码

Q:Bean 循环依赖

答: Bean 的循环依赖指的是 A 依赖 B , B 又依赖 A 这样的依赖闭环问题,在 Spring 中,通过三个对象缓存区来解决循环依赖问题,这三个缓存区被定义到了 DefaultSingletonBeanRegistry 中,分别是 singletonObjects 用来存储创建完毕的

Bean , earlySingletonObjecs 用来存储未完成依赖注入的 Bean ,还有 SingletonFactories 用来存储创建 Bean 的ObjectFactory 。假如说现在 A 依赖 B , B 依赖 A ,整个 Bean 的创建过程是这样的:

  • 1.调用 A 的构造方法实例化 A ,当前的 A 还没有处理依赖注入,暂且把它称为半成品,此时会把半成品 A 封装到一个 ObjectFactory 中,并存储到 springFactories 缓存区
  • 2.要处理 A 的依赖注入了,由于此时还没有 B ,所以得先实例化一个 B ,同样的,半成品 B 也会被封装到ObjectFactory 中,并存储到 springFactory 缓存区
  • 3.要处理 B 的依赖注入了,此时会找到 springFactories 中 A 对应的 ObjecFactory, 调用它的 getObject方法得到刚才实例化的半成品 A( 如果需要代理对象 , 则会自动创建代理对象 , 将来得到的就是代理对象 ) ,把得到的半成品 A注入给 B ,并同时会把半成品 A 存入到 earlySingletonObjects 中,将来如果还有其他的类循环依赖了 A ,就可以直接从earlySingletonObjects 中找到它了,那么此时 springFactories 中创建 A ObjectFactory 也可以删除了
  • 4.B 的依赖注入处理完了后, B 就创建完毕了,就可以把 B 的对象存入到 singletonObjects 中了,并同时删除掉 springFactories 中创建 B 的 ObjectFactory
  • 5.B 创建完毕后,就可以继续处理 A 的依赖注入了,把 B 注入给 A ,此时 A 也创建完毕了,就可以把 A 的对象存储到singletonObjects 中,并同时删除掉 earlySingletonObjects 中的半成品 A

截此为止, A 和 B 对象全部创建完毕,并存储到了 singletonObjects 中,将来通过容器获取对象,都是从singletonObejcts 中获取
总:总结起来还是一句话,借助于 DefaultSingletonBeanRegistry 的三个缓存区可以解决循环依赖问题

Q:SpringMvc 执行流程

答 : 使用了 SpringMvc 后,所有的请求都需要经过 DispatcherServlet 前端控制器,该类中提供了一个 doDispatch 方法,有关请求处理和结果响应的所有流程都在该方法中完成

  • 1.借助于 HandlerMapping 处理器映射器得到处理器执行链,里面封装了 HandlerMethod 代表目标Controller 的方法,同时还通过一个集合记录了要执行的拦截器
  • 2.会根据 HandlerMethod 获取对应的 HandlerAdapter 处理器适配器,里面封装了参数解析器以及结果处理器
  • 3.执行拦截器的 preHandle 方法
  • 4.通过 HandlerAdapter 处理器适配器执行目标 Controller 的方法,在这个过程中会通过参数解析器和结果处理器分别解析浏览器提交的数据以及处理 Controller 方法返回的结果
  • 5.执行拦截器的 postHandle 方法
  • 6.处理响应,在这个过程中如果有异常抛出,会执行异常的逻辑,这里还会执行全局异常处理器的逻辑,并通过视图解析器 ViewResolver 解析视图,再渲染视图,最后再执行拦截器的 afterCompletion
相关推荐
wmd131643067122 分钟前
IDEA插件CamelCase,快速转变命名格式
java·ide·intellij-idea
捂月36 分钟前
Spring Boot 核心逻辑与工作原理详解
java·spring boot·后端
埋头编程~38 分钟前
【C++】踏上C++学习之旅(十):深入“类和对象“世界,掌握编程黄金法则(五)(最终篇,内含初始化列表、静态成员、友元以及内部类等等)
java·c++·学习
菜鸟起航ing1 小时前
Java中日志采集框架-JUL、Slf4j、Log4j、Logstash
java·开发语言·log4j·logback
Nightselfhurt1 小时前
RPC学习
java·spring boot·后端·spring·rpc
苹果醋31 小时前
vue3 在哪些方便做了性能提升?
java·运维·spring boot·mysql·nginx
孔汤姆1 小时前
部署实战(二)--修改jar中的文件并重新打包成jar文件
java·pycharm·jar
荆州克莱1 小时前
Vue3 源码解析(三):静态提升
spring boot·spring·spring cloud·css3·技术
Abladol-aj2 小时前
并发和并行的基础知识
java·linux·windows
清水白石0082 小时前
从一个“支付状态不一致“的bug,看大型分布式系统的“隐藏杀机“
java·数据库·bug