废话
最近在看SpringMVC...里面东西好多...反正东看一点西看一点吧...
分享一下最近的一些心得..是关于DispatcherServlet的
DispatcherServlet与ContextLoaderListener
dispattcherServlet这个类大家肯定不陌生的...因为使用SpringMVC一定会在web.xml里配置它.
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 默认/WEB-INF/[servlet名字]-servlet.xml加载上下文, 如果配置了contextConfigLocation参数,
将使用classpath:/mvc-dispatcher-servlet.xml加载上下文 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring/mvc-dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
从配置中就可以看出这是1个servlet.
而使用SpringMVC需要使用Spring,Spring在web环境中时通过一个listener来配置的
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> <!-- 配置spring的bean用 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/spring/application-context.xml
</param-value>
</context-param>
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {.....}
ContextLoaderListener实现了ServletContextListener.
public interface ServletContextListener extends EventListener {
/**
** Notification that the web application initialization
** process is starting.
** All ServletContextListeners are notified of context
** initialization before any filter or servlet in the web
** application is initialized.
*/ public void contextInitialized ( ServletContextEvent sce ); /**
** Notification that the servlet context is about to be shut down.
** All servlets and filters have been destroy()ed before any
** ServletContextListeners are notified of context
** destruction.
*/
public void contextDestroyed ( ServletContextEvent sce );
}
ServletContextListener的contextInitialized是在任何filter或者servlet之前调用的.
贴了这么多代码片段只为了说明一个问题:Spring的applicationContext先于SpringMVC的applicationContext加载.
HttpServletBean
DispatcherServlet继承自FrameworkServlet继承自HttpServletBean继承自HttpServlet
所以我觉得按时间顺序的话应该从HttpServletBean的init方法看起.
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
} // Set bean properties from init parameters.
try {
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
} // Let subclasses do whatever initialization they like.
initServletBean(); if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
9-14行主要就是把当前类的对象(dispatcherServlet)包装成BeanWrapperImpl对象,然后设置属性.
PropertyValues可以来自ServletConfig,然后PropertyValues又会被设置到dispatcher的属性里去.
从中我们可以得到的信息是什么呢?
我们可以得到的信息就是:
<init-param>
<!-- 默认/WEB-INF/[servlet名字]-servlet.xml加载上下文, 如果配置了contextConfigLocation参数,
将使用classpath:/mvc-dispatcher-servlet.xml加载上下文 -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:/spring/mvc-dispatcher-servlet.xml</param-value>
</init-param>
dispatcherServlet里面的init-param我们可以配置dispatcherServlet里的所有属性名称和值
/** ServletContext attribute to find the WebApplicationContext in */
private String contextAttribute; /** WebApplicationContext implementation class to create */
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS; /** WebApplicationContext id to assign */
private String contextId; /** Namespace for this servlet */
private String namespace; /** Explicit context config location */
private String contextConfigLocation; ...............
做完这些以后就转到22行,让FrameworkServlet来做剩下的事情
FrameworkServlet
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis(); try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
} if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
FrameworkServlet我觉得就做了一件事情.就是初始化了applicationContext(第14行).只是这个初始化非常复杂.....
然后留了1个扩展点:
第15行initFrameworkServlet()现在的实现是空的.它会在applicationContext初始化完成之后被调用.
所以我们可以通过继承dispatcherServlet来扩展.但是话又说回来.Spring的扩展点真的很多.可以通过继承很多接口来参与Spring bean的生命周期.也并不一定要通过这个方法来做.
然后再来看看initWebApplicationContext方法...这个方法里面嵌套了N层..
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null; if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
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 -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
} if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
} if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
} return wac;
}
里面有很多种情况,很多if情况我都没遇到过..我只知道在我最一般的配置下.这个时候Spring的rootContext(11行)是已经完成了初始化.而SpringMVC的wac applicationContext还没有初始化.所以这个时候wac = createWebApplicationContext(rootContext);
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment());
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation()); configureAndRefreshWebApplicationContext(wac); return wac;
}
如果不通过init-param修改dispatcherServlet的属性的话.contextClass默认是XmlWebApplicationContext.class
所以通过BeanUtils通过反射来创建XmlWebApplicationContext(applicationContext).
然后各种setter方法以后调用configureAndRefreshWebApplicationContext方法
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + "/" + getServletName());
}
} wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); // The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
} postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
然后又是各种setter方法.
18行这里添加了一个eventListener,当xmlWebapplicationContext refresh(创建各种bean?)以后会通知这个listener.
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> { @Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
而这个listener会delegate另外1个linstener ContextRefreshListener来处理.
而ContextRefreshListener是FrameworkServlet的内部类.因为是内部类.他可以调用FrameworkServlet的方法(onApplicationEvent方法).
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
onRefresh(event.getApplicationContext());
}
onRefresh又是一个扩展接口.这个时间点是xmlWebapplicationContext finishRefresh的时候.
这个方法被dispatcherServlet用到了.后面会说.
25行 ConfigurableWebEnvironment的(实际上是StandardServletEnvironment)的initPropertySources方法具体做的事情是:
这我没太看懂...根据我的理解就是有些properties一开始在servletContext或者servletConfig还没有的时候会先用占位符properties去占位,然后现在通过这个方法替换成真正的包含servletContext或者servletConfig的properties.但是SpringMVC初始化的时候这些都是有了的...到底什么时候会存在这种情况我就不知道了..说不定Spring初始化xmlWebApplicationContext的时候会遇到吧.毕竟那个时候还是没有servletConfig的....
然后28行又是一个扩展点.
postProcessWebApplicationContext(wac);我们可以像先前那样,通过继承dispatcherServlet来实现.
这个时间点就是applicationContext还没有refresh(生成各种bean?)的时候.
然后又是1个扩展点:
applyInitializers(wac);
protected void applyInitializers(ConfigurableApplicationContext wac) {
String globalClassNames = getServletContext().getInitParameter(ContextLoader.GLOBAL_INITIALIZER_CLASSES_PARAM);
if (globalClassNames != null) {
for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
this.contextInitializers.add(loadInitializer(className, wac));
}
} if (this.contextInitializerClasses != null) {
for (String className : StringUtils.tokenizeToStringArray(this.contextInitializerClasses, INIT_PARAM_DELIMITERS)) {
this.contextInitializers.add(loadInitializer(className, wac));
}
} AnnotationAwareOrderComparator.sort(this.contextInitializers);
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
可以通过在web.xml里配置globalInitializerClasses和contextInitializerClasses来初始化applicationContext
比如公司的做法:
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>XXXX.spring.SpringApplicationContextInitializer</param-value>
</context-param>
public class SpringApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> { /**
* The Constant LOGGER.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(SpringApplicationContextInitializer.class); /* (non-Javadoc)
* @see org.springframework.context.ApplicationContextInitializer#initialize(org.springframework.context.ConfigurableApplicationContext)
*/
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
try {
ResourcePropertySource rps = new ResourcePropertySource(
applicationContext.getResource("classpath:application-cfg.properties"));
applicationContext.getEnvironment().getPropertySources().addFirst(rps);
} catch (IOException e) {
LOGGER.error("Fail to add properties in application-cfg.properties to environment", e);
}
}
}
通过编码方式强行加载application-cfg.properties
最后就是调用applicationContext的refresh方法了:
wac.refresh(); 容器的refresh里面也有很多很多东西.但是和SpringMVC好像没啥关系..和Spring有关.我就没细看了..
DispatcherServlet
前面提到在frameworkServlet初始化applicationContext finish的时候会触发监听器的事件,然后调用DispatcherServlet的onRefresh方法.
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
} /**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
这个方法要做的就是各种init....各种init方法里面都是差不多的.我们来看一个就可以了.
/**
* Initialize the HandlerMappings used by this class.
* <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
* we default to BeanNameUrlHandlerMapping.
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null; if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
OrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
} // Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
detectAllHandlerMappings默认是true.所以会去applicationContext里找HandlerMapping.class类型的bean.因为配置过<mvc:annotation-driven>所以可以加载到3个bean..
把dispatcherServlet里的成员变量指向这个HandlerMapping的list.因为后面分发请求的时候要用到.弄成成员域就不需要每次都去applicationContext里找了.
3个bean到底使用哪个呢? 先来排个序... 这个3个bean都实现了Ordered接口...排序结果不用多说大家肯定知道.RequestMappingHanddlerMapping肯定是最优先的...
后续
然后...然后以要做的后续事情就不多了...回到FrameworkServlet.把applicationContext放到ServletContext.与Spring初始化applicationContext的一样..
然后貌似就真的没有然后了....
思考与收获
这次学习学到了什么:
1. DispatcherServlet里有很多扩展点:
DispatcherServlet提供的扩展方法至少有(可能还有些我没发现):
(1) initFrameworkServlet(),在xmlWebApplicationContext初始化完成后(refresh完成后)调用
(2) postProcessWebApplicationContext(wac);在xmlWebApplicationContext各种set以后但是xmlWebApplicationContext还没有refresh(生成bean?)的时候调用
(3) onRefresh(ApplicationContext context);在xmlWebApplicationContext内部被调用,时间点是finish refresh的时候(bean初始化完成?).
DispatcherServlet指明的通过接口实现的扩展点至少有(可能还有些我没发现):
(1) 配置globalInitializerClasses或contextInitializerClasses,自己实现 ApplicationContextInitializer<C extends ConfigurableApplicationContext>接口,时间点是xmlWebApplicationContext refresh方法调用之前,postProcessWebApplicationContext(wac);之后.
2.结合上一篇文章,我知道了DispatcherServlet其实也并不是很特殊的,SpringMVC的@Controller自动委派请求等功能还是通过配置Bean(比如mvc:annotation-driven标签),让Bean参与Spring Bean的声明周期来完成的.所以这些事情是在xmlWebApplicationContext的refresh的时候发生的.并不是在DispatcherServlet里实现的.所以要搞清楚这些奇特的bean需要研究这些bean到底参与了bean声明周期的哪些步骤,到底做了写什么.此外还需要自己研究下xmlWebApplicationContext的refresh到底干了些什么事情.DispatcherServlet的initStrategies里的各种resolver,每种resolver都是为request与response的某一个环节服务的.比如RequestMappingHandlerMapping是为了让请求委派给正确的Controller服务的.研究这些resolver可以了解从解析请求到生成响应的细节.
3.对SpringMVC初始化的步骤有了了解.