Spring项目如此强大 , 以至于现在的项目都是依赖Spring搭建,天天与spring打交道,自问一下,你是否真的了解它?
Servlet与Spring的关系
J2EE标准 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>