• Spring源码情操陶冶-ContextLoader


    前言-阅读源码有利于陶冶情操,本文承接前文Spring源码情操陶冶-ContextLoaderListener

    静态代码块内容

    ContextLoader在被主动调用的时候,会执行其的一个静态块,代码如下

    static {
    		// Load default strategy implementations from properties file.
    		// This is currently strictly internal and not meant to be customized
    		// by application developers.
    		try {
    		        //这里DEFAULT_STRATEGIES_PATH值为ContextLoader.properties
    			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
    			//这里的loadProperties方法其实调用的getClass().getResourceAsStream(path)方法
    			//其是从当前包路径下查找相应的资源,点开相应的class路径,果不其然ContextLoader.properties与ContextLoader.class在同一目录下
    			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    		}
    		catch (IOException ex) {
    			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
    		}
    	}
    

    具体的解析功能见注释,上面分析到其会读取ContextLoader.properties,这个文件下只有一个属性:
    org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

    由此可见其最有可能是通过XmlWebApplicationContext类来进行相应的web上下文初始化

    初始化方法initWebApplicationContext

    从前文得知,ContextLoaderListener会调用ContextLoader#initWebApplicationContext(ServletContext context)方法来创建web application上下文环境,代码清单如下

    //首先判断有无org.springframework.web.context.WebApplicationContext.ROOT属性,不允许已有
    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!");
    		}
    
    		Log logger = LogFactory.getLog(ContextLoader.class);
    		servletContext.log("Initializing Spring root WebApplicationContext");
    		if (logger.isInfoEnabled()) {
    			logger.info("Root WebApplicationContext: initialization started");
    		}
    		//用来计算初始化这个过程会耗费多少时间
    		long startTime = System.currentTimeMillis();
    
    		try {
    			// Store context in local instance variable, to guarantee that
    			// it is available on ServletContext shutdown.
    			if (this.context == null) {
    				//创建方法,一般会创建前点所述的XmlWebApplicationContext
    				this.context = createWebApplicationContext(servletContext);
    			}
    			//XmlWebApplicationContext是ConfigurableWebApplicationContext的实现类,指定的contextClass应该是ConfigurableWebApplicationContext的实现类
    			if (this.context instanceof ConfigurableWebApplicationContext) {
    				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
    				//一般来说刚创建的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.
    						//在web.xml中配置了<context-param>的parentContextKey才会指定父级应用
    						ApplicationContext parent = loadParentContext(servletContext);
    						cwac.setParent(parent);
    					}
    					//读取相应的配置并且刷新context对象
    					configureAndRefreshWebApplicationContext(cwac, servletContext);
    				}
    			}
                            //设置属性,避免再被初始化			
                            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    
    			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
    			if (ccl == ContextLoader.class.getClassLoader()) {
    				currentContext = this.context;
    			}
    			else if (ccl != null) {
    				currentContextPerThread.put(ccl, this.context);
    			}
    
    			if (logger.isDebugEnabled()) {
    				logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
    						WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
    			}
    			if (logger.isInfoEnabled()) {
    				long elapsedTime = System.currentTimeMillis() - startTime;
    				logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
    			}
    
    			return this.context;
    		}
    		catch (RuntimeException ex) {
    			logger.error("Context initialization failed", ex);
    			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
    			throw ex;
    		}
    		catch (Error err) {
    			logger.error("Context initialization failed", err);
                            //即使初始化失败仍不允许有再次的初始化			
                            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
    			throw err;
    		}
    

    上述的代码片段中主要用到了两个关键方法createWebApplicationContext(ServletContext context)configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac,ServletContext sc),本文必须对这两个方法进行相应的解读

    ContextLoader#createWebApplicationContext(ServletContext context)

    代码清单如下

                    //决定采用默认的contextClass还是ServletContext中的参数属性contextClass
                    Class<?> contextClass = determineContextClass(sc);
    		//这里Spring强调指定的contextClass必须是ConfigurableWebApplicationContext的实现类或者子类
    		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
    			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
    					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    		}
    		//这里就是实例化指定的contextClass
    		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    

    实现的主要功能为创建ConfigurableWebApplicationContext对象,这里可以稍微瞄一眼determineContextClass(ServletContext context)

                //优先从ServletContext取contextClass参数对应的值,即查看是否在web.xml中配置
                //对应的contextClass<context-param>参数值
                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 {
    			//倘若不存在,则加载指定的XmlWebApplicationContext类
    			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);
    			}
    		}
    

    ContextLoader#configureAndRefreshWebApplicationContext()

    代码清单如下

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc){
    //一般此处为真
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
    			// The application context id is still set to its original default value
    			// -> assign a more useful id based on available information
    			//获取servletContext中的contextId属性
    			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
    			if (idParam != null) {
    				//存在则设为指定的id名
    				wac.setId(idParam);
    			}
    			else {
    				// Generate default id... 一般为org.springframework.web.context.WebApplicationContext:${contextPath}
    				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
    						ObjectUtils.getDisplayString(sc.getContextPath()));
    			}
    		}
    
    		wac.setServletContext(sc);
    		//读取contextConfigLocation属性
    		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    		if (configLocationParam != null) {
    			//设置指定的spring文件所在地,支持classpath前缀并多文件,以,;为分隔符
    			wac.setConfigLocation(configLocationParam);
    		}
    
    		// The wac environment's #initPropertySources will be called in any case when the context
    		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
    		// use in any post-processing or initialization that occurs below prior to #refresh
    		//获得真实对象为StandardEnvironment 其非ConfigurableWebEnvironment的实现类
    		ConfigurableEnvironment env = wac.getEnvironment();
    		if (env instanceof ConfigurableWebEnvironment) {
    			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    		}
    		//查看是否有ApplicationContextInitializer<C extends ConfigurableApplicationContext>启动类,其实是web.xml需要指定globalInitializerClasses参数或者contextInitializerClasses参数,前提是指定的这些类的泛型类必须是wac的父类或者与wac类相同,否则就会有异常抛出
    		customizeContext(sc, wac);
    		//调用AbstractApplicationContext的refresh方法实现加载spring文件等操作
    		wac.refresh();
    }
    

    可见关键处理还是在AbstractApplicationContext#refresh()方法,前面都是refresh()方法的准备工作,包括指定contextConfigLocationSpring配置文件位置、给应用一个id倘若指定了contextId属性、如果指定了globalInitializerClasses或者contextInitializerClasses参数则调用其中的initialize()方法

    记录总结

    1. ContextLoader在web.xml配置文件中没有指定contextClass的属性下会默认加载XmlWebApplicationContext类,如果指定了contextClass属性,Spring强调指定的contextClass必须是ConfigurableWebApplicationContext的实现类或者子类

    2. web.xml配置文件中如果没有指定contextId属性,则WebApplicationContext的id为org.springframework.web.context.WebApplicationContext:${contextPath},其中${contextPath}是项目的上下文路径,例如localhost:8080/test/info/query路径下的contextPath为/test

    3. web.xml配置的contextConfigLocation属性支持多文件,以,;为分隔符,不指定默认会加载applicationContext.xml,其在后续章节剖析

    4. web.xml倘若指定globalInitializerClasses参数或者contextInitializerClasses参数,具体要求如下
      * 指定的类实现ApplicationContextInitializer<C extends ConfigurableApplicationContext>接口
      * 指定的这些类中的泛型类必须是contextClass(默认为XmlWebApplicationContext)的父类或者一致,否则就会有异常抛出

    下节预告

    Spring源码情操陶冶-AbstractApplicationContext

  • 相关阅读:
    Geometry
    后缀数组dc3算法模版(待补)
    CodeForces 467D(267Div2-D)Fedor and Essay (排序+dfs)
    HDU 3572 Task Schedule (最大流)
    Acdream手速赛7
    hdu2732 Leapin' Lizards (网络流dinic)
    HDU 3549 Flow Problem (最大流ISAP)
    HDU 1532 Drainage Ditches (网络流)
    [容易]合并排序数组 II
    [容易]搜索插入位置
  • 原文地址:https://www.cnblogs.com/question-sky/p/6694627.html
Copyright © 2020-2023  润新知