DispatcherServlet的类结构设计
1、servlet
在Spring中,ContextLoadListener只是辅助功能,用于创建WebApplicationContext类型实例,而真正的逻辑实现其实是在DispatcherServlet中进行的,DispatcherServlet是实现servlet接口的实现类。servlet主要是处理客户端的请求并将其结果发送到客户端。servlet的生命周期是由servlet的容器控制的,分为初始化、运行和销毁阶段。
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
(1)初始化阶段
servelt容器创建一个ServletConfig对象,该对象包含了servlet的初始化配置信息。
servelt容器调用servlet对象的init方法进行初始化。
(2)运行阶段
当servelt容器接受到一个请求时,servelt容器会针对这个请求创建serveltRequest 和serveltResponse对象,然后调用service方法。并把这两个参数传递给service方法。service方法通过servletRequest对象获得请求的信息,并处理该请求。再通过servletResponse对象生成这个请求的响应结果。
(3)销毁阶段
当Web应用被终止时,servlet容器会先调用servelt对象的destrory方法,然后再销毁servlet对象,同时也会销毁与servlet对象相关联的serveltConfig对象
2、DispatcherServlet的初始化
servelt初始化阶段会调用init方法,所有我们首先查看在DispatcherServelt中是否重写了inti方法。我们在其父类HttpServletBean中找到了该方法。
public final void init() throws ServletException {
//解析int-param并封装至pvs中
PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//将当前的这个Servlet类转化为一个BeanWrapper,从而能够以Spring的方式来对init-param的值进行注入
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
//注册自定义的属性编辑器,一旦遇到Resource类型的属性将会使用ResourceEditor进行解析
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
//空实现,留给子类去实现
this.initBeanWrapper(bw);
//属性注入
bw.setPropertyValues(pvs, true);
} catch (BeansException var4) {
if (this.logger.isErrorEnabled()) {
this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
}
throw var4;
}
}
//留给子类去扩展
this.initServletBean();
}
DispatcherServlet的初始化过程主要是通过将当前的servlet类型实例转换为BeanWrapper类型实例,以便使用Spring中提供的注入功能进行对应属性的注入,及在web.xml文件中以初始化参数的方法配置在servlet的声明中。DispatcherServlet继承自FrameworkServlet,
public class DispatcherServlet extends FrameworkServlet { ······ }
FrameworkServlet类上包含对应的同名属性,Spring会保证这些参数被注入到对应的值中。属性注入主要包含以下几个步骤:
1、封装及验证初始化参数
ServletConfigPropertyValues除了封装属性外还有对属性的验证功能。以下继续跟踪源码。
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties) throws ServletException { Set<String> missingProps = !CollectionUtils.isEmpty(requiredProperties) ? new HashSet(requiredProperties) : null; Enumeration paramNames = config.getInitParameterNames(); while(paramNames.hasMoreElements()) { String property = (String)paramNames.nextElement(); Object value = config.getInitParameter(property); this.addPropertyValue(new PropertyValue(property, value)); if (missingProps != null) { missingProps.remove(property); } } if (!CollectionUtils.isEmpty(missingProps)) { throw new ServletException("Initialization from ServletConfig for servlet '" + config.getServletName() + "' failed; the following required properties were missing: " + StringUtils.collectionToDelimitedString(missingProps, ", ")); } }
从代码中可知,封装属性主要是对初始化参数进行封装,也就是servlet中配置的<init-param>
中配置的封装。当然,用户可以通过对required-Properties参数的初始化来强制验证某些属性的必要性。
2、将当前servlet实例转化成Bean-Wrapper实例
PropertyAccessorFactory.forBeanPropertyAccess是Spring中提供的工具方法,主要用于将指定实例转化为Spring中可以处理的BeanWrapper类型的实例。
3、注册相对于Resource的属性编辑器
注册自定义的属性编辑器,一旦遇到Resource类型的属性将会使用ResourceEditor进行解析。
4、属性注入
BeanWrapper为Spring中的方法,支持Spring的自动注入。其实我们最常用的属性无非就是contextAttribute、context、namespace、contextConfigLocation等属性。
5、servletBean的初始化
在ContextLoaderListener加载的时候已经创建了WebApplicationContext实例,而在这个函数中最重要的就是这个实例进行进一步的补充初始化。继续查看源码,发现FrameworkServlet覆盖了HttpServletBean中的initServletBean函数:
protected final void initServletBean() throws ServletException { this.getServletContext().log("Initializing Spring " + this.getClass().getSimpleName() + " '" + this.getServletName() + "'"); if (this.logger.isInfoEnabled()) { this.logger.info("Initializing Servlet '" + this.getServletName() + "'"); } long startTime = System.currentTimeMillis(); try { this.webApplicationContext = this.initWebApplicationContext(); this.initFrameworkServlet(); } catch (RuntimeException | ServletException var4) { this.logger.error("Context initialization failed", var4); throw var4; } if (this.logger.isDebugEnabled()) { String value = this.enableLoggingRequestDetails ? "shown which may lead to unsafe logging of potentially sensitive data" : "masked to prevent unsafe logging of potentially sensitive data"; this.logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails + "': request parameters and headers will be " + value); } if (this.logger.isInfoEnabled()) { this.logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms"); } }
上面函数设计了计时器来统计初始化的执行时间,而且提供了一个扩展方法initFrameworkServlet()用于子类的覆盖操作,而作为关键的初始化逻辑实现委托给了initWebApplicationContext()。
3、WebApplicationContext的初始化
initWebApplicationContext函数的主要工作就是创建或刷新WebApplicationContext实例并对servlet功能所使用的变量进行初始化。
protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac; if (!cwac.isActive()) { if (cwac.getParent() == null) { cwac.setParent(rootContext); } this.configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { wac = this.findWebApplicationContext(); } if (wac == null) { wac = this.createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { synchronized(this.onRefreshMonitor) { this.onRefresh(wac); } } if (this.publishContext) { String attrName = this.getServletContextAttributeName(); this.getServletContext().setAttribute(attrName, wac); } return wac; }
本函数的初始化主要包含几个部分:
1、寻找或创建对应的WebApplicationContext实例。
(1)通过构造函数的注入进行初始化。
在Web中包含SpringWeb的核心逻辑的DispatcherServlet只可被声明一次,在Spring中已经存在验证,所以这就确保了如果this.webApplicationContext !=null,则可以直接判定this.webApplicationContext已经通过构造函数初始化。
(2)通过contextApplication进行初始化
通过在web.xml文件中配置的servlet参数contextAttribute来查找ServletContext中对应的属性,默认为WebApplicationContext.class.getName() + ".ROOT",也就是ContextLoaderListener加载时会创建WebApplicationContext实例,并将实例以WebApplicationContext.class.getName()+ ".ROOT"为key放入ServletContext中。
protected WebApplicationContext findWebApplicationContext() { String attrName = this.getContextAttribute(); if (attrName == null) { return null; } else { WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext(), attrName); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); } else { return wac; } } }
(3)重新创建WebApplicationContext实例。
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { Class<?> contextClass = this.getContextClass(); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } else { ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); wac.setEnvironment(this.getEnvironment()); wac.setParent(parent); String configLocation = this.getContextConfigLocation(); if (configLocation != null) { wac.setConfigLocation(configLocation); } this.configureAndRefreshWebApplicationContext(wac); return wac; } }
2、configureAndRefreshWebApplicationContext
代码显示,构造函数与单独创建,都会调configureAndRefreshWebApplicationContext方法来对已经创建的WebApplicationContext实例进行配置及刷新。追踪源码显示:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { if (this.contextId != null) { wac.setId(this.contextId); } else { wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(this.getServletContext().getContextPath()) + '/' + this.getServletName()); } } wac.setServletContext(this.getServletContext()); wac.setServletConfig(this.getServletConfig()); wac.setNamespace(this.getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new FrameworkServlet.ContextRefreshListener())); ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment)env).initPropertySources(this.getServletContext(), this.getServletConfig()); } this.postProcessWebApplicationContext(wac); this.applyInitializers(wac); wac.refresh(); }
3、onRefresh是FrameworkServlet类中提供的模板方法,在其子类DispatcherServlet中进行了重写,主要用于刷新Spring在Web功能实现中所必须使用的全局变量。
protected void onRefresh(ApplicationContext context) { }
4、追踪DispatcherServlet源代码:
protected void onRefresh(ApplicationContext context) { this.initStrategies(context); } protected void initStrategies(ApplicationContext context) { this.initMultipartResolver(context); this.initLocaleResolver(context); this.initThemeResolver(context); this.initHandlerMappings(context); this.initHandlerAdapters(context); this.initHandlerExceptionResolvers(context); this.initRequestToViewNameTranslator(context); this.initViewResolvers(context); this.initFlashMapManager(context); }
这里是DispatcherServlet的9个个性化初始化方法,该方法调用了DispatcherServlet内部的9个初始化方法,分别初始化不同的组件。这里分析一下ViewResolvers方法。
在SpringMVC方法中,当Controller将请求处理结果放入到ModelAndView中以后,DispatcherServlet会根据ModelAndView选择合适的视图进行渲染。那么在SpringMVC中是如何选择合适的VIEW呢?View对象是是如何创建的呢?答案就在ViewResolver中。ViewResolver接口定义了resolverViewName方法,根据viewName创建合适类型的View实现。那么如何配置ViewResolver呢?在Spring中,ViewResolver作为Spring Bean存在,可以在Spring配置文件中进行配置,例如下面的代码,配置了JSP相关的viewResolver。
<bean class="org.Springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean>
5、viewResolvers属性的初始化工作在initViewResolvers中完成。
private void initViewResolvers(ApplicationContext context) { this.viewResolvers = null; if (this.detectAllViewResolvers) { Map<String, ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false); if (!matchingBeans.isEmpty()) { this.viewResolvers = new ArrayList(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.viewResolvers); } } else { try { ViewResolver vr = (ViewResolver)context.getBean("viewResolver", ViewResolver.class); this.viewResolvers = Collections.singletonList(vr); } catch (NoSuchBeanDefinitionException var3) { } } if (this.viewResolvers == null) { this.viewResolvers = this.getDefaultStrategies(context, ViewResolver.class); if (this.logger.isTraceEnabled()) { this.logger.trace("No ViewResolvers declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties"); } } }