• springMVC源码(1) ContextLoaderListener


    ContextLoaderListener的作用

    在spring官网有这样一幅图(如下),它讲述了web项目中父子容器的概念,从图中可以看到在进行web项目开发的时候我们可以把跟前端比较靠近的一部分(如:Controller,ViewResolver,HandlerMapping)组件放入servlet webApplicationContext这个子容器中,把跟业务逻辑相关的组件(如:Service,Repositories)放在Root webApplicationContext父容器中。当我们需要这些组件的时候,只需要通过子容器就可以拿到,如果在子容器中没找到就会去父容器中找。父子容器并不是web项目特有的,在使用spring作为Bean容器的项目中都可以为一个容器设置父容器。

    这样做的好处在于将组件配置文件分离,不必在一个xml文件中配置,分工明确,减少不必要的错误和麻烦。在web项目中ContextLoaderListener就承担着创建父容器的任务,DispatcherServlet承担创建子容器的任务。

    ContextLoaderListener的结构

    public class ContextLoaderListener extends ContextLoader implements ServletContextListener 
    

    可以看到ContextLoaderListener继承ContextLoader类并实现了ServletContextListener接口。

    public interface ServletContextListener extends EventListener {
        public void contextInitialized(ServletContextEvent sce);
        public void contextDestroyed(ServletContextEvent sce);
    }
    

    可以看到ServletContextListener有两个方法,它们的作用是在servletContext初始化之后和销毁之前做一些事情。

    public class ContextLoader {
        	public WebApplicationContext initWebApplicationContext(ServletContext servletContext);
            protected WebApplicationContext createWebApplicationContext(ServletContext sc);
            protected Class<?> determineContextClass(ServletContext servletContext);
            protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)
            ......
    }
    

    ContextLoader中方法主要是实例化一个webApplicationContext对象即IOC容器。

    对ContextLoaderListener进行配置

      <!-- 初始化spring容器 -->
      <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>
    

    在web.xml文件中进行这样的简单的配置就可以在启动一个web项目的时候创建一个webApplicationContext对象即在web项目中使用的IOC容器,容器会加载你配置的xml文件。

    ContextLoaderListener的初始化

    如我们在web.xml配置的一样,ContextLoaderListener在tomcat中只是一个Listener,从它实现的接口来看它是一个ServletContextListener,前面也介绍了实现了这个接口的类会在一个servletContext初始化的时候被调用。所以从contextInitialized方法跟踪ContextLoaderListener的初始化。

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

    这个方法调用了initWebApplication方法并将一个servletContext作为参数传入。

    	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    		// 第一步: 查看 servletContext是否已经有了webApplicationContext
                    // WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.ROOT
    		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)!= null) {
    			throw new IllegalStateException();
    		}
    			if (this.context == null) {
    				// 第二步:创建一个xmlwebpApplicationContext对象
    				this.context = createWebApplicationContext(servletContext);
    			}
    			if (this.context instanceof ConfigurableWebApplicationContext) {
    				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
    				if (!cwac.isActive()) {
    					if (cwac.getParent() == null) {
    						ApplicationContext parent = loadParentContext(servletContext);
    						cwac.setParent(parent);
    					}
                                            //第三步  配置webApplicationContext并调用其onfresh方法加载xml中的单例bean
    					configureAndRefreshWebApplicationContext(cwac, servletContext);
    				}
    			}
    			//第四步:将application Context 放入servletContext 中
    			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    			return this.context;
    	}
    
    

    可以看到在上面方法中做了这个几件事情:

    1.查看servletContext中是否已经了webApplicationContext(通过查看servletContext是否有属性名为WebApplicationContext.ROOT的属性值来判断的),有就抛出异常,没有则进行下一步。

    2.如果没有通过构造方法传递一个WebApplicationContext对象也就是说this.context == null 的时候就调用createWebApplicationContext方法实例化一个XmlWebApplicationContext对象(ContextLoader 可以通过构造方法传入一个webApplicationContext对象,但是一般我们在web.xml配置的都是使用默认构造方法,所以这里this.context == null)

    3.调用configureAndRefreshWebApplicationContext方法配置webApplicationContext并加载xml中单例Bean。

    4.将webApplicationContext对象放入ServletContext中。

    看看是如何create一个WebApplicationContext对象的。

    	protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
                    //获得WebApplicationContext的class
    		Class<?> contextClass = determineContextClass(sc);
    		.....
                    //利用反射实例化一个contextClass对象
    		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
    	}
    

    接着看是如何获得WebApplicationContext的class对象的。

    protected Class<?> determineContextClass(ServletContext servletContext) {
    	          // 从servletContext中获得参数名为contextclass的参数                          
    		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
    		if (contextClassName != null) {
    			try {
                                    //返回在web.xml中配置的contextClass的Class对象
    				return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
    			}
    			catch (ClassNotFoundException ex) {
    				throw new ApplicationContextException(
    						"Failed to load custom context class [" + contextClassName + "]", ex);
    			}
    		}
    		else {
                            //如果没有在web.xml中设置,则使用默认的。
    			contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
    			try {
    				//返回一个org.springframework.web.context.support.XmlWebApplicationContext Class对象
    				return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
    			}
    			catch (ClassNotFoundException ex) {
    				throw new ApplicationContextException(
    						"Failed to load default context class [" + contextClassName + "]", ex);
    			}
    		}
    	}
    
    

    首先从servletContext中获得属性名为contextclass的属性值,如果没有配置这样的属性,则使用默认的WebApplicationContext类型。

    那么默认的又是如何设置的呢?设置的WebApplicationContext的类型又是什么呢?

    从ContextLoaderListener一段静态代码块看出是加载了ContextLoader.properites文件,

    文件位于spring-websrcmain esourcesorgspringframeworkwebcontextContextLoader.properties

    文件内容是org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext。

    所以通过defaultStrategies.getProperty(WebApplicationContext.class.getName())就获得了webApplicationContext的类型。

    static {
    		try {
    			//  加载了  ContextLoader.properties  中的属性			
    			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
    			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    		}
    		catch (IOException ex) {
    			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
    		}
    	}
    

    从determineContextClass方法中可以看出,我们可以自己配置WebApplicationContext的类型,但是配置的类型一定要是ConfigurableWebApplicationContext 的子类!!!!!

    <context-param>
        <param-name>contextclass</param-name>
        <param-value>....</param-value>
    </context-param>
    

    在执行第三步之前,首先判断WebApplicationContext容器isActive,意思是确定此容器是否处于活动状态,即是否已至少刷新一次并且尚未关闭。刷新指执行容器的onfresh方法,没有关闭是 没有执行close方法。然后在容器的父容器为空的情况下使用loadParentContext方法找出父容器,这个方法直接返回null。

    	protected ApplicationContext loadParentContext(ServletContext servletContext) {
    		return null;
    	}
    

    看看第三步到底做了什么

    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    		// 1.  设置id
    		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
    			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
    			if (idParam != null) {
    				wac.setId(idParam);
    			}
    			else {
    				// Generate default id...
    				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
    						ObjectUtils.getDisplayString(sc.getContextPath()));
    			}
    		}
    		//2. 设置servletContext
    		wac.setServletContext(sc);
                    //3.设置xml文件
    		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    		// 设置configLocation
    		if (configLocationParam != null) {
    			wac.setConfigLocation(configLocationParam);
    		}
    		//4.将servletContext作为属性放入Environment中,可以通过getProperty来获取你配置在servletContext中的initParam参数值
    		ConfigurableEnvironment env = wac.getEnvironment();
    		if (env instanceof ConfigurableWebEnvironment) {
    			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    		}
                    //5.执行实现了ApplicationContextInitializer接口的类
    		customizeContext(sc, wac);
                    //6. 执行refresh方法在单例bean
    		wac.refresh();
    	}
    

    可以看到通过ServletContext获得初始化参数来设置了id,配置文件地址等信息,所以我们也可以自己来自定义这些信息。值得注意的步骤是4,5,6步骤。

    4步骤就是将servletContext作为属性设置到Environment中。

    5步骤主要是执行实现了ApplicationContextInitializer接口的类,这个接口只有一个方法

    public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    	void initialize(C applicationContext);
    }
    

    可以看到initialize 方法传入一个ApplicationContext参数,可以方便实现这个接口的类在执行refresh方法即初始化bean容器之前做一些事情。

    在看看具体是怎么执行的吧

    protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
                    // 获得配置的实现了ApplicationContextInitializer接口的类的class对象
    		List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
    				determineContextInitializerClasses(sc);
    
    		for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
    			...
                            // 利用反射将他们实例化,然后放入contextInitializers中
    			this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
    		}
    
    		AnnotationAwareOrderComparator.sort(this.contextInitializers);
                        //一一执行其initialize方法
    		for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
    			initializer.initialize(wac);
    		}
    	}
    

    在看看determineContextInitializerClasses方法是如何找到我们配置的类的。

    	protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
    			determineContextInitializerClasses(ServletContext servletContext) {
    
    		List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
    				new ArrayList<>();
    		String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
    		if (globalClassNames != null) {
    			for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
    				classes.add(loadInitializerClass(className));
    			}
    		}
    		String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
    		if (localClassNames != null) {
    			for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
    				classes.add(loadInitializerClass(className));
    			}
    		}
    		return classes;
    	}
    

    可以看到 比较重要的两句话 :

    servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);

    servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);

    也就是说 他是从servletContext的初始化参数中查找contextInitializerClasses和globalInitializerClasses参数名的配置

    <context-param>
        <param-name>contextInitializerClasses</param-name>
        <param-value></param-value>
    </context-param>
    <context-param>
        <param-name>globalInitializerClasses</param-name>
        <param-value></param-value>
    </context-param>  
    

    6步骤就是让ApplicationContext启动起来,配置一些信息,加载xml文件,实例化一些singleton bean。

  • 相关阅读:
    HDU 1423
    POJ 3264
    POJ 3177
    CodeForces 81A
    SPOJ RATING
    ZOJ 2588
    POJ 1523
    POJ 3667
    递归
    数据结构
  • 原文地址:https://www.cnblogs.com/zhangchenwei/p/12512195.html
Copyright © 2020-2023  润新知