之前的文章介绍了Spring的初始化过程,Spring Web应用初始化依赖于Web容器的初始化,这在之前已经提到过了。那么初始化完成后,SpringMVC又是怎样发挥作用的呢?首先呢,Web容器初始化完成后会继续读取web.xml里的节点,我们知道DispatcherServlet是配置在web.xml中的,所以DispatcherServlet是由Web容器主动去加载的。那么,DispatcherServlet到底是个什么呢?
首先,DispatcherServlet仍是一个Servlet容器。在Web容器初始化的过程中,会创建每个Servlet容器的的实例对象。我们都知道,在Tomcat容器中,维护了一个线程池,每当有一个客户请求抵达服务器的时候,就分配一个线程来处理该请求。同时,Servlet是单例多线程的,也就是多个请求共用一个Servlet容器。所以呢,DispatcherServlet的完整定义是:是一个具有唯一性的增强型Servlet容器。 明确了这一点,我们就可以想象的到,DispatcherServlet具有servlet的大多数特性,包括Servlet的完整生命周期。
按照惯例,我们需要先找到DispatcherServlet的初始化入口,打开源码,它的继承关系如下:
可以看到,DispatcherServlet拥有一系列的父类,顶级父类是GenericServlet,它是Servlet接口的最简单实现。Web容器会直接调用DispatcherServlet拥有的init()方法,其实现在HttpServletBean类中,如下:
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
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;
}
}
initServletBean();
}
我们需要重点关注initServletBean()方法的调用,它的具体实现在FrameworkServlet类中。
protected final void initServletBean() throws ServletException {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
protected WebApplicationContext initWebApplicationContext() {
//ROOT上下文(ContextLoaderListener方式加载的)
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
// 1、在创建该Servlet注入的上下文
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
//2、查找已经绑定的上下文
wac = findWebApplicationContext();
}
if (wac == null) {
//3、如果没有找到相应的上下文,并指定父亲为ContextLoaderListener
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
//4、刷新上下文(执行一些初始化)
onRefresh(wac);
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, 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());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
initWebApplicationContext()方法不用多说,获取通过ContextLoader初始化完成的Spring容器的上下文,然后以此为父容器来加载DispatcherServlet。如果获取失败,则分情况间接创建一个上下文。追踪代码可以发现最后都是间接调用了configureAndRefreshWebApplicationContext()方法来刷新容器。刷新容器可以类比Spring容器的启动过程,实现过程在AbstractApplicationContext类中。容器刷新工作后就准备完成了,通过onRefresh()方法来做一些初始化工作,它的实现在DispatcherServlet类中,如下:
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);//文件上传解析,如果请求类型是multipart将通过MultipartResolver进行文件上传解析
initLocaleResolver(context);//本地化解析
initThemeResolver(context);//主题解析
initHandlerMappings(context);//通过HandlerMapping,将请求映射到处理器
initHandlerAdapters(context);//通过HandlerAdapter支持多种类型的处理器
initHandlerExceptionResolvers(context);//如果执行过程中遇到异常,将交给HandlerExceptionResolver来解析
initRequestToViewNameTranslator(context);//直接解析请求到视图名
initViewResolvers(context);//通过viewResolver解析逻辑视图到具体视图实现
initFlashMapManager(context);//flash映射管理器
}
以上,就是所有内容了,可以看出这是一种很巧妙的设计。