doDispatch看完SpringMVC流程也就基本完成了
接下来就是了解流程内SpringMVC是如何完成请求处理,与结果封装等操作的。即用到了哪些组件?组件的作用?是否可自定义组件?
本文只是大致的了解这些组件的接口
文章目录
1. HandlerMapping
根据request找到相应的处理器Handler和Interceptors
/ * *
*由定义之间映射的对象实现的接口
*请求和处理器对象。
*
这个类可以由应用程序开发人员实现,虽然这不是
*必要的,如{@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}
*和{@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping}
*已包含在框架内。如果没有,则默认为前者
HandlerMapping bean在应用程序上下文中注册。
*
处理器映射实现可以支持映射拦截器
*/
接口内只有一个方法:
/ * *
*为这个请求返回一个处理程序和任何拦截器。可以做出选择了
*根据请求URL、会话状态或实现类选择的任何因素。
返回的HandlerExecutionChain包含一个处理程序对象,而不是
*甚至一个标签接口,这样处理程序不受任何限制。
例如,可以编写一个HandlerAdapter来允许另一个框架的
*要使用的处理程序对象。
*
返回{@code null},如果没有找到匹配。这不是错误。
DispatcherServlet将查询所有要查找的已注册HandlerMapping bean
* @param请求当前HTTP请求
返回一个HandlerExecutionChain实例,其中包含处理程序对象和
*任何拦截器,或{@code null},如果没有找到映射
* @抛出异常,如果有内部错误
* /
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
2. HandlerAdapter
/**
* MVC框架SPI,允许参数化核心MVC工作流。
bb0接口,该接口必须为每个处理程序类型实现以处理请求。
这个接口允许{@link DispatcherServlet}无限扩展。
{@code DispatcherServlet}通过
*这个接口访问所有安装的处理程序,这意味着它不包含特定于任何处理程序类型的代码。
注意,处理程序的类型可以是{@code Object}。
这是为了使来自其他框架的处理程序无需自定义编码就能与此框架集成,
同时也允许注解驱动的处理程序对象不遵循任何特定的Java接口。
此接口不适用于应用程序开发人员。它适用于那些想要开发自己的web工作流的处理程序。
注意:{@code HandlerAdapter}实现者可以实现{@link * org.springframework.core。order}
接口可以为{@code DispatcherServlet}的应用指定一个排序*顺序(以及一个优先级)。*未排序的实例被视为最低优先级。
* ./
其内有三个方法:
supports(Object handler)判断是否可以使用某个Handler;handler方法是用来具体使用Handler干活;getLastModified是获取资源的Last-Modified,Last-Modified是资源最后一次修改的时间。
/ * *
*给定一个处理程序实例,返回是否使用这个{@code HandlerAdapter}
*可以支持它。典型的handleradapter将基于处理程序进行决策
*类型。handleradapter通常只支持每种处理器类型。
*
一个典型实现:
* < p > {@code
* return (handler instanceof MyHandler);
*}
*要检查的@param handler handler对象
* @return该对象是否可以使用给定的处理程序
* /
boolean supports(Object handler);
/ * *
*使用给定的处理程序来处理此请求。
*所需的工作流程可能有很大的不同。
* @param请求当前HTTP请求
* @param响应当前HTTP响应
* @param处理器要使用的处理器。此对象必须以前已被传递
*到该接口的{@code supports}方法,该方法必须具有
*返回{@code true}。
* @抛出错误时的异常
返回一个ModelAndView对象,其中包含视图的名称和所需的参数
*模型数据,或{@code null},如果请求已被直接处理
* /
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
/ * *
*与HttpServlet的{@code getLastModified}方法相同的契约。
如果处理程序类不支持,
*可以简单地返回-1。
* @param请求当前HTTP请求
* @param处理器要使用的处理器
返回给定处理程序的lastModified值
* @see javax.servlet.http.HttpServlet # getLastModified
* @see org.springframework.web.servlet.mvc.LastModified # getLastModified
* /
long getLastModified(HttpServletRequest request, Object handler);
可以查看SpringMVC自己的org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//用来使用实现了Controller接口的处理器干活的,干活的方法是直接调用处理器的handleRequest方法。
return ((Controller) handler).handleRequest(request, response);
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
}
}
在org.springframework.web.servlet.DispatcherServlet中,获取handlerAdapter是通过遍历得到的:
/ * *
*返回此处理程序对象的HandlerAdapter。
要为其查找适配器的处理程序对象
* @抛出ServletException,如果没有找到处理程序的处理器适配器。
* 这是一个致命错误。
* /
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
3. HandlerExceptionResolver
一个专门的角色对异常情况进行处理的组件
/ * *
*由对象实现的接口,这些对象可以解决期间抛出的异常
处理程序映射或执行,在典型情况下,到错误视图。实现者是
*通常在应用程序上下文中注册为bean。
*
>错误视图类似于JSP错误页面,但是可以与任何类型的错误页面一起使用
*异常包括任何检查异常,与潜在的细粒度映射
*具体处理程序。
*
* /
public interface HandlerExceptionResolver {
此组件的作用是根据异常设置ModelAndView,然后再交给render方法进行渲染。
该接口只有一个方法:
/ * *
*尝试解决处理程序执行过程中抛出的给定异常,
*返回一个表示特定错误页面的{@link ModelAndView}。
*
返回的{@code ModelAndView}可以是{@linkplain ModelAndView#isEmpty() empty}
*表示异常已成功解决,但没有视图
*应该被呈现,例如通过设置状态码。
* @param请求当前HTTP请求
* @param响应当前HTTP响应
执行的处理程序,或{@code null},如果没有选择
*异常时间(例如,多部分解析失败)
* @param ex处理程序执行期间抛出的异常
返回一个对应的{@code ModelAndView}来转发,
*或{@code null}用于解析链中的默认处理
* /
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
该方法,只需要从异常解析出Model-AndView就可以了。
4. ViewResolver
ViewResolver用来将String类型的视图名(有的地方也叫逻辑视图,都指同一个东西)和Locale解析为View类型的视图。
/ * *
*接口,由可以通过名称解析视图的对象实现。
*
*
视图状态在应用程序运行过程中没有改变,
*实现可以自由缓存视图。
*
* <p鼓励>实现支持国际化,
*即本地化视图分辨率。
*
* @see org.springframework.web.servlet.view.InternalResourceViewResolver
* @see org.springframework.web.servlet.view.ResourceBundleViewResolver
* @see org.springframework.web.servlet.view.XmlViewResolver
* /
public interface ViewResolver {
该接口只有一方法,如下:
/ * *
按名称解析给定视图。
注意:为了允许ViewResolver链接,ViewResolver应该
*如果未定义具有给定名称的视图,则返回{@code null}。
*然而,这不是必需的:一些ViewResolvers将总是尝试
*以给定的名称构建视图对象,无法返回{@code null}
*(而是在创建视图失败时抛出异常)。
要解析的视图的名称
* @param locale解析视图的locale。
*支持国际化的ViewResolvers应该尊重这一点。
* @返回视图对象,如果没有找到,返回{@code null}
*(可选,允许ViewResolver链接)
* @抛出异常,如果不能解决视图
*(通常在创建实际视图对象时出现问题)
*/
@Nullable
View resolveViewName(String viewName, Locale locale) throws Exception;
可以看出解析视图所需的参数是视图名和Locale,不过一般情况下我们只需要根据视图名找到对应的视图,然后渲染就行,并不需要对不同的区域使用不同的视图进行显示,如果需要国际化支持也只要将显示的内容或者主题使用国际化支持。
View是用来渲染页面的,通俗点说就是要将程序返回的参数填入模板里,生成html(也可能是其他类型)文件。
ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交给不同的视图自己完成。我们最常使用的UrlBasedViewResolver系列的解析器都是针对单一视图类型进行解析的,只需要找到使用的模板就可以了,比如,InternalResourceViewResolver只针对jsp类型的视图,FreeMarkerViewResolver只针对FreeMarker,VelocityViewResolver只针对Velocity。而ResourceBundleViewResolver、XmlViewResolver、BeanNameViewResolver等解析器可以同时解析多种类型的视图。
举个例子:
@RequestMapping("/success")
public String say2() {
return "success";
}
InternalResourceViewResolver就是将返回值success映射到success.jsp。
5. RequestToViewNameTranslator
从request获取view-Name就是RequestToViewNameTranslator要做的事情。以处理不存在viewName的情况。比如错误页面,
/ * *
*翻译传入数据的策略接口
* {@link javax.servlet.http。HttpServletRequest}到一个
*没有显式提供视图名称时的逻辑视图名称。
* /
public interface RequestToViewNameTranslator {
其接口内只有一个getView方法:
/ * *
*将给定的{@link HttpServletRequest}翻译为视图名称。
* @param请求传入的{@link HttpServletRequest}提供
要解析视图名称的上下文
* @返回视图名称,如果没有找到默认值,则返回{@code null}
* @抛出异常,如果视图名转换失败
* /
@Nullable
String getViewName(HttpServletRequest request) throws Exception;
值得注意的是:RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。
6. LocaleResolver
一个是视图名,另一个是Locale。视图名是处理器返回的(或者使用RequestToViewNameTranslator解析的默认视图名),而Locale则从LocaleResolver来。
LocaleResolver用于从request解析出Locale。
/ * *
*基于web的语言环境解决策略的接口,允许
通过请求进行语言环境解析和通过语言环境修改
*请求和响应。
*
该接口允许基于请求、会话、
*饼干等。默认实现是
* {@link org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver},
简单地使用由各自的HTTP头提供的请求区域设置。
*
*
使用{@link org.springframework.web. servlets .support. requestcontext #getLocale()}
*在控制器或视图中检索当前语言环境
*可能包括相关时区信息。春天的
*提供的解析器实现实现扩展
* {@link LocaleContextResolver}接口。
* @see LocaleContextResolver
* @see org.springframework.context.i18n.LocaleContextHolder
* @see org.springframework.web.servlet.support.RequestContext # getLocale
* @see org.springframework.web.servlet.support.RequestContextUtils # getLocale
* /
public interface LocaleResolver {
接口内有两个方法:
/ * *
从request解析出Locale
*通过给定的请求解析当前的语言环境。
*可以在任何情况下返回默认的语言环境作为回退。
* @param请求解析语言环境的请求
* @return当前语言环境(never {@code null})
* /
Locale resolveLocale(HttpServletRequest request);
/ * *
将特定的Locale设置给某个request。
*将当前语言环境设置为给定的语言环境。
* @param请求用于语言环境修改的请求
* @param响应用于语言环境修改的响应
* @param locale是新的locale,或者{@code null}清除locale
* @抛出UnsupportedOperationException如果LocaleResolver
* 实现不支持语言环境的动态更改
* /
void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale);
这俩方法基本就是setter,getter。
网站可以选择显示什么语言,这就需要提供人为修改Locale的机制。
Spring MVC中主要在两个地方用到了Locale:①ViewResolver解析视图的时候;②使用到国际化资源或者主题的时候。
如果我们想要修改Locale,我们没有必要自己再写个controller。可以选用Interceptor,即org.springframework.web.servlet.i18n.LocaleChangeInterceptor
<bean id="localeResolver" class="org.springframewrok.web.servlet
.i18n.SessionLocaleResolver">
<property name="defaultLocale" value="en"/>
</bean>
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
lci.setParamName("lang");
return lci;
}
不过想要使用国际化,前提在resources目录下建立 messages_en_US.properties、messages_zh_CN.properties语言配置文件。感兴趣的可以去了解一下。
还有一种修改方法是树上的,没有验证过。
7. ThemeResolver
ThemeResolver用来解析主题。
/ * *
*界面为基于web的主题解决策略,允许
*既可以通过请求解决主题,也可以通过修改主题
*请求和响应。
*
该接口允许基于会话的实现,
*饼干等。默认实现是
* {@link org.springframework.web.servlet.theme.FixedThemeResolver},
简单地使用一个配置的默认主题。
*
请注意,该解析器仅负责确定
当前主题名称。已解析主题名称的主题实例
*通过各自的Th由DispatcherServlet查找
*
*
使用{@link org.springframework.web. servlets . support.requestcontext #getTheme()}
*在控制器或视图中检索当前主题,独立
*的实际解决策略。
* @see org.springframework.ui.context.Theme
* @see org.springframework.ui.context.ThemeSource
*/
public interface ThemeResolver {
/ * *
*通过给定的请求解析当前的主题名称。
*应该在任何情况下返回默认的主题作为回退。
* @param请求用于解析的请求
* @返回当前主题名称
* /
String resolveThemeName(HttpServletRequest request);
/ * *
*设置当前主题的名称为给定的。
用于修改主题名称的请求
* @param响应用于修改主题名称
* @param themeName新主题名称({@code null}或空重置)
* @抛出UnsupportedOperationException如果ThemeResolver实现
*不支持动态更改主题
* /
void setThemeName(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable String themeName);
}
不多看这个。感觉没啥用。
8. MultipartResolver
MultipartResolver用于处理上传请求,处理方法是将普通的request包装成Multipart-HttpServletRequest,后者可以直接调用getFile方法获取到File。
在DispatcherServlet的doDispatch方法中就判断了一下请求是否为上传请求:
接口如下:
public interface MultipartResolver {
/ * *
*确定给定的请求是否包含多部分内容。
通常会检查内容类型为“multipart/form-data”,但实际上不会
*接受的请求可能取决于解析器实现的能力。
* @param请求要评估的servlet请求
* @return请求是否包含多部分内容
* /
boolean isMultipart(HttpServletRequest request);
/ * *
*解析给定的HTTP请求成多部分文件和参数,
*并将请求包装在a中
* {@link org.springframework.web.multipart.MultipartHttpServletRequest}
*提供对文件描述符的访问并使其包含的对象
*通过标准ServletRequest方法访问的参数。
封装的servlet请求(必须是多部分内容类型)
@return包装好的servlet请求
@抛出MultipartException,如果servlet请求不是multipart,或者如果
*遇到特定于实现的问题(例如超过文件大小限制)
* @see MultipartHttpServletRequest # getFile
* @see MultipartHttpServletRequest # getfilename
* @see MultipartHttpServletRequest # getFileMap
* @see javax.servlet.http.HttpServletRequest # getParameter
* @see javax.servlet.http.HttpServletRequest # getParameterNames
* @see javax.servlet.http.HttpServletRequest#getParameterMap
*/
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
/ * *
*清理用于多部分处理的所有资源,
*像一个存储上传的文件。
* @param请求清理资源的请求
* /
void cleanupMultipart(MultipartHttpServletRequest request);
}
9. FlashMapManager
用在redirect中传递参数。
/**
*用于检索和保存FlashMap实例的策略接口。
*查看{@link FlashMap}了解flash属性的一般概述。
*
*/
public interface FlashMapManager {
/ * *
*查找先前保存的与当前请求匹配的FlashMap
*请求,删除它从底层存储,也删除其他
*过期的FlashMap实例。
相比之下,该方法在每个请求开始时被调用
*到{@link #saveOutputFlashMap},只有在存在时才调用
*要保存的flash属性-例如,在重定向之前。
* @param请求当前请求
* @param响应当前响应
* @return匹配当前请求的FlashMap或{@code null}
* /
@Nullable
FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
/ * *
*保存给定的FlashMap,在一些底层存储并设置开始
*其有效期。
*
注意:在重定向前按顺序调用此方法
*允许在HTTP会话或响应中保存FlashMap
* cookie在响应提交之前。
* @param flashMap要保存的flashMap
* @param请求当前请求
* @param响应当前响应
* /
void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);
}
默认实现是org.springframework.web.servlet.support.SessionFlashMapManager,它是将参数保存到session中。
整个redirect的参数通过FlashMap传递的过程分三步:
1)在处理器中将需要传递的参数设置到outputFlashMap中,设置方法在分析Dispat-cherServlet的时候已经介绍了,可以直接使用request.getAttribute-(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)拿到outputFlashMap,然后将参数put进去,也可以将需要传递的参数设置到处理器的RedirectAttributes类型的参数中,当处理器处理完请求时,如果是redirect类型的返回值RequestMappingHandlerAdapter会将其设置到outputFlashMap中。
2)在RedirectView的renderMergedOutputModel方法中调用FlashMapManager的saveOutput-FlashMap方法,将outputFlashMap中的参数设置到Session中。
3)请求redirect后DispatcherServlet的doServic会调用FlashMapManager的retrieveAnd-Update方法从Session中获取inputFlashMap并设置到Request的属性中备用,同时从Session中删除。