声明

1.建议先阅读《Spring源码分析专题 —— 阅读指引》

2.强烈建议阅读过程中要参照调用过程图,每篇都有其对应的调用过程图

3.写文不易,转载请标明出处

前言

关于 IOC 容器启动的内容很多,我将分上中下三篇讲解,其中上篇相对简单,中篇最为复杂,请大家耐心阅读。

  • 上篇 - 主要是相关基础说明和找到分析入口
  • 中篇 - 讲解定位、加载、注册的过程(实例化在依赖注入的章节再讲)
  • 下篇 - 细节补充

调用过程图

由于篇幅问题,此处我只放个缩略图,高清大图请点击链接☞ IOC容器启动调用过程图.jpg

请务必一边对照图片一边阅读文章。

先放结论

此处先放结论,大家稍微记一记,后边将展开详解

  • Spring 的启动流程主要是定位 -> 加载 -> 注册 -> 实例化

    • 定位 - 获取配置文件路径
    • 加载 - 把配置文件读取成 BeanDefinition
    • 注册 - 存储 BeanDefinition
    • 实例化 - 根据 BeanDefinition 创建实例
  • 所谓的IOC容器其实就是 BeanFactory , BeanFactory 是一个接口,有很多对应的实现类
  • IOC容器的关键入口方法是 refresh()
  • Web 应用中使用的容器是 XmlWebApplicationContext ,其类图如下,可以看出最终是一个实现了 BeanFactory 的类

IOC容器源码的入口

我们知道 Spring 框架不仅仅是面向 Web 应用,所以 Spring 中对应不同场景有许多 IOC 容器的实现类,其中有简单的也有复杂的,在此我们跳过简单容器的讲解,直接以我们最熟悉、也是最感兴趣的 Java Web 项目下手,寻找其对应的 IOC 容器实现类,同时一口气寻找到 IOC 容器的关键入口方法 refresh() 。

1. 寻找IOC容器实现类

以下是我们熟知的 SpringMVC 项目中 web.xml 的基础配置,其关键是要配置一个 ContextLoaderListener 和一个 DispatcherServlet

  1. <web-app>
  2. <context-param>
  3. <param-name>contextConfigLocation</param-name>
  4. <param-value>classpath:spring.xml</param-value>
  5. </context-param>
  6. <!-- ContextLoaderListener -->
  7. <listener>
  8. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  9. </listener>
  10. <!-- DispatcherServlet -->
  11. <servlet>
  12. <description>spring mvc servlet</description>
  13. <servlet-name>springMvc</servlet-name>
  14. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  15. <init-param>
  16. <description>spring mvc</description>
  17. <param-name>contextConfigLocation</param-name>
  18. <param-value>classpath:spring-mvc.xml</param-value>
  19. </init-param>
  20. <load-on-startup>1</load-on-startup>
  21. </servlet>
  22. <servlet-mapping>
  23. <servlet-name>springMvc</servlet-name>
  24. <url-pattern>*.do</url-pattern>
  25. </servlet-mapping>
  26. </web-app>

我们知道在 Java Web 容器中相关组件的启动顺序是 ServletContext -> listener -> filter -> servlet , listener 是优于 servlet 启动的,所以我们先看一看 ContextLoaderListener 的内容

  1. public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
  2. public ContextLoaderListener() {
  3. }
  4. public ContextLoaderListener(WebApplicationContext context) {
  5. super(context);
  6. }
  7. public void contextInitialized(ServletContextEvent event) {
  8. this.initWebApplicationContext(event.getServletContext());
  9. }
  10. public void contextDestroyed(ServletContextEvent event) {
  11. this.closeWebApplicationContext(event.getServletContext());
  12. ContextCleanupListener.cleanupAttributes(event.getServletContext());
  13. }
  14. }

根据 Java Web 容器的规范可知,当 Listener 启动时会调用 contextInitialized 方法,而 ContextLoaderListener 中该方法的内容是继续调用 initWebApplicationContext 方法,于是我们再跟踪 initWebApplicationContext

