Spring启动并加载(源码解析)

Spring启动并加载(源码解析)

一、材料

1. web.xml

      <context-param>
        	<param-name>contextConfigLocation</param-name>
        	<param-value>classpath:config/spring.xml</param-value>
      </context-param>
      
      <!-- Spring监听器 -->
      <listener>
        	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>    	

2. ContextLoaderListener

package org.springframework.web.context;

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
	……
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }
	……
}

3.ContextLoader

package org.springframework.web.context;

public class ContextLoader {
    ……
    private static final Properties defaultStrategies;
    ……    
    private WebApplicationContext context;
    ……
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            ……
        } else {
            ……
            try {
                if (this.context == null) {
                    this.context = this.createWebApplicationContext(servletContext);
                }
                ……
                return this.context;
            } catch (RuntimeException var8) {
               ……
            } catch (Error var9) {
                ……
            }
        }
    }
        
    ……
        
    protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    	Class<?> contextClass = this.determineContextClass(sc);
    	if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
       	 	throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        } else {
            return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
        }
    }
   		
    ……
   		
    protected Class<?> determineContextClass(ServletContext servletContext) {
        String contextClassName = servletContext.getInitParameter("contextClass");
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            } catch (ClassNotFoundException var4) {
                	throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
            }
        } else {
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            } catch (ClassNotFoundException var5) {
                throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
            }
        }
    }
    
    ……
    
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        String configLocationParam;
        ……
        configLocationParam = sc.getInitParameter("contextConfigLocation");
        ……
        wac.refresh();
    }
    
    ……
    
}

二、流程

执行顺序

  1. web.xml 中配置的Spring监听器(ContextLoaderListener)被加载并调用上下文初始化方法(public void contextInitialized(ServletContextEvent event))

  2. 上下文初始化方法中调用父类(ContextLoader)方法(public WebApplicationContext initWebApplicationContext(ServletContext servletContext))去初始化Web应用上下文(this.initWebApplicationContext(event.getServletContext());)

  3. 现在我们的视野应该是在ContextLoader类中的initWebApplicationContext方法上,这个方法中忽略掉其他代码后,重点在于里面有一句this.context = this.createWebApplicationContext(servletContext);这句调用完后会返回一个被WebApplicationContext 接收的对象。

  4. 我简单介绍下WebApplicationContext 是什么。先参考文章了解ApplicationContext以及它的相关的实现子类
    WebApplicationContext 是ApplicationContext的子接口。总之是用来接收Spring上下文对象的东西。

  5. 接着,ContextLoader#createWebApplicationContext方法中做了什么事呢?它通过调用ContextLoader#determineContextClass方法确定了上下文的类并实例化该类得到对象后强转成ConfigurableWebApplicationContext返回给步骤4中的WebApplicationContext 接收。

  6. ContextLoader#determineContextClass做了什么事呢?determineContextClass这个方法我还没细看,大体上意思是如果servletContext中找不到“contextClass”对应的类名,那就走默认策略,从defaultStrategies中取,主要在这一句上contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());一般默认情况取出来的contextClassName值是“org.springframework.web.context.support.XmlWebApplicationContext”,而XmlWebApplicationContext是从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。

  7. 现在程序已经知道要以xml配置方式去加载Spring上下文,那么只剩下如何去读取到web.xml中配置好的contextConfigLocation值得到xml链接并加载了。

  8. ContextLoader#initWebApplicationContext方法中的this.context = this.createWebApplicationContext(servletContext);执行完后默认情况会得到这样的值,例如《Spring启动并加载(源码解析)》
    接着方法中再执行到调用this.configureAndRefreshWebApplicationContext(cwac, servletContext);这句就时用来读取到web.xml中配置好的contextConfigLocation值得到xml链接并加载了。

  9. ContextLoader#configureAndRefreshWebApplicationContext这个方法中的configLocationParam = sc.getInitParameter(“contextConfigLocation”);这句获取到了web.xml中配置好的contextConfigLocation值,也就是“classpath:config/spring-mybatis.xml”,这个方法在最后的时候调用了wac.refresh();

  10. 而这个refresh方法的实现是在XmlWebApplicationContext的父类AbstractApplicationContext里面实现的。refresh里面完成了WebApplicationContext里面的beanfactory的初始化和bean载入,beanfactorypostprocessor的调用,beanpostprocessor的注册,ApplicationEvent的监听和注册,non-lazy-init的bean的初始化。
    换言之,已经把该准备的都准备好了,只需要有请求来获取bean,就根据情况或返回已经初始化的bean或进行bean的Instantiation 和 Initialization。

参考

Spring-MVC理解之一:应用上下文webApplicationContext
spring容器和上下文的理解
spring源码分析之——Spring bean的加载入口
Sping提供了两种类型的 IOC 容器实现.
Spring基础篇——Spring容器和应用上下文理解

PS

“为避免误人子弟,以上仅供参考,个人见解,请配合多个相关博客食用……”

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/qq_29405977/article/details/83342699
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