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());
}
}
從源碼可以看出來的資訊:
- Spring的ContextLoaderListener實作自servlet包的ServletContextListener接口(從這裡能看出來spring與servlet的依賴關系)
- 從類繼承關系 extends ContextLoader類,并在contextInitialized方法調用了initWebApplicationContext方法,說明是通過ContextLoader類來實作spring context的初始化的
- 其中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>