ContextLoaderListener监听器

一、作用

ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext.xml的配置信息。

二、如何使用
<?xml version="1.0" encoding="UTF-8"?>
...省略<web-app>标签...
	<display-name>cassini</display-name>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
	
	<!-- 配置spring核心监听器,默认会以 /WEB-INF/applicationContext.xml作为配置文件 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
	<!—contextConfigLocation用于指定Spring的配置文件 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:application.xml</param-value>
	</context-param>
	
	<servlet>
		<servlet-name>DispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextAttribute</param-name>
			<param-value>org.springframework.web.context.WebApplicationContext.ROOT</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>DispatcherServlet</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>
...省略</web-app>标签...
三、原理

1、org.springframework.web.context.ContextLoaderListener类实现了javax.servlet.ServletContextListener接口,所以Tomcat发布Web应用时执行该类contextInitialized(ServletContextEvent event)方法,代码如下:
在这里插入图片描述
2、执行ContextLoaderListener类中的initWebApplicationContext(event.getServletContext())方法(该方法源自ContextLoader类:ContextLoaderListener类继承自ContextLoader类),源码如下:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
     // 判断application对象是否已经存放了XmlWebApplicationContext实例
     //WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE即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) {
			this.context = createWebApplicationContext(servletContext); // 创建XmlWebApplicationContext
		}
		if (this.context instanceof ConfigurableWebApplicationContext) {// XmlWebApplicationContext类间接实现了ConfigurableWebApplicationContext接口
             // 强制转换为ConfigurableWebApplicationContext类型
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
			if (!cwac.isActive()) {// cwac未被激活,尚没有加载配置文件
				// 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);
			}
		}
         // 将XmlWebApplicationContext实例保存至application内置对象
		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;// 返回XmlWebApplicationContext实例对象
	} 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;
	}
}

3、执行ContextLoaderListener类中的configureAndRefreshWebApplicationContext(cwac, servletContext)方法(该方法源自ContextLoader类:ContextLoaderListener类继承自ContextLoader类),源码如下:

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
		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()));
		}
	}
    
	wac.setServletContext(sc); //为wac绑定ServletContext对象
	String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); //获取web.xml配置文件中context-param标签key为contextConfigLocation的value值
	if (configLocationParam != null) {
		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
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
	}
     
	customizeContext(sc, wac);
	wac.refresh();

4、由于org.springframework.web.servlet.DispatcherServlet配置了load-on-startup,所以Tomcat发布Web应用时依次执行init方法—>initServletBean()方法—>执行initWebApplicationContext()方法,代码如下:

protected WebApplicationContext initWebApplicationContext() {
	WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	WebApplicationContext wac = null;
	
	if (this.webApplicationContext != null) {
		// A context instance was injected at construction time -> use it
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			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 -> set
					// the root application context (if any; may be null) as the parent
					cwac.setParent(rootContext);
				}
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	if (wac == null) {
		// No context instance was injected at construction time -> see if one has been registered in the servlet context. If one exists, it is assumed that the parent context (if any) has already been set and that the user has performed any initialization such as setting the context id
		wac = findWebApplicationContext();
	}
	if (wac == null) {
		// No context instance is defined for this servlet -> create a local one
		wac = createWebApplicationContext(rootContext);// 创建WebApplicationContext对象
	}
	
	if (!this.refreshEventReceived) {// refreshEventReceived全局变量为false
		// Either the context is not a ConfigurableApplicationContext with refresh support or the context injected at construction time had already been refreshed -> trigger initial onRefresh manually here.
		onRefresh(wac);
	}
	
	if (this.publishContext) {
		// Publish the context as a servlet context attribute.
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]");
		}
	}
	
	return wac;
}

5、执行findWebApplicationContext()方法,源码如下:

protected WebApplicationContext findWebApplicationContext() {
	String attrName = getContextAttribute();// 获取web.xml配置文件中配置DispatcherServlet 时所配置的param-name标签key为contextAttribute的value值,即org.springframework.web.context.WebApplicationContext.ROOT
	if (attrName == null) {
		return null;
	}
	WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);// 获取application内置对象中保存的XmlWebApplicationContext实例
	if (wac == null) {
		throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
	}
	return wac;
}

6、执行WebApplicationContextUtils类getWebApplicationContext(getServletContext(), attrName)方法,源码如下:

public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
	Assert.notNull(sc, "ServletContext must not be null");
	Object attr = sc.getAttribute(attrName); // 获取application内置对象中保存的XmlWebApplicationContext实例
	if (attr == null) {
		return null;
	}
	if (attr instanceof RuntimeException) {
		throw (RuntimeException) attr;
	}
	if (attr instanceof Error) {
		throw (Error) attr;
	}
	if (attr instanceof Exception) {
		throw new IllegalStateException((Exception) attr);
	}
	if (!(attr instanceof WebApplicationContext)) {
		throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
	}
	return (WebApplicationContext) attr;
}
四、Root WebApplicationContext和Servlet WebApplicationContext

前面例子我们将Spring相关配置和SpringMVC相关配置一股脑写在了一个xml配置文件,试想如果需要配置的东西很多,那么该文件势必很长,势必很乱,为了解决这一诟病需要按照配置的不同创建两个配置文件:一个配置文件专门用于Spring相关配置;一个配置文件专门用于SpringMVC相关配置,如下图:
在这里插入图片描述
示例:
1、root-context.xml:该文件仅用于实例化Service层和Dao层,用于数据库相关配置;

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
	xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
	
	<!--仅仅实例化被@Component、@Service或@Repository等注解所修饰的类 -->
	<context:component-scan base-package="com.jd">
		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	</context:component-scan>
	
	<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" lazy-init="false" destroy-method="close">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8"></property>
		<property name="username" value="root"></property>
		<property name="password" value="root"></property>
	</bean>
	
	<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"></bean>
	<tx:annotation-driven/>
	
	<bean class="org.mybatis.spring.SqlSessionFactoryBean" 	p:dataSource-ref="dataSource" p:configLocation="classpath:mybatis-config.xml"
		p:mapperLocations="classpath:/sql/*.xml">
	</bean>
	
	<mybatis-spring:scan base-package="com.jd.*.dao" />
</beans>

2、servlet-context.xml:该文件仅用于实例化Controller层,用于视图解析器等SpringMVC相关配置;

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
	
	<!-- 仅实例化被@Controller等注解所修饰的类 -->
	<context:component-scan base-package="com.jd" use-default-filters="false">
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	</context:component-scan>
	
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/view/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>
	
	<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" p:defaultEncoding="UTF-8" p:maxUploadSize="10241000">
	</bean>
	
	<mvc:annotation-driven></mvc:annotation-driven>
</beans>

3、web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	<display-name>cassini</display-name>
	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
	
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
    
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:root-context.xml</param-value>
	</context-param>
    
	<servlet>
		<servlet-name>DispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>DispatcherServlet</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>
</web-app>

原理:
1、 Tomcat发布Web应用,执行ContextLoaderListener类contextInitialized(ServletContextEvent event)方法,该方法用于创建XmlWebApplicationContext实例并将该实例保存至application内置对象;
2、 Tomcat发布Web应用,由于org.springframework.web.servlet.DispatcherServlet配置了load-on-startup,所以依次执行init方法—>initServletBean()方法—>执行initWebApplicationContext()方法,代码如下:

protected WebApplicationContext initWebApplicationContext() {
	WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());// 获取application内置对象中保存的XmlWebApplicationContext实例
	WebApplicationContext wac = null;
	
	if (this.webApplicationContext != null) {
		// A context instance was injected at construction time -> use it
		wac = this.webApplicationContext;
		if (wac instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
			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 -> set
					// the root application context (if any; may be null) as the parent
					cwac.setParent(rootContext);
				}
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	if (wac == null) {
		// No context instance was injected at construction time -> see if one has been registered in the servlet context. If one exists, it is assumed that the parent context (if any) has already been set and that the user has performed any initialization such as setting the context id
		wac = findWebApplicationContext();// 配置org.springframework.web.servlet.DispatcherServlet时未指定contextAttribute所对应的org.springframework.web.context.WebApplicationContext.ROOT值,所以无法获取application内置对象中保存的XmlWebApplicationContext实例。
	}
	if (wac == null) {
		// No context instance is defined for this servlet -> create a local one
		wac = createWebApplicationContext(rootContext);// 创建WebApplicationContext对象
	}
	
	if (!this.refreshEventReceived) {// refreshEventReceived全局变量为false
		// Either the context is not a ConfigurableApplicationContext with refresh support or the context injected at construction time had already been refreshed -> trigger initial onRefresh manually here.
		onRefresh(wac);
	}
	
	if (this.publishContext) {
		// Publish the context as a servlet context attribute.
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]");
		}
	}

3、执行createWebApplicationContext(rootContext)方法,源码如下:

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {//parent指向执行ContextLoaderListener类contextInitialized(ServletContextEvent event)方法时所创建的XmlWebApplicationContext实例
	Class<?> contextClass = getContextClass();//获取org.springframework.web.context.support.XmlWebApplicationContext类所对应Class对象
	if (this.logger.isDebugEnabled()) {
		this.logger.debug("Servlet with name '" + getServletName() +
			"' will try to create custom WebApplicationContext context of class '" +
			contextClass.getName() + "'" + ", using parent context [" + parent + "]");
	}
	if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
		throw new ApplicationContextException(
			"Fatal initialization error in servlet with name '" + getServletName() +
			"': custom WebApplicationContext class [" + contextClass.getName() +
			"] is not of type ConfigurableWebApplicationContext");
	}
	ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	
	wac.setEnvironment(getEnvironment());
	wac.setParent(parent);
	wac.setConfigLocation(getContextConfigLocation());
	
	configureAndRefreshWebApplicationContext(wac);
	
	return wac;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值