Spring初始化过程源码分析(1)

时间:2021-07-01 03:52:14

本文主要详细分析Spring初始化过程的源码分析,目的是理解Spring具体是如何工作的。部分内容查阅于网络,有不妥之处望指正。

1、web项目中服务器一启动就开始加载web.xml,Spring的启动是从web.xml中的org.springframework.web.context.ContextLoaderListener监听器(上下文加载监听器)开始的。

<context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/classes/bean-*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

进入ContextLoaderListener类,该类中主要是上下文初始化方法contextInitialized来初始化上下文。

public void contextInitialized(ServletContextEvent event) {
        this.contextLoader = createContextLoader();
        if (this.contextLoader == null) {
            this.contextLoader = this;
        }
        this.contextLoader.initWebApplicationContext(event.getServletContext());
    }

进入initWebApplicationContext方法,主要看下面这部分

if (this.context == null) {
                this.context = createWebApplicationContext(servletContext);
            }
            if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent ->
                        // determine parent for root web application context, if any.
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }

第二行的createWebApplicationContext(servletContext)是创建上下文的方法,进去查看该方法的源码如下:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class<?> contextClass = determineContextClass(sc);
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                    "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
        }
        return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

进入determineContextClass(sc)方法,该方法翻译即为“确定上下文类”,也就是说这个类是能确定上下文的实现类。

protected Class<?> determineContextClass(ServletContext servletContext) {
        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            }
            catch (ClassNotFoundException ex) {
                throw new ApplicationContextException(
                        "Failed to load custom context class [" + contextClassName + "]", ex);
            }
        }
        else {
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            }
            catch (ClassNotFoundException ex) {
                throw new ApplicationContextException(
                        "Failed to load default context class [" + contextClassName + "]", ex);
            }
        }
    }

首先从servletContext中查找是否有上下文类名(contextClassName )即这个servletContext.getInitParameter(CONTEXT_CLASS_PARAM),其中CONTEXT_CLASS_PARAM=“contextClass”是个常量,这个方法要做的就是从web.xml中(如下)查找param-name 为contextClass的参数值,即XmlWebApplicationContext类。

<param-name>contextClass</param-name>  
        <param-value>org.springframework.web.context.support.XmlWebApplicationContext</param-value>  
    </context-param>   

如果web.xml中配置了这段就获取这个contextClassName,没有就用默认的策略contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());其中的defaultStrategies对象就是前面声明的,如下:

private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";

    private static final Properties defaultStrategies;

    static {
        // Load default strategy implementations from properties file.
        // This is currently strictly internal and not meant to be customized
        // by application developers.
        try {
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }

其中用static代码块完成默认的处理方式。ClassPathResource 获取当前路径下的资源文件,从第一行的声明DEFAULT_STRATEGIES_PATH = “ContextLoader.properties”可以看出是叫ContextLoader.properties的文件。如下图:
Spring初始化过程源码分析(1)
其中的内容为:

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

到此获取到了上下文实现类XmlWebApplicationContext。即 determineContextClass(sc)返回的值。下面再回到createWebApplicationContext方法,该方法获取到上下文实现类后返回return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);返回的就是实例化了这个类的对象。注意此处强制转换为ConfigurableWebApplicationContext接口,该接口是WebApplicationContext接口的子接口,XmlWebApplicationContext实现了ConfigurableWebApplicationContext接口。关系如下

Spring初始化过程源码分析(1)
之后就走到了调用createWebApplicationContext方法的initWebApplicationContext方法,运行到这段代码:

if (this.context instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent ->
                        // determine parent for root web application context, if any.
                        ApplicationContext parent = loadParentContext(servletContext);
                        cwac.setParent(parent);
                    }
                    configureAndRefreshWebApplicationContext(cwac, servletContext);
                }
            }

此时会进入这个if代码段,主要看configureAndRefreshWebApplicationContext(cwac, servletContext)这个方法。这个方法第一步先设置上下文id, 再读取web.xml中配置的contextConfigLocation参数值,进一步设置环境,并加载环境初始化参数(会取web.xml中读取,如果有配置的话)

customizeContext(sc, wac); 方法前主要是自定义初始化,只需要实现接口org.springframework.context.ApplicationContextInitializer

wac.refresh(); 这里才是真正加载xml配置, 这里调用了org.springframework.context.support.AbstractApplicationContext的refresh()。

限于篇幅剩下的部分看第二篇详解。