【Spring】浅谈ContextLoaderListener及其上下文与DispatcherServlet的区别
本文内容纲要:
一般在使用SpingMVC开发的项目中,一般都会在web.xml文件中配置ContextLoaderListener监听器,如下:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在开始讲解这个之前先讲讲web工程的上下文,对于一个web容器,web容器提供了一个全局的上下文环境,这个上下文就是ServletContext,其为后面SpringIOC容器提供宿主环境。
在web容器启动时会触发容器初始化事件,contextLoaderListener监听到这个事件后其contextInitialized方法就会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文就是根上下文,也就是WebApplicationContext,实际实现类一般是XmlWebApplicationContext,这个其实就是spring的IoC容器,这个IoC容器初始化完后,Spring会将它存储到ServletContext,可供后面获取到该IOC容器中的bean。
下面一步步来跟进,看下ContextLoaderListener源码:
publicclassContextLoaderListenerextendsContextLoaderimplementsServletContextListener{
publicContextLoaderListener(){
}
publicContextLoaderListener(WebApplicationContextcontext){
super(context);
}
@Override
publicvoidcontextInitialized(ServletContextEventevent){
initWebApplicationContext(event.getServletContext());
}
@Override
publicvoidcontextDestroyed(ServletContextEventevent){
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
从上面可以看出ContextLoaderListener继承ContextLoader类并实现了ServletContextListener接口,ServletContextListener接口中只有初始化和销毁的两个方法,如下:
publicinterfaceServletContextListenerextendsEventListener{
/**
**Notificationthatthewebapplicationinitialization
**processisstarting.
**AllServletContextListenersarenotifiedofcontext
**initializationbeforeanyfilterorservletintheweb
**applicationisinitialized.
*/
publicvoidcontextInitialized(ServletContextEventsce);
/**
**Notificationthattheservletcontextisabouttobeshutdown.
**Allservletsandfiltershavebeendestroy()edbeforeany
**ServletContextListenersarenotifiedofcontext
**destruction.
*/
publicvoidcontextDestroyed(ServletContextEventsce);
}
ContextLoaderListener主要的功能还是在继承的ContextLoader类中实现,接下来看看ContextLoaderListener中上下文初始化的方法,也就是:
/**
*Initializetherootwebapplicationcontext.
*/
@Override
publicvoidcontextInitialized(ServletContextEventevent){
initWebApplicationContext(event.getServletContext());
}
跟进initWebApplicationContext()方法,其调用的实现就在ContextLoader类中:
/**
*InitializeSpring'swebapplicationcontextforthegivenservletcontext,
*usingtheapplicationcontextprovidedatconstructiontime,orcreatinganewone
*accordingtothe"{@link#CONTEXT_CLASS_PARAMcontextClass}"and
*"{@link#CONFIG_LOCATION_PARAMcontextConfigLocation}"context-params.
*@paramservletContextcurrentservletcontext
*@returnthenewWebApplicationContext
*@see#ContextLoader(WebApplicationContext)
*@see#CONTEXT_CLASS_PARAM
*@see#CONFIG_LOCATION_PARAM
*/
publicWebApplicationContextinitWebApplicationContext(ServletContextservletContext){
//先判断ServletContext中是否已存在上下文,有的话说明已加载或配置信息有误(看下面抛出的异常信息)
if(servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)!=null){
thrownewIllegalStateException(
"Cannotinitializecontextbecausethereisalreadyarootapplicationcontextpresent-"+
"checkwhetheryouhavemultipleContextLoader*definitionsinyourweb.xml!");
}
Loglogger=LogFactory.getLog(ContextLoader.class);
servletContext.log("InitializingSpringrootWebApplicationContext");
if(logger.isInfoEnabled()){
logger.info("RootWebApplicationContext:initializationstarted");
}
longstartTime=System.currentTimeMillis();
try{
//Storecontextinlocalinstancevariable,toguaranteethat
//itisavailableonServletContextshutdown.
if(this.context==null){
//创建WebApplicationContext上下文
this.context=createWebApplicationContext(servletContext);
}
if(this.contextinstanceofConfigurableWebApplicationContext){
ConfigurableWebApplicationContextcwac=(ConfigurableWebApplicationContext)this.context;
if(!cwac.isActive()){
//Thecontexthasnotyetbeenrefreshed->provideservicessuchas
//settingtheparentcontext,settingtheapplicationcontextid,etc
if(cwac.getParent()==null){
//Thecontextinstancewasinjectedwithoutanexplicitparent->
//determineparentforrootwebapplicationcontext,ifany.
//加载父上下文
ApplicationContextparent=loadParentContext(servletContext);
cwac.setParent(parent);
}
//对WebApplicationContext进行初始化,初始化参数从web.xml中取
configureAndRefreshWebApplicationContext(cwac,servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,this.context);
ClassLoaderccl=Thread.currentThread().getContextClassLoader();
if(ccl==ContextLoader.class.getClassLoader()){
currentContext=this.context;
}
elseif(ccl!=null){
currentContextPerThread.put(ccl,this.context);
}
/*省略部分代码*/
}
上面initWebApplicationContext()方法中,通过createWebApplicationContext(servletContext)创建root上下文(即IOC容器),之后Spring会以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE属性为Key,将该root上下文存储到ServletContext中,下面看看createWebApplicationContext(servletContext)源码:
/**
*InstantiatetherootWebApplicationContextforthisloader,eitherthe
*defaultcontextclassoracustomcontextclassifspecified.
*<p>Thisimplementationexpectscustomcontextstoimplementthe
*{@linkConfigurableWebApplicationContext}interface.
*Canbeoverriddeninsubclasses.
*<p>Inaddition,{@link#customizeContext}getscalledpriortorefreshingthe
*context,allowingsubclassestoperformcustommodificationstothecontext.
*@paramsccurrentservletcontext
*@returntherootWebApplicationContext
*@seeConfigurableWebApplicationContext
*/
protectedWebApplicationContextcreateWebApplicationContext(ServletContextsc){
//确定载入的上下文的类型,参数是在web.xml中配置的contextClass(没有则使用默认的)
Class<?>contextClass=determineContextClass(sc);
if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)){
thrownewApplicationContextException("Customcontextclass["+contextClass.getName()+
"]isnotoftype["+ConfigurableWebApplicationContext.class.getName()+"]");
}
//初始化WebApplicationContext并强转为ConfigurableWebApplicationContext类型
return(ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
}
从上面源码也可以看出使用createWebApplicationContext方法创建的上下文肯定是实现了ConfigurableWebApplicationContext接口,否则抛出异常。上面createWebApplicationContext(servletContext)方法里的determineContextClass方法用于查找root上下文的Class类型,看源码:
/**
*ReturntheWebApplicationContextimplementationclasstouse,eitherthe
*defaultXmlWebApplicationContextoracustomcontextclassifspecified.
*@paramservletContextcurrentservletcontext
*@returntheWebApplicationContextimplementationclasstouse
*@see#CONTEXT_CLASS_PARAM
*@seeorg.springframework.web.context.support.XmlWebApplicationContext
*/
protectedClass<?>determineContextClass(ServletContextservletContext){
StringcontextClassName=servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if(contextClassName!=null){
try{
returnClassUtils.forName(contextClassName,ClassUtils.getDefaultClassLoader());
}
catch(ClassNotFoundExceptionex){
thrownewApplicationContextException(
"Failedtoloadcustomcontextclass["+contextClassName+"]",ex);
}
}
else{
contextClassName=defaultStrategies.getProperty(WebApplicationContext.class.getName());
try{
returnClassUtils.forName(contextClassName,ContextLoader.class.getClassLoader());
}
catch(ClassNotFoundExceptionex){
thrownewApplicationContextException(
"Failedtoloaddefaultcontextclass["+contextClassName+"]",ex);
}
}
}
从以上可以看到如果web.xml中配置了实现ConfigurableWebApplicationContext的contextClass类型就用那个参数,否则使用默认的XmlWebApplicationContext。
上面ContextLoader类的initWebApplicationContext()方法里还有个加载父上下文的方法loadParentContext(ServletContextservletContext),也来看看其源码:
/**
*Templatemethodwithdefaultimplementation(whichmaybeoverriddenbya
*subclass),toloadorobtainanApplicationContextinstancewhichwillbe
*usedastheparentcontextoftherootWebApplicationContext.Ifthe
*returnvaluefromthemethodisnull,noparentcontextisset.
*<p>Themainreasontoloadaparentcontexthereistoallowmultipleroot
*webapplicationcontextstoallbechildrenofasharedEARcontext,or
*alternatelytoalsosharethesameparentcontextthatisvisibleto
*EJBs.Forpurewebapplications,thereisusuallynoneedtoworryabout
*havingaparentcontexttotherootwebapplicationcontext.
*<p>Thedefaultimplementationuses
*{@linkorg.springframework.context.access.ContextSingletonBeanFactoryLocator},
*configuredvia{@link#LOCATOR_FACTORY_SELECTOR_PARAM}and
*{@link#LOCATOR_FACTORY_KEY_PARAM},toloadaparentcontext
*whichwillbesharedbyallotherusersofContextsingletonBeanFactoryLocator
*whichalsousethesameconfigurationparameters.
*@paramservletContextcurrentservletcontext
*@returntheparentapplicationcontext,or{@codenull}ifnone
*@seeorg.springframework.context.access.ContextSingletonBeanFactoryLocator
*/
protectedApplicationContextloadParentContext(ServletContextservletContext){
ApplicationContextparentContext=null;
StringlocatorFactorySelector=servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
StringparentContextKey=servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
if(parentContextKey!=null){
//locatorFactorySelectormaybenull,indicatingthedefault"classpath*:beanRefContext.xml"
BeanFactoryLocatorlocator=ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);
Loglogger=LogFactory.getLog(ContextLoader.class);
if(logger.isDebugEnabled()){
logger.debug("Gettingparentcontextdefinition:usingparentcontextkeyof'"+
parentContextKey+"'withBeanFactoryLocator");
}
this.parentContextRef=locator.useBeanFactory(parentContextKey);
parentContext=(ApplicationContext)this.parentContextRef.getFactory();
}
returnparentContext;
}
上面源码就是实现根据locatorFactorySelector和parentContextKey来给上下文设置父上下文,前提是我们在web.xml中配置了这两个参数,不过一般开发中很少会设置这两个参数,从上面源码的大段注释也可以看出如果没有的话父上下文就为空。
在contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet,这个servlet可以配置多个,以DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、处理每个servlet请求。DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有springmvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中实现的,基本工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是XmlWebApplicationContext。初始化完毕后,spring以与servlet的名字相关的属性为Key,也将其存到ServletContext中。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(WebApplicationContext)。
最后讲讲ContextLoaderListener与DispatcherServlet所创建的上下文ApplicationContext的区别:
- ContextLoaderListener中创建ApplicationContext主要用于整个Web应用程序需要共享的一些组件,比如DAO,数据库的ConnectionFactory等。而由DispatcherServlet创建的ApplicationContext主要用于和该Servlet相关的一些组件,比如Controller、ViewResovler等。
- 对于作用范围而言,在DispatcherServlet中可以引用由ContextLoaderListener所创建的ApplicationContext,而反过来不行。
这两个ApplicationContext都是通过ServletContext的setAttribute方法放到ServletContext中的。从web.xml的配置可知ContextLoaderListener会先于DispatcherServlet创建ApplicationContext,DispatcherServlet在创建ApplicationContext时会先找到由ContextLoaderListener所创建的ApplicationContext,再将后者的ApplicationContext作为参数传给DispatcherServlet的ApplicationContext的setParent()方法,作为它的父上下文,在Spring源代可以看出:
protectedWebApplicationContextcreateWebApplicationContext(ApplicationContextparent){
Class<?>contextClass=getContextClass();
if(this.logger.isDebugEnabled()){
this.logger.debug("Servletwithname'"+getServletName()+
"'willtrytocreatecustomWebApplicationContextcontextofclass'"+
contextClass.getName()+"'"+",usingparentcontext["+parent+"]");
}
if(!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)){
thrownewApplicationContextException(
"Fatalinitializationerrorinservletwithname'"+getServletName()+
"':customWebApplicationContextclass["+contextClass.getName()+
"]isnotoftypeConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContextwac=
(ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
//设置父ApplicationContext
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
returnwac;
}
这里wac即为由DisptcherServlet创建的ApplicationContext,而parent则为有ContextLoaderListener创建的ApplicationContext。
当Spring在执行ApplicationContext的getBean时,如果在自己context中找不到对应的bean,则会在父ApplicationContext中去找。这也解释了为什么我们可以在DispatcherServlet中获取到由ContextLoaderListener对应的ApplicationContext中的bean。
父子两个WebApplicationContext带来的麻烦
父WebApplicationContext里的bean可以在不同的子WebApplicationContext里共享,而不同的子WebApplicationContext里的bean区不干扰。
但是实际上有会不少的问题:
如果开发者不知道Springmvc里分有两个WebApplicationContext,导致各种重复构造bean,各种bean无法注入的问题。
有一些bean,比如全局的aop处理的类,如果先父WebApplicationContext里初始化了,那么子WebApplicationContext里的初始化的bean就没有处理到。如果在子WebApplicationContext里初始化,在父WebApplicationContext里的类就没有办法注入了。
本文内容总结:
原文链接:https://www.cnblogs.com/natee/p/6341395.html