( ContextLoaderListener 是 ContextLoader 的子类,所以其实是调用了父类的 initWebApplicationContext 方法)

  1. public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
  2. if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
  3. throw new IllegalStateException(
  4. "Cannot initialize context because there is already " +
  5. "a root application context present - check whether " +
  6. "you have multiple ContextLoader* definitions in your web.xml!");
  7. } else {
  8. Log logger = LogFactory.getLog(ContextLoader.class);
  9. servletContext.log("Initializing Spring root WebApplicationContext");
  10. if (logger.isInfoEnabled()) {
  11. logger.info("Root WebApplicationContext: initialization started");
  12. }
  13. long startTime = System.currentTimeMillis();
  14. try {
  15. if (this.context == null) {
  16. this.context = this.createWebApplicationContext(servletContext);
  17. }
  18. .
  19. .
  20. .
  21. }

此处我们关心的是 createWebApplicationContext 方法

  1. protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
  2. /** [note-by-leapmie] determineContextClass方法中获取contextClass **/
  3. Class<?> contextClass = determineContextClass(sc);
  4. if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
  5. throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
  6. "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
  7. }
  8. /** [note-by-leapmie] 根据contextClass返回实例 */
  9. return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
  10. }

从代码可知,方法中的逻辑主要是调用 determineContextClass 获取 contextClass ,然后根据 contextClass 创建 IOC 容器实例。所以, contextClass 的值将是关键。

  1. protected Class<?> determineContextClass(ServletContext servletContext) {
  2. String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
  3. if (contextClassName != null) {
  4. .
  5. .
  6. .
  7. }
  8. else {
  9. /**
  10. * [note-by-leapmie]
  11. * defaultStrategies的值是在本类中的static方法中注入的
  12. * 即该类加载过程中defaultStrategies已经被赋值
  13. * 本类的开始部分有static代码块
  14. * **/
  15. contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
  16. try {
  17. return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
  18. }
  19. catch (ClassNotFoundException ex) {
  20. throw new ApplicationContextException(
  21. "Failed to load default context class [" + contextClassName + "]", ex);
  22. }
  23. }
  24. }

可以看到, contextClassName 是从 defaultStrategies 中获取的,而关于 defaultStrategies 的赋值需要追溯到 ContextLoader 类中的静态代码块

  1. static {
  2. try {
  3. /**
  4. * [note-by-leapmie]
  5. * DEFAULT_STRATEGIES_PATH的值是ContextLoader.properties
  6. */
  7. ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
  8. defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
  9. }
  10. catch (IOException ex) {
  11. throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
  12. }
  13. }

defaultStrategies 是从 resource 中获取的参数,而 resource 又是从 DEFAULT_STRATEGIES_PATH 中获取,查看可知 DEFAULT_STRATEGIES_PATH 的值是 ContextLoader.properties ,通过全局查找到ContextLoader.properties文件,其中内容如下

  1. org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

由此可知, SpringMVC 项目中使用到的 IOC 容器类型是 XmlWebApplicationContext。

2. 寻找关键入口方法refresh()

我们回到 ContextLoader 的 initWebApplicationContext 方法,前边我们说到调用 createWebApplicationContext 方法创建容器,容器创建后我们关注的下一个方法是 configureAndRefreshWebApplicationContext

  1. public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
  2. .
  3. .
  4. .
  5. try {
  6. // Store context in local instance variable, to guarantee that
  7. // it is available on ServletContext shutdown.
  8. if (this.context == null) {
  9. /** [note-by-leapmie] 获取SpringIOC容器类型 **/
  10. this.context = createWebApplicationContext(servletContext);
  11. }
  12. if (this.context instanceof ConfigurableWebApplicationContext) {
  13. ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
  14. if (!cwac.isActive()) {
  15. // The context has not yet been refreshed -> provide services such as
  16. // setting the parent context, setting the application context id, etc
  17. if (cwac.getParent() == null) {
  18. // The context instance was injected without an explicit parent ->
  19. // determine parent for root web application context, if any.
  20. ApplicationContext parent = loadParentContext(servletContext);
  21. cwac.setParent(parent);
  22. }
  23. /** [note-by-leapmie] 配置和刷新容器 **/
  24. configureAndRefreshWebApplicationContext(cwac, servletContext);
  25. }
  26. }
  27. .
  28. .
  29. .
  30. }

configureAndRefreshWebApplicationContext的代码如下

  1. protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
  2. if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
  3. // The application context id is still set to its original default value
  4. // -> assign a more useful id based on available information
  5. String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
  6. if (idParam != null) {
  7. wac.setId(idParam);
  8. }
  9. else {
  10. // Generate default id...
  11. wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
  12. ObjectUtils.getDisplayString(sc.getContextPath()));
  13. }
  14. }
  15. wac.setServletContext(sc);
  16. String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
  17. if (configLocationParam != null) {
  18. wac.setConfigLocation(configLocationParam);
  19. }
  20. // The wac environment's #initPropertySources will be called in any case when the context
  21. // is refreshed; do it eagerly here to ensure servlet property sources are in place for
  22. // use in any post-processing or initialization that occurs below prior to #refresh
  23. ConfigurableEnvironment env = wac.getEnvironment();
  24. if (env instanceof ConfigurableWebEnvironment) {
  25. ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
  26. }
  27. customizeContext(sc, wac);
  28. /** [note-by-leapmie] 调用容器的refresh()方法,此处wac对应的类是XmlWebApplicationContext **/
  29. wac.refresh();
  30. }

