SpringMVC之DispatcherServlet(一)
上一篇介绍了ContextLoaderListener,作用就是创建 根上下文
XmlWebApplicationContext , 并储存在全局上下文 ServletContext 中。
在 web.xml 中除了 ContextLoaderListener 配置之外,还有对 DispatcherServlet 的配置。作为一个Servlet,所有的 Web 请求都需要通过它来处理,进行转发,匹配,数据处理后,并转由页面进行展现,它可以说是 SpringMVC 最核心的部分。除此之外 SpringMVC 还有不同的 HandlerMapping 映射策略,各种 Controller 控制器的实现,各种视图解析,拦截器,LocalResolver 国际化处理。
在完成对 ContextLoaderListener 的初始化后,Web 容器开始初始化 DispatcherServlet。 DispatcherServlet 会建立自己的上下文来持有SpringMVC的Bean 对象,过程是首先从 ServletContext 中获 取上下文
作为自己的父上下文,再对自己的上下文进行初始化,最后存储到 ServletContext 中。
来看看继承图:
DispatcherServlet 继承自 HttpServlet ,通过使用 Servlet API 对 HTTP 请求进行响应。其工作大致分为两个部分:一是初始化部分,由 init() 启动,经 initServletBean() , 通过 initWebApplicationContext() 最终调用 DispatcherServlet 的 initStrategies 方法,在该方法里对诸如 handlerMapping,ViewResolver 等进行初始化;另一个是对 HTTP 请求进行响应的部分,作为一个 Servlet,Web 容器会调用其 doGet(),doPost 等方法,经过 FrameworkServlet 的processRequest() 简单处理后会调用 DispatcherServlet 的doService() 方法,该方法会调用 doDispatch(),doDispatch 是实现 MVC 模式的主要部分,流程图如下
关于 Servlet
Servlet是一个Java编写的程序,基于Http协议,在服务端运行,主要处理客户端的请求并将结果发送给客户端。其生命周期由Servlet容器来控制,分为三个阶段:初始化,运行和销毁。
初始化阶段servlet容器会做以下动作:
- 加载servlet类,把servlet.class文件中的数据读到内存中
- 创建一个ServletConfig对象,该对象包含了servlet的初始化配置信息
- 创建一个servlet对象
- 调用servlet对象的init方法进行初始化
运行阶段:当请求到来servlet容器会针对该请求创建ServletRequest和ServletResponse对象,然后调用service方法,并将这两个作为参数传给service方法。该方法通过ServletRequest对象获得请求信息并处理,再通过ServletResponse对象生成这个请求的响应结果,然后销毁这两个对象。
销毁阶段:当Web应用被终止,servlet容器会先调用servlet对象的destroy方法,然后再销毁servlet对象,包括与其相关联的servletConfig对象。可以在destroy方法中释放servlet所占用的资源,如关闭数据库连接,关闭文件输入输出流等。
servlet的框架由两个Java包组成:javax.servlet和javax.servlet.http。javax.servlet中定义了所有的servlet类必须实现或扩展的通用接口和类,javax.servlet.http中定义了采用HTTP通信协议的HttpServlet类。
servlet被设计成请求驱动,HTTP请求方式包括delete, get, options, post, put, trace,在HttpServlet类中分别提供了相应的服务方法,doGet(), doDelete() …
DispatcherServlet的初始化
正如上面所说servlet初始化阶段会调用其init方法,来看看DispatcherServlet的init方法。
定位到HttpServletBean#init()
@Override
public final void init() throws ServletException {
// 获取该<servlet>中的<init-param>中设置的键值对,封装在PropertyValues中
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
//将当前这个servlet类转化为BeanWrapper,从而能够以Spring的方式对init-param 中的属性进行注入
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
//注册自定义属性编辑器,对Resource类型的属性会使用ResourceEditor进行解析
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();
}
DispatcherServlet的初始化过程主要是将servlet实例转化为BeanWrapper类型,以便利用Spring的注入功能进行属性注入。如namespace,contextConfigLocation,contextAttribute,contextClass等,这些你都可以在web.xml中以初始化参数的方式配置在<servlet>下声明配置,这些参数在FrameworkServlet,Spring将这些参数注入到对应的值中。
1,封装及验证初始化参数
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
addPropertyValue(new PropertyValue(property, value));
if (missingProps != null) {
missingProps.remove(property);
}
}
// Fail if we are still missing properties.
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>中的配置封装成PropertyValue
,再存储到private final List<PropertyValue> propertyValueList;
。requiredProperties
代表你所需的属性,若是<init-param>没有设置这些属性则会报错,HttpServletBean的子类可以通过addRequiredProperty
方法指定requiredProperties
。
protected final void addRequiredProperty(String property) {
this.requiredProperties.add(property);
2,将当前servlet转化为BeanWrapper
PropertyAccessorFactory.forBeanPropertyAccess是Spring的工具方法,用于将指定实例转化为Spring中可以处理的BeanWrapper类型的实例。
3,Resource属性编辑器
对属性进行注入过程中一旦遇到 Resource 类型的属性就会使用ResourceEditor 去解析。
DispatcherServlet持有的IOC容器的初始化
ContextLoaderListener加载的时候已经创建了 WebApplicationContext 作为 根上下文
,在 initServletBean 中创建自己的上下文,并以根上下文为父。可以认为根上下文是与 Web 应用相对应的一个上下文,而 DispatcherServlet 持有的上下文是和 Servlet 对应的一个上下文。在一个 Web 应用中,可以容纳多个 Servlet 存在,根上下文是它们共同的父上下文。
这IOC容器 父 与 子 的关系是:当向 IOC 容器 getBean 时,会先向其 父 去获取,也就是说 根上下文 定义的 bean 是被各个 Servlet 共享的。
DispatcherServlet 持有的上下文初始化后会设置到 Web 容器的上下文中,即 ServletContext,存储的 key 即名称就是在 web.xml 中设置的 <servlet-name>
来看看 FrameworkServlet 中的实现
@Override
protected final void initServletBean() throws ServletException {
......
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
......
}
下面从initWebApplicationContext开始介绍WebApplicationContext的初始化。
WebApplicationContext的初始化
initWebApplicationContext 就是创建/更新 WebApplicationContext 实例并对 servlet 功能所使用的变量进行初始化。
protected WebApplicationContext initWebApplicationContext() {
// 根上下文,即ContextLoaderListener初始化的XmlWebApplicationContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 本 Servlet 的上下文已创建
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()) {
// 未指定父上下文
if (cwac.getParent() == null) {
//指定根上下文为其父,构建双亲上下文
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 该Servlet的上下文未构建,这里是去找寻该上下文
//所谓找寻,指的是去Web容器上下文中(ServletContext)通过你在<servlet>下<init-param>中设置的contextAttribute属性的值
//来找寻你指定的上下文 WebApplicationContext
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
// refreshEventReceived标识onRefresh是否已被调用
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.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
1,寻找与创建上下文WebApplicationContext
ContextLoaderListener
会创建XmlWebApplicationConntext
实例并将其存储进ServletContext中,key为WebApplicationContext.class.getName() + ".ROOT";
,上面 rootContext 指的就是它。this.webApplicationContext == null
说明在构建时没有注入,调用findWebApplicationContext
方法,该方法是去ServletContext中获取你指定的WebApplicationContext,根据你设置的contextAttribute
的值
protected WebApplicationContext findWebApplicationContext() {
// 获取<servlet>下<init-param>中设置的contextAttribute属性的值
// 若没有设置则返回null
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
// 这里就是去全局上下文servletContext中去找你指定的WebApplicationContext
// 没有则抛异常
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
若findWebApplicationContext
返回null,则createWebApplicationContext
创建
protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
return createWebApplicationContext((ApplicationContext) parent);
}
这里parent
指的是ContextLoaderListener创建的XmlWebApplicationContext 根上下文
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
//获取servlet的初始化参数contextClass,如果没有设置则默认为XmlWebApplicationContext
Class<?> contextClass = getContextClass();
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);
//获取该上下文的配置文件
//处理后赋给AbstractRefreshableConfigApplicationContext类的String[] configLocations字段
//该类是 WebApplicationContext的父级类
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
getContextClass
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
public Class<?> getContextClass() {
return this.contextClass;
}
可以看出默认是 XmlWebApplicationContext ,若你在 web.xml 中该<servlet>下<init-param>中配置了 contextClass 的值,则在上面一开始的 init
方法里你设置的值就会被注入到这里,从而获得的是你设置的值。
2,刷新onRefresh
onRefresh 是 FrameworkServlet 类中提供的空方法,在其子类DispatcherServlet中实现,主要是初始化MVC各个部分。
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
// 初始化MultipartResolver,LocaleResolver......
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
//初始化LocaleResolver,与国际化配置相关
initLocaleResolver(context);
//初始化ThemeResolver,主题解析器的接口
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
初始化MultipartResolver
MultipartResolver 用于处理文件上传,需要添加multipart解析器,如下
在DispatcherServlet的配置文件中指定
<!-- 文件上传,id名称固定 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760"/> <!-- 文件最大size限制为10m -->
<property name="maxInMemorySize" value="4096" /> <!-- 文件上传过程中使用的最大内存块 -->
<property name="defaultEncoding" value="UTF-8"></property>
</bean>
initMultipartResolver
就是获取 CommonsMultipartResolver
的 bean 赋给 DispatcherServlet#multipartResolver
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
......
}
}
catch (NoSuchBeanDefinitionException ex) {
// Default is no multipart resolver.
this.multipartResolver = null;
......
}
}
}
初始化HandlerMappings
DispatcherServlet 按优先级的高低来选择 HandlerMapping ,如果当前的 HandlerMapping 能够返回可用的handler,则使用当前返回的 handler 来处理请求。
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// 默认为true,从所有IOC容器中去获取
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<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else { // 根据名称从当前的IOC容器中通过getBean获取handlerMapping
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.
}
}
// 仍没有找到的化需要为servlet生成默认的handlerMappings,
// 默认值设置在 DispatcherServlet.properties
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
......
}
}
}
关于 detectAllHandlerMappings
<servlet>
<init-param>
<param-name>detectAllHandlerMappings</init-param>
<param-value>false</param-value>
</init-param>
若是你有如上设置,则 SpringMVC 将查找名为 “handlerMapping” 的bean,并作为唯一的handlerMapping。否则会加载该WebApplicationContext下的所有handlerMapping。若是没有则调用 getDefaultStrategies
创建。
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList<>(classNames.length);
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
......
}
return strategies;
}
else {
return new LinkedList<>();
}
}
获取配置文件中设置的 HandlerMapping
,关于 defaultStrategies
private static final Properties defaultStrategies;
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
......
}
文件名为 "DispatcherServlet.properties"
,来看看该配置文件指定的 HandlerMappingorg.springframework.web.servlet.DispatcherServlet
所在目录下的 DispatcherServlet.properties
中定义的 HandlerMapping
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
初始化HandlerAdapters
代码逻辑与上面一样,同样可以设置 detectAllHandlerAdapters
值为 false, 来加载名为 handlerAdapter
的 HandlerAdapter 的 bean, 用户可以通过此来加载自定义的 HandlerAdapter 。来看看 Spring 配置文件DispatcherServlet.properties
中指定的 HandlerAdapter。
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter