天天看點

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>
           

繼續閱讀