在这里我们要关注的是最后一行 wac.refresh() ,意思是调用容器的 refresh() 方法,此处我们的容器是XmlWebApplicationContext,对应的 refresh() 在其父类 AbstractApplicationContext

  1. @Override
  2. /** 核心过程 **/
  3. public void refresh() throws BeansException, IllegalStateException {
  4. synchronized (this.startupShutdownMonitor) {
  5. // Prepare this context for refreshing.
  6. prepareRefresh();
  7. // Tell the subclass to refresh the internal bean factory.
  8. /**
  9. * obtainFreshBeanFactory方法中会调用loadBeanDefinition方法,用于加载bean的定义
  10. */
  11. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  12. // Prepare the bean factory for use in this context.
  13. prepareBeanFactory(beanFactory);
  14. try {
  15. // Allows post-processing of the bean factory in context subclasses.
  16. postProcessBeanFactory(beanFactory);
  17. // Invoke factory processors registered as beans in the context.
  18. invokeBeanFactoryPostProcessors(beanFactory);
  19. // Register bean processors that intercept bean creation.
  20. registerBeanPostProcessors(beanFactory);
  21. // Initialize message source for this context.
  22. initMessageSource();
  23. // Initialize event multicaster for this context.
  24. initApplicationEventMulticaster();
  25. // Initialize other special beans in specific context subclasses.
  26. onRefresh();
  27. // Check for listener beans and register them.
  28. registerListeners();
  29. // Instantiate all remaining (non-lazy-init) singletons.
  30. /** 初始化所有非lazy-init的bean **/
  31. finishBeanFactoryInitialization(beanFactory);
  32. // Last step: publish corresponding event.
  33. finishRefresh();
  34. }
  35. catch (BeansException ex) {
  36. if (logger.isWarnEnabled()) {
  37. logger.warn("Exception encountered during context initialization - " +
  38. "cancelling refresh attempt: " + ex);
  39. }
  40. // Destroy already created singletons to avoid dangling resources.
  41. destroyBeans();
  42. // Reset 'active' flag.
  43. cancelRefresh(ex);
  44. // Propagate exception to caller.
  45. throw ex;
  46. }
  47. finally {
  48. // Reset common introspection caches in Spring's core, since we
  49. // might not ever need metadata for singleton beans anymore...
  50. resetCommonCaches();
  51. }
  52. }
  53. }

至此我们已经找到了关键的入口 refresh() ,我们看一下在调用过程图中我们所处的位置

refresh 方法是 Spring IOC 容器启动过程的核心方法,方法中按顺序调用了好几个命名清晰的方法,其对应的都是 IOC 容器启动过程的关键步骤,更多的细节我们将在下一节继续讲解。

话痨一下

大家可能会觉得,在源码分析过程中一个方法中调用了很多方法,例如先执行方法 a() ,再执行方法 b() ,为什么我们直接看方法 b() 而跳过了方法 a() ?

在这里我想说的是,Spring的源码量很庞大,如果每个细节都去了解可能一年过去了都看不完,我们应该先关注大流程,其他的细枝末节可以在了解了大流程后再慢慢深入了解。

至于为什么是看方法 b() 而跳过方法 a() ,这些都是前人总结的经验与心血,在学习过程中我也是跟着别人的步伐在源码中探索,中间有些缺失的路线我也花费大量时间去踩坑,最后绘制了每一份调用过程图。在本专题中我能确保的是,只要跟着我的步伐,你们不会在源码分析的路上迷路。


本文首发地址:https://blog.leapmie.com/archives/390/

[目录]

[上一篇]Spring源码分析专题 —— 阅读指引

[下一篇]Spring源码分析专题 —— IOC容器启动过程(中篇)


