上一篇日志总结了Servlet规范,对于所有处理用户请求的服务器组件,都要去实现Servlet接口。GenericServlet保留了Servlet配置,提供无参数的init()方法供子类重写,还提供了通用协议的service()方法,该方法子类必须重写,来实现不同协议的Servlet。最后HttpServlet重写GenericServlet的service()方法,实现支持HTTP协议的Servlet,负责分发HTTP请求到不同的方法中。由DipsatcherServlet的多级继承图可以看到,往下走,到了HttpServletBean。
HttpServletBean的作用是把Servlet配置参数作为Bean属性,对我们自己的Servlet(如Spring MVC中的DispatcherServlet)做初始化。具体是在web.xml文件中的
springmvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc.xml
实现方式通过重写GenericServlet中的init()方法:
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
// 从Servlet配置中获取名字,这个Servlet配置来自GenericServlet
Logger.debug("Initializing servlet '" + getServletName() + "'");
}
// 设置Servlet的初始化参数作为Servlet Bean的属性
try {
// PropertyValues是一个容器类,存放从web.xml配置文件中配置的参数
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(),
this.requiredProperties);
// 把当前的Servlet作为一个Bean,把Bean的属性和属性的存取方法信息放入BeanWrapper中
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
// 注册一个可以在资源和路径之间进行转化的用户化编辑器,这些资源是这个Web应用的内部资源,例如一个文件或图片等
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader));
// 提供一个模板方法initServletBean(),供子类去实现更多的初始化
initBeanWrapper(bw);
// 把初始化指定的参数都赋值到Serlvet的属性中,第二个参数true表示忽略位置属性
bw.setPropertyValues(pvs, true);
} catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() +
"'", ex);
}
// 提供一个模板方法initServletBean(),供子类去初始化其他资源
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
来看看代码,第3-6行是日志输出,关键从第9行开始,首先实例化一个ServletConfigPropertyValues容器对象,用来存放web.xml配置文件中配置的参数,即
最后,第25行,调用BeanWrapper的setPropertyValues()方法,把前面的初始化参数,即ServletConfigPropertyValues中存放的配置参数,都设置到被BeanWrapper包裹的实例里对应的成员变量中去,完成对Servlet的初始化。在第32行还提供了一个模板方法initServletBean(),供子类对Servlet做更多的初始化。
总结下HttpServletBean,它继承自HttpServlet,功能是把web.xml中的Servlet配置参数(
再往下到了FrameworkServlet,FrameworkServlet就是Spring Web的一个基本Servlet实现类了。它的作用是初始化,加载WebApplicationContext(Web应用程序环境),FrameworkServlet实现了HttpServlet中的所有接口实现,所以可以将HTTP支持的GET、PUT、POST,DELETE等方法分发到Spring MVC的控制器方法中进行处理,,子类必须实现其doService()方法来处理接收到的请求。
FrameworkServlet通过继承HttpServletBean,实现了ApplicationContextAware接口,接口中定义了一个setApplicationContext()方法,以此来在初始化时加载Web应用程序环境。FrameworkServlet加载WebApplicationContext应用程序环境过程:
可以看到,FrameworkServlet初始化Servlet时,首先会去查找一个专用的应用程序跟环境,即查找我们在web.xml中servlet标签对里是否有配置contextClass,如果有,就会去加载我们配置的专用的contextClass,如果不存在专用的根环境,就会加载默认的共享根环境。
在FrameworkServlet父类HttpServletBean中的init()方法里,最后调用了一个模板方法initServletBean(),这个方法就是HttpServletBean供子类去重写,完成Servlet框架的初始化,来看看FrameworkServlet重写的这个方法:
@Override
public final void initServletBean() throws ServletException {
// 输出初始化信息到日志中
getServletContext().log("Initializing Spring FrameworkServlet '" +
getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() +
"': initializationstarted");
}
// 初始化环境开始时间
long startTime = System.currentTimeMillis();
try {
// 初始化Servlet环境
this.webApplicationContext = initWebApplicationContext();
// 调用模板方法,供子类实现,初始化指定的资源(例如具体实现在DispatcherServlet中)
initFrameworkServlet();
} catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throws ex;
} catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throws ex;
}
if (this.logger.isInfoEnabled()) {
// 初始化环境用时
long elapsedTime = System.currentTimeMillis() - startTime;
// 输出到日志信息中
this.logger.info("FrameworkServlet '" + getServletName() +
"': initializationcomplete in " + elapsedTime + "ms");
}
}
可以看到,initServletBean()方法关键在于第16行,调用initWebApplicationContext()方法,完成Web应用程序环境的加载(加载过程在上面已经贴出了图示)。接下来就看看,initWebApplicationContext()方法是如何实现的:
protected WebApplicationContext initWebApplicationContext() {
// 首先查找这个Servlet(例如DispatcherServlet)是否配置有专用的根环境
WebApplicationContext wac = findWebApplicationContext();
// 如果没有,就加载默认的共享根环境
if (wac == null) {
// 加载默认的共享根环境,这个根环境通过关键字ROOT_WEB_APPLICATION
// _CONTEXT_ATTRIBUTE保存在Servlet环境里
WebApplicationContext parent =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
// 创建Servlet的子环境,并引用已经得到的著环境
wac = createWebApplicationContext(parent);
}
// Servlet框架在初始化时为子类提供了初始化模板方法initFrameworkServlet()和onRefresh()
if (!this.refreshEventReceived) {
onRefresh(wac);
}
// 如果设置了发布环境属性,则把这个Web应用程序环境以ServletContextAttributeName的值
// 作为关键字保存到Servlet环境里,以此来让其他Servlet共享这个Web应用程序环境
if (this.publishContext) {
// 将这个环境发布并存储到Servlet Context中
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;
}
按照加载Web应用程序环境的过程,第3行首先判断在web.xml中是否有配置servlet的专用根环境,如果你没有在里面配置ContextLoaderListener,那么wac的值就为null。接下来第9行就会去加载一个默认的共享根环境,并把我们的Servlet子环境引用得到这个主环境。
在Web应用环境初始化后,这个Servlet类就会作为Web应用程序环境的事件处理监听器,然后通过onRefresh()方法动态刷新来实现各种组件的初始化,例如DispatcherServlet就是通过onRefresh()方法来动态加载Spring MVC需要的组件。第17行的refreshEventReceived标识onRefresh()方法是否已经被调用过,如果没有,就调用onRefresh()。
最后,第23-31行,根据publishContext标识是否设置了发布环境属性,如果设置了,就把当前初始化的Web应用程序环境设置到Servlet环境中,供其他Servlet共享这个Web应用程序环境。
总的来说,FrameworkServlet的作用就是初始化WebApplicationContext,我们的Web应用程序环境(如Spring MVC环境)。
最后来到我们的DispatcherServlet,由前面经过多级继承自HttpServlet,每一级都完成特定的初始化。Servlet提供初始化模板方法,GenericServlet保存了config配置,并实现了Servlet接口的通用协议的service()方法,下一级HttpServlet重写了service()方法,实现了支持HTTP协议的Servlet,完成对用户HTTP请求的分发。HttpServletBean根据
根据其父类FrameworkServlet,当Web应用程序环境初始化完成之后,该类就会变成当前应用程序进行环境事件处理的监听器,所以当DispatcherServlet监听事件知道Web应用程序环境刷新后(调用了onRefresh()方法),便会在主环境和子环境中查找是否已经注册了Spring MVC需要的组件。Spring MVC的组件按照数量来划分,分为可选组件、单值组件和多值组件。在Web应用程序环境初始化和刷新时,首先调用initStrategies()方法,该方法完成组件的加载:
protected void initStrategies(ApplicationContext context) {
// 初始化多部(multipart)请求解析器,没有默认的实现
initMultipartResolver(context);
// 初始化地域解析器,默认的实现是AcceptHeaderLocaleResolver
initLocaleResolver(context);
// 初始化主题解析器,默认的实现是FixedThemeResolver
initThemeResolver(context);
// 初始化处理器映射器集合,默认的实现是BeanNameUrlHandlerMapping和
// DefaultAnnotationHandlerMapping
initHandlerMappings(context);
// 初始化处理器适配器集合,默认的实现是HttpRequestHandlerAdapter,
// SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter
initHandlerAdapters(context);
// 初始化处理器异常解析器集合,默认的实现是AnnotationMethodHandlerExceptionResolver、
// ResponseStatusExceptionResolver和DefaultHandlerExceptionResolver
initHandlerExceptionResolvers(context);
// 初始化请求视图名解析器,默认的实现是DefaultRequestToViewNameTranslator
initRequestToViewNameTranslator(context);
// 初始化视图解析器集合,默认的实现是InternalResourceViewResolver
initViewResolvers(context);
}
接下来看看里面的几个具体方法。
可选组件指的是在工作流中可能需要也可能不需要的组件,例如MultipartResolver,它负责处理文件上传:
private void initMultipartResolver(ApplicationContext context) {
try {
// 从配置的Web应用程序环境中查找多部请求解析器
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME,
MultipartResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
}
} catch (NoSuchBeanDefinitionException ex) {
// 如果没有查找到多部请求解析器,则忽略它,因为不是所有的应用程序都需要它,多部请求
// 通常用在文件上传中
this.multipartResolver = null;
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate MultipartResolver with name '" +
MULTIPART_RESOLVER_BEAN_NAME + "': no multipart request handling provided");
}
}
}
它的初始化很简单,之间在Web应用程序环境中查找,如果没有找到,则会忽略这个组件。
单值组件顾名思义就是整个工作流中只需要一个的组件,例如主题解析器ThemeResolver,地域解析器LocaleResolver和请求视图名解析器RequestToViewNameTranslator。与可选组件的初始化一样,都是直接在Web应用程序环境中查找,不同的是,如果查找失败,则会去查找Spring MVC的默认配置,并根据其默认配置来加载地域解析器:
private void initLocaleResolver(ApplicationContext context) {
try {
// 从配置的Web应用程序环境中查找地域请求解析器
this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME,
LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
}
} catch (NoSuchBeanDefinitionException ex) {
// 如果没有查找到低于请求解析器,则查找默认的配置,并根据默认配置初始化地域解析器
this.localResolver = getDefaultStrategy(context, LocaleResolver.class);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate LocaleResolver with name '" +
LOCALE_RESOLVER_BEAN_NAME + "': using default [" + this.localResolver +
"]");
}
}
}
多值组件也就是整个工作流中可以有多个实现组件,很容易想到,肯定有我们的处理器映射器,因为DispatcherServlet需要轮询查找出能够处理当前HTTP请求的处理器映射器,让其返回一个执行链。来看看多值组件的初始化代码:
private void initHandlerMappings(ApplicationContext context) {
this.initHandlerMappings = null;
if (this.detectAllhandlerMappings) {
// 如果配置为自动检测所有的处理器映射器,则在加载的Web应用程序环境中查找
// 所有实现处理器映射器接口的Bean
Map matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new
ArratList(matchingBeans.values());
// 根据这些Bean所实现的Order接口进行排序
orderComparator.sort(this.handlerMappings);
}
} else {
// 如果没有配置为自动检测所有的处理器映射器,则在Web应用程序环境中查找名为
// "handlerMapping"的Bean作为处理器映射器
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME,
HandlerMapping.class);
// 构造单个Bean集合
this.handlerMappings = Collections.singletonList(hm);
} catch (NoSuchBeanDefinitionException ex) {
// 忽略已成,后面讲根据引用是否为空判断是否查找成功
}
}
if (this.handlerMappings == null) {
// 如果仍然没有查找到注册的处理器映射器的实现,则使用默认的配置加载处理器映射器
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() +
"': using default");
}
}
}
第4行首先判断是否配置为检测所有的处理器映射器,如果是,就在Web应用程序环境中查找所有的处理器映射器。若不是配置检测所有的处理器映射器,第17行则去加载一个名为”handlerMapping”的处理器映射器。第31行,若以上方式都没有查找到注册的处理器映射器,那么Spring MVC就会使用默认的配置,加载默认的处理器映射器。
以上,就是DispatcherServlet的初始化过程,经过多级继承,逐层初始化,最后完成Spring MVC的组件注册后,DispatcherServlet就可以开始拦截用户的HTTP请求,将其分发到Spring MVC的工作流中进行响应处理了。
以上源码已上传GitHub:
https://github.com/justinzengtm/SSM-Framework/tree/master/SpringMVC_Project/source%20code