很多文章中将 DispatcherServlet 称为 “前端控制器” , 我更愿意称它为 “请求分发器”(这也不算是文字游戏,只是觉得这样能更好的理解作者的设计思想,同时一个好的名字确实也很重要。)个人认为这种分发请求的方式也可以抽象成一个设计模式 ——“分发处理模式” , struts2 和 spring-mvc 都是用这种方式实现的,以一个 web 组件作为入口,进入这个入口后有另外一个天地,这时再更具某些因素决定选择该走哪条道路。(就像是我们进入了一个山洞,发现山洞后面通向了另一个世界,我们到达另一个世界的前题就是先进入这个山洞)。废话不多说,进入源码的世界,用代码表达思想,体会大师的思想,希望能与大师并肩前行。
-
DispatcherServlet 结构
-
DispatcherServlet 的默认策略
当 Servlet 容器启动的时候,DispatcherServlet 会初始化(load-on-stratup 大于 0 时),当 DispatcherServlet 被加载进内存时首先加载 static 的内容,也就是如下代码:
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
// 指定的默认文件为 DispatcherServlet.properties,
// 该文件位于 org.springframework.web.servlet 包下
ClassPathResource resource =
new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
// 将 DispatcherServlet.properties 配置加载到 Properties 中
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
}
}
-
执行 Servlet 的 init 方法,做为 DispatcherServlet 的初始化入口 , 调用父类 org.springframework.web.servlet.HttpServletBean 的 init() 方法 :
-
public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // Set bean properties from init parameters. try { // 获取 Servlet 的 init-param 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) { if (logger.isErrorEnabled()) { 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"); } }
-
初始化org.springframework.web.context.WebApplicationContext ,调用 org.springframework.web.servlet.FrameworkServlet 的 如下方法 :
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 { // 初始化 spring 容器 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"); } }
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; }
如果寻找了一遍发现没有 spring 容器的时候就会去创建一个:
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"); } // 调用无参构造器创建一个 contextClass 类型的对象 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); // 设置父容器(有可能不存在父容器) wac.setParent(parent); // 设置配置文件的位置 wac.setConfigLocation(getContextConfigLocation()); // 对该容器进行配置,并执行刷新操作 configureAndRefreshWebApplicationContext(wac); return wac; }
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()); } } // 设置 ServletContext 以及其他 Servlet 组件 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(); }
添加的监听器:
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { // 在内容类中调用了 FrameworkServlet 的 onApplicationEvent 方法 FrameworkServlet.this.onApplicationEvent(event); } }
public void onApplicationEvent(ContextRefreshedEvent event) { this.refreshEventReceived = true; // 调用 DispatcherServlet 的 onRefresh 方法 onRefresh(event.getApplicationContext()); }
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); }
接下来就是去设置 DispatcherServlet 的各个组件 :
Bean type Explanation Maps incoming requests to handlers and a list of pre- and post-processors (handler interceptors) based on some criteria the details of which vary by
HandlerMapping
implementation. The most popular implementation supports annotated controllers but other implementations exists as well.HandlerAdapter
Helps the
DispatcherServlet
to invoke a handler mapped to a request regardless of the handler is actually invoked. For example, invoking an annotated controller requires resolving various annotations. Thus the main purpose of aHandlerAdapter
is to shield theDispatcherServlet
from such details.Maps exceptions to views also allowing for more complex exception handling code.
Resolves logical String-based view names to actual
View
types.Resolves the locale a client is using and possibly their time zone, in order to be able to offer internationalized views
Resolves themes your web application can use, for example, to offer personalized layouts
Parses multi-part requests for example to support processing file uploads from HTML forms.
Stores and retrieves the "input" and the "output"
FlashMap
that can be used to pass attributes from one request to another, usually across a redirect.
private void initLocaleResolver(ApplicationContext context) {
try {
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
}
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default. 如果没有指定 spring-mvc 使用哪一个具体的组件,
// 那么这时候就会选择默认的策略
this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
"': using default [" + this.localeResolver + "]");
}
}
}
在完成了上述步骤之后,WebApplicationContext , 以及 DispatcherServlet 就已经构建完成了。之后的 blog 会对 spring-webmvc 的执行过程,各个组件,以及特性做源码学习,分析。