Spring源码分析专题 —— IOC容器启动过程(上篇)的更多相关文章

  1. 【spring源码分析】IOC容器初始化(总结)

    前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正 ...

  2. 【spring源码分析】IOC容器初始化(二)

    前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...

  3. 【spring源码分析】IOC容器初始化(三)

    前言:在[spring源码分析]IOC容器初始化(二)中已经得到了XML配置文件的Document实例,下面分析bean的注册过程. XmlBeanDefinitionReader#registerB ...

  4. 【spring源码分析】IOC容器初始化(四)

    前言:在[spring源码分析]IOC容器初始化(三)中已经分析了BeanDefinition注册之前的一些准备工作,下面将进入BeanDefinition注册的核心流程. //DefaultBean ...

  5. 【spring源码分析】IOC容器初始化(七)

    前言:在[spring源码分析]IOC容器初始化(六)中分析了从单例缓存中加载bean对象,由于篇幅原因其核心函数 FactoryBeanRegistrySupport#getObjectFromFa ...

  6. 【spring源码分析】IOC容器初始化(十)

    前言:前文[spring源码分析]IOC容器初始化(九)中分析了AbstractAutowireCapableBeanFactory#createBeanInstance方法中通过工厂方法创建bean ...

  7. 【spring源码分析】IOC容器初始化——查漏补缺(一)

    前言:在[spring源码分析]IOC容器初始化(十一)中提到了初始化bean的三个步骤: 激活Aware方法. 后置处理器应用(before/after). 激活自定义的init方法. 这里我们就来 ...

  8. 【spring源码分析】IOC容器初始化——查漏补缺(五)

    前言:我们知道在Spring中经常使用配置文件的形式对进行属性的赋值,那配置文件的值是怎么赋值到属性上的呢,本文将对其进行分析. 首先了解一个类:PropertySourcesPlaceholderC ...

  9. 【spring源码分析】IOC容器初始化——查漏补缺(二)

    前言:在[spring源码分析]IOC容器初始化(八)中多次提到了前置处理与后置处理,本篇文章针对此问题进行分析.Spring对前置处理或后置处理主要通过BeanPostProcessor进行实现. ...

随机推荐

  1. Liquibase简介(1)

    Liquibase是一个用于跟踪.管理和应用数据库变化的开源的数据库重构工具.它将所有数据库的变化(包括结构和数据)都保存在XML文件中,便于版本控制. Liquibase具备如下特性: * 不依赖于 ...

  2. IOS--文件管理NSFileManager

    iOS的沙盒机制.应用仅仅能訪问自己应用文件夹下的文件.iOS不像android.没有SD 卡概念.不能直接訪问图像.视频等内容. iOS应用产生的内容,如图像.文件.缓存内容等都必须存储在自己的沙盒 ...

  3. nyoj--19--擅长排列的小明(dfs)

    擅长排列的小明 时间限制:1000 ms  |  内存限制:65535 KB 难度:4 描述 小明十分聪明,而且十分擅长排列计算.比如给小明一个数字5,他能立刻给出1-5按字典序的全排列,如果你想为难 ...

  4. Android 数据库框架总结,总有一个适合你!

    一:OrmLite 简述: 优点: 1.轻量级:2.使用简单,易上手:3.封装完善:4.文档全面.缺点:1.基于反射,效率较低(本人还没有觉得效率低):2.缺少中文翻译文档 jar包 地址:http: ...

  5. java线程深入学习

    一.java中的线程是通过Thread类创建的, //下面是构造函数,一个共同的特点就是:都是调用init()进行创建的 public Thread() { init(null, null, &quo ...

  6. Vue或React多页应用脚手架

    https://github.com/zhujiasheng/vue-multipage https://github.com/MeCKodo/vue-multipage

  7. BZOJ 4430 Guessing Camels

    Description Jaap, Jan, and Thijs are on a trip to the desert after having attended the ACM ICPC Worl ...

  8. Js中的数据类型--String

    昼猫笔记--给你带来不一样的笔记 不止是笔记 更多的是思考 上一期咱们大概了解了下什么是JavaScript,想必大家也都知道 今天主要说下Js中的数据类型 在Js中一共分为六种数据类型 其中基本数据 ...

  9. Java基础String的方法

    Java基础String的方法 字符串类型写法格式如下: 格式一: String 变量名称; 变量名称=赋值(自定义或传入的变量值); 格式二: String 变量名称=赋值(自定义或传入的变量值); ...

  10. android ActionBar的使用

    Action Bar主要功能包括:   1. 显示选项菜单   2. 提供标签页的切换方式的导航功能,能够切换多个fragment.    3.  提供下拉的导航条目.   4. 提供交互式活动视图取 ...