Spring源码阅读之WebapplicationContext初始化

Spring源码阅读之WebapplicationContext初始化

​ 当一个Web应用在服务器(tomcat)中启动的时,tomcat需要给该应用创建一个ServletContext对象作为公共容器保存了应用中的配置信息。这些配置信息是通过读取web.xml文件中的标签来获取的。

​ 应用在tomcat中启动时,首先会读取web.xml中两个标签节点<context-param>、<listener>并创建ServletContext对象,将context-param标签中配置文件的位置通过键值对的方式存入该对象中。以后可以通过getInitParameter("")的方式来获取(webapplicationContext的初始化/上下文环境的刷新正是使用这种方法来获取配置文件)

从WebApplicationContext中可以获得ServletContext的引用,整个Spring应用上下文对象将作为属性放置到ServletContext中,以便Web应用环境可以访问Sping应用上下文。
<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring/applicationContext*.xml</param-value>
</context-param>
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

​ 全局的ServletContext对象创建完成之后,接下来就需要对Spring的IOC容器进行初始化,并配置其上下文环境对象WebApplicationContext

​ 将配置文件的信息存入ServletContext对象后,接着读取listener标签并创建ContextLoaderListener对象,该对象继承了ContextLoader和实现了ServletContextListener接口。ServletContextListener是ServletContext的监听者,用来监听对象的创建和销毁,并执行相应的方法。

ContextLoaderListener的作用就是读取配置文件的信息并存入webApplicationContext对象中。由于继承自ContextLoader,所以这一过程由ContextLoader完成。

源码解释:

ContextLoaderListener对象创建的时候会执行contextInitialized来完成初始化工作,具体的初始化流程在ContextLoader中的initWebApplicationContext方法。

public void contextInitialized(ServletContextEvent event) {
     this.initWebApplicationContext(event.getServletContext());
}

initWebApplicationContext创建并初始化Context对象。

private WebApplicationContext context;
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //首先判断该sercontext对象中是否已经有WebApplicationContext对象,如果有表明已经初始化过了,就会报错。
	if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
        } else {
            try {
                if (this.context == null) {
                    //如果WebApplicationContext没有被初始化过,通过该方法创建该对象。
                    this.context = this.createWebApplicationContext(servletContext);
                }
                /*如果创建的WebApplicationContext对象是ConfigurableWebApplicationContext的一个实例(有继承关系)。
                那么就会将创建的对象强制转型为ConfigurableWebApplicationContext*/
                if (this.context instanceof ConfigurableWebApplicationContext) {
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                 	//对象是否处于活跃状态
                    if (!cwac.isActive()) {
                        //是否有父环境对象
                        if (cwac.getParent() == null) {
                            //将ServletContext设置为父环境对象
                            ApplicationContext parent = this.loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }
                        //通过servletContext中的配置信息来刷新ConfigurableWebApplicationContext对象环境
                        this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }
                //将创建并初始化完成的WebApplicationContext对象保存进ServletContext中。
        	servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
           
        }	
}

​ 首先看是如何创建的,并且创建的具体对象是什么。通过defaultStrategies对象来得知实际创建的类名称,并实例化该类,该对象在静态代码块中初始化的,并通过加载ContextLoader.properties文件完成初始化过程。最后实际创建的对象为XmlWebApplicationContext

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
    	//通过determineContextClass来得到具体类
        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) {
    //读取ServletContext中的contextClass的值。一般来说web.xml并不会配置该信息。
        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 {
            //实际上会通过defaultStrategies来得到具体创建的类名。
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

            try {
                //通过类的名称得到该类Class对象。
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            } catch (ClassNotFoundException var5) {
                throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
            }
        }
    }
//通过静态代码块来初始化defaultStrategies对象。
static {
        try {
            //加载资源文件ContextLoader.properties(相对路径) 
            ClassPathResource resource = new ClassPathResource("ContextLoader.properties", ContextLoader.class);
            //通过配置文件类初始化defaultStrategies对象。
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        } catch (IOException var1) {
            throw new IllegalStateException("Could not load 'ContextLoader.properties': " + var1.getMessage());
        }

        currentContextPerThread = new ConcurrentHashMap(1);
    }


//ContextLoader.properties文件内容:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
    
    
//可以看到XmlWebApplicationContext设置了一些默认的值,如果在web.xml中没有指定<context-param>标签那么默认会读取该类中指定的文件。
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
    public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
    public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";
    public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";
}

​ 实际的创建过程了解之后,context对象如何读取我们指定的配置文件的呢?

​ 首先了解一下webApplicationContenxt的继承关系:

在这里插入图片描述

​ 通过图片得知XmlWebApplicationContext继承AbstractRefreshableWebApplicationContextAbstractRefreshableWebApplicationContext实现ConfigurableWebApplicationContext接口,该接口继承了WebApplicationContext接口。

​ 再回到initWebApplicationContext中这段代码:

if (this.context instanceof ConfigurableWebApplicationContext) {
			/*如果创建的WebApplicationContext对象是ConfigurableWebApplicationContext的一个实例(有继承关系)。
                那么就会将创建的对象强制转型为ConfigurableWebApplicationContext*/
                    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
                 	//对象是否处于活跃状态
                    if (!cwac.isActive()) {
                        //是否有父环境对象
                        if (cwac.getParent() == null) {
                            //将ServletContext设置为父环境对象
                            ApplicationContext parent = this.loadParentContext(servletContext);
                            cwac.setParent(parent);
                        }
                        //通过servletContext中的配置信息来刷新ConfigurableWebApplicationContext对象环境
                        this.configureAndRefreshWebApplicationContext(cwac, servletContext);
                    }
                }
                //将创建并初始化完成的WebApplicationContext对象保存进ServletContext中。
        	servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

​ 通过创建过程了解到实际创建的是XmlWebApplicationContext对象,该对象和ConfigurableWebApplicationContext是存在继承关系的。因此这里会发生强制转型,通过configureAndRefreshWebApplicationContext来初始化或者刷新context对象的上下文环境。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
        String configLocationParam;
        if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
            configLocationParam = sc.getInitParameter("contextId");
            if (configLocationParam != null) {
                wac.setId(configLocationParam);
            } else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
            }
        }
		//设置ServletContext环境。
        wac.setServletContext(sc);
    	/*读取Servlet对象中的contextConfigLocation.  ServletContext在创建的时候就读取了
    		<context-param>标签,并将该标签中的配置文件的信息通过键值对的形式保存进来了。
    	*/
        configLocationParam = sc.getInitParameter("contextConfigLocation");
        if (configLocationParam != null) {
            //保存配置文件信息的位置。
            wac.setConfigLocation(configLocationParam);
        }
		//刷新环境
        wac.refresh();
    }

​ 读取servletContext对象中contextConfigLocation的值(配置文件的位置)保存进configureAndRefreshWebApplicationContext对象中,并刷新上下文环境。

​ SpringIOC容器先根据监听初始化WebApplicationContext,然后再初始化web.xml中其他配置的servlet(DispatcherServlet),为其初始化自己的上下文信息servletContext,并加载其设置的配置信息和参数信息到该上下文中,将WebApplicationContext设置为它的父容器。所以最后的关系是ServletContext包含了WebApplicationContext,WebApplicationContext包含了其他的Servlet上下文环境。(DispatcherServlet),为其初始化自己的上下文信息servletContext,并加载其设置的配置信息和参数信息到该上下文中,将WebApplicationContext设置为它的父容器。所以最后的关系是ServletContext包含了WebApplicationContext,WebApplicationContext包含了其他的Servlet上下文环境。

参考:https://www.jianshu.com/p/f8d560962a68

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章