天天看点

Spring源码(1)Context篇之ContextLoaderServlet与Spring的关系

Spring项目如此强大 , 以至于现在的项目都是依赖Spring搭建,天天与spring打交道,自问一下,你是否真的了解它?

Servlet与Spring的关系

我们经常在web.xml里配置如下代码:

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
           

来查看一下ContextLoaderListener的源码:

package org.springframework.web.context;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
 * 
 * @author Juergen Hoeller
 * @author Chris Beams
 * @since 17.02.2003
 * @see #setContextInitializers
 * @see org.springframework.web.WebApplicationInitializer
 * @see org.springframework.web.util.Log4jConfigListener
 */
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	/**
	 * 类构造方法
	 * @see ContextLoader
	 * @see #ContextLoaderListener(WebApplicationContext)
	 * @see #contextInitialized(ServletContextEvent)
	 * @see #contextDestroyed(ServletContextEvent)
	 */
	public ContextLoaderListener() {
	}

	/**
	 * 类构造方法
	 * method is invoked on this listener.
	 * @param context the application context to manage
	 * @see #contextInitialized(ServletContextEvent)
	 * @see #contextDestroyed(ServletContextEvent)
	 */
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}


	/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}


	/**
	 * Close the root web application context.
	 */
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}

}
           

从源码可以看出来的信息:

  1. Spring的ContextLoaderListener实现自servlet包的ServletContextListener接口(从这里能看出来spring与servlet的依赖关系)
  2. 从类继承关系 extends ContextLoader类,并在contextInitialized方法调用了initWebApplicationContext方法,说明是通过ContextLoader类来实现spring context的初始化的
  3. 其中context销毁的方法,调用了ContextCleanupListener类,该类同样实现了ServletContextListener接口

分析源码的同时可以关联项目启动日志 对应着分析:

十二月 25, 2018 1:35:22 下午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring root WebApplicationContext
[2018-12-25 13:35:22  INFO org.springframework.web.context.ContextLoader:304] Root WebApplicationContext: initialization started
[2018-12-25 13:35:22  INFO org.springframework.web.context.support.XmlWebApplicationContext:583] Refreshing Root WebApplicationContext: startup date [Tue Dec 25 13:35:22 CST 2018]; root of context hierarchy
[2018-12-25 13:35:22  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext-cxf.xml]
[2018-12-25 13:35:23  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from class path resource [META-INF/cxf/cxf.xml]
[2018-12-25 13:35:23  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from class path resource [META-INF/cxf/cxf-servlet.xml]
[2018-12-25 13:35:23  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\cxf-beans-demo.xml]
[2018-12-25 13:35:24  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext-ext.xml]
[2018-12-25 13:35:24  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext-msg.xml]
[2018-12-25 13:35:24  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext-redis.xml]
[2018-12-25 13:35:24  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext-websocket.xml]
[2018-12-25 13:35:24  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext.xml]
[2018-12-25 13:35:26  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:317] Loading XML bean definitions from file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\cxf-beans-demo.xml]
[2018-12-25 13:35:26  INFO com.ctrip.framework.vi.component.ComponentManager:64] vi.appinfo register self to jmx
[2018-12-25 13:35:26  INFO com.ctrip.framework.vi.component.ComponentManager:75] vi.appinfo finish register!
[2018-12-25 13:35:28  WARN org.springframework.beans.factory.support.DefaultListableBeanFactory:1524] Bean creation exception on non-lazy FactoryBean type check: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\spring\applicationContext.xml]: Invocation of init method failed; nested exception is javax.naming.NameNotFoundException: Name [${db.jndiName}] is not bound in this Context. Unable to find [${db.jndiName}].
[2018-12-25 13:35:28  WARN org.springframework.beans.GenericTypeAwarePropertyDescriptor:135] Invalid JavaBean property 'ignoreResourceNotFound' being accessed! Ambiguous write methods found next to actually used [public void org.springframework.core.io.support.PropertiesLoaderSupport.setIgnoreResourceNotFound(boolean)]: [public void qunar.tc.qconfig.client.spring.QConfigPropertyPlaceholderConfigurer.setIgnoreResourceNotFound(boolean)]
[2018-12-25 13:35:28  INFO qunar.tc.qconfig.client.impl.FileStore:237] use remote file, name=config.properties, version=VersionProfile{version=1, profile='fat:'}
[2018-12-25 13:35:29  INFO org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor:155] JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
[2018-12-25 13:35:29  INFO org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:325] Bean 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0' of type [org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[2018-12-25 13:35:30  WARN org.springframework.beans.factory.support.DefaultListableBeanFactory:1524] Bean creation exception on non-lazy FactoryBean type check: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sendRetrieveMapper' defined in file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\com\ctrip\groupfinance\hap\account\mapper\SendRetrieveMapper.class]: Cannot resolve reference to bean 'sqlSessionFactory' while setting bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Property 'dataSource' is required
[2018-12-25 13:35:30  WARN org.springframework.beans.factory.support.DefaultListableBeanFactory:1524] Bean creation exception on non-lazy FactoryBean type check: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userMapper' defined in file [D:\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp2\wtpwebapps\accplatform-job\WEB-INF\classes\com\ctrip\groupfinance\hap\account\mapper\UserMapper.class]: Cannot resolve reference to bean 'sqlSessionFactory' while setting bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Property 'dataSource' is required
[2018-12-25 13:35:30  WARN org.springframework.beans.factory.support.DefaultListableBeanFactory:1524] Bean creation exception ...
...
[2018-12-25 13:37:14  INFO org.springframework.web.context.ContextLoader:344] Root WebApplicationContext: initialization completed in 112076 ms

           

查看Spirng中ContextLoader的源码,其中initWebApplicationContext()方法即为初始化context的核心代码

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
 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);
	 }
	 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);
	     }
		}
	 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;
  }
}
           

从上面源码可以看出,正好与打印的日志相对应!!!

有时候项目里需要自定义一个ContextListener,来实现一些系统级别的操作!

package com.zhoufy.core.web;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.zhoufy.core.ConfigBean;
import com.zhoufy.core.ConfigBeanFactory;

/**
 * @author zhoufy
 * @date 2018年12月25日 下午6:57:54
 */
@WebListener
public class MyServletContextListener implements ServletContextListener {

  private static final Logger logger = LoggerFactory.getLogger(MyServletContextListener.class);

  @Override
  public void contextInitialized(ServletContextEvent event) {
	ServletContext context = event.getServletContext();
	context.log("Initializing MyServletContextListener");
	
	WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(context);
	ConfigBean config = ctx.getBean(ConfigBean.class);
	ConfigBeanFactory.getInstance().setConfig(config);
	context.setAttribute("webPath", "/");
	context.setAttribute("resPath", "/res");
	context.setAttribute("cssPath", "/res/css");
	context.setAttribute("jsPath", "/res/js");
  }
	
  @Override
  public void contextDestroyed(ServletContextEvent arg0) {
	 logger.info("ServletContex contextDestroyed");
  }
}
           

web.xml文件里同样需要添加相关配置:

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--这里的顺序不能反了,因为MyServletContextListener里用的WebApplicationContext是上面的ContextLoaderListener初始化的-->
<listener>
	<listener-class>com.zhoufy.core.web.MyServletContextListener </listener-class>
</listener>
           

继续阅读