《看透springMvc源代码分析与实践》学习笔记
SpringMVC 版本4.1.5.RELEASE
HandlerMapping的作用是:根据request找到相应的处理器Handler和Interceptors
,
HandlerMapping接口只有一个方法:
//org.springframework.web.servlet.HandlerMapping
public interface HandlerMapping {
//HandlerExecutionChain(包括 处理器Handler和Interceptors等)
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
方法的实现非常灵活,只需使用request返回HandlerExecutionChain
即可,我们也可以定义自己的HandlerMapping,只需要实现getHandler
方法,并注册到spring容器即可 。
在DispatcherServlet.doDispatch()
方法中使用getHandler()
,来匹配获取处理器:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
//logger 略.....
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
,通过这段逻辑可以找到一个request对应的
如果一个request找到了与之对应的HandlerMapping,如何决定使用哪一个呢?
HandlerAdapter可以理解为使用Handler干活的人。它一共有三个方法:
//org.springframework.web.servlet.HandlerAdapter
public interface HandlerAdapter {
//判断当前HandlerAdapter是否支持使用给出的参数handler
boolean supports(Object handler);
//使用具体Handler处理request请求
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
//获取资源最后一次处理时间
long getLastModified(HttpServletRequest request, Object handler);
}
之所以要使用HandlerAdapter,是因为Spring MVC没有对处理器有任何限制,它是Object类型的,这种模式给开发者提供了极大的便利。
同HandlerMapping
一样,HandlerAdapter也必须注册到spring容器中才能使用。
选择使用哪一个HandlerAdapter,它的逻辑定义在DispatcherServlet.getHandlerAdapter()
中:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
//logger 略.....
if (ha.supports(handler)) {
return ha;
}
}
//未找到匹配HandlerAdapter,抛出异常...
throw new ServletException("No adapter for handler [" + handler +
"]: Does your handler implement a supported interface like Controller?");
}
别的组件都是在正常情况下用来干活的,不过干活的过程中难免会出现问题,出现问题后就需要HandlerExceptionResolver
对异常进行处理。
具体来说,此组件的作用是根据异常设置ModelAndView
,之后再交给render方法进行渲染。
HandlerExceptionResolver的接口非常简单:
//org.springframework.web.servlet.HandlerExceptionResolver
public interface HandlerExceptionResolver {
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}
ViewResolver
用来将String类型的视图名和Locale解析为View类型视图
的组件,ViewResolver接口非常简单:
//org.springframework.web.servlet.ViewResolver
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
从接口方法的定义可以看出解析视图所需的参数是viewName和Locale
,一般情况下我们只需要根据viewName
找到对应的视图,然后进行渲染就行了,并不需要对不同
的区域使用不同的视图进行显示。
如果需要支持国际化或不同主题时,可以使用此参数。
如ResourceBundleViewResolver
,它就需要同时使用viewName和locale来解析视图。
ResourceBundleViewResolver需要将每一个视图名和对应的视图类型配置到相应的properties文件中,默认使用classpath下的views为baseName的配置文件,
如:views.properties,views_zh_CN.properties等,配置文件中需要同时配置class和url两项内容:
hello.(class)=org.springframework.web.servlet.view.InternalResourceView
hello.url=/WEB-INF/go.jsp
View
是用来渲染页面的,通俗来说就是将程序返回的参数填入模板里,生成html(也可以是其他类型)文件,这里有两个关键问题:
这其实就是ViewResolver主要做的工作,ViewResolver需要找到渲染使用的模板和所用的技术(也就是视图类型)进行渲染,具体的渲染过程则交给不同的视图自己完成。
常用的ViewResolver如下图,默认使用(org.springframework.web.servlet.view.InternalResourceViewResolver)
:
ViewResolver是根据viewName查找View,但是有的Handler处理完之后并没有设置View也没有设置viewName,这时就需要从request获取viewName了,
如何从request中获取viewName,就是RequestToViewNameTranslator
需要做的工作。它的接口定义如下,它仅有一个方法:
//org.springframework.web.servlet.RequestToViewNameTranslator
public interface RequestToViewNameTranslator {
//根据request获取viewName
String getViewName(HttpServletRequest request) throws Exception;
}
例:
public class MyRequest2ViewNameTranslater implements RequestToViewNameTranslator {
@Override
public String getViewName(HttpServletRequest request) throws Exception {
if(request.getRequestURI().toString().startsWith("/oa") && request.getMethod().equalsIgnoreCase("GET")){
return "oaIndexPage";
}
return "404";
}
}
注意:RequestToViewNameTranslator在SpringMvc容器里只能配置一个
,所有request到viewName的转换规则都要在一个RequestToViewNameTranslator中全部实现.
解析视图需要两个参数:一个视图名viewName,另一个local. 视图名是处理结果返回的(或者RequestToViewNameTranslator
解析的默认视图名),Local从哪里来。
这个就是LocalResolver要做的工作。LocalResolver用于从request中解析出Local.它是i18n的基础。
LocaleResolver接口也很简单,定义了两个方法:
//org.springframework.web.servlet.LocaleResolver
public interface LocaleResolver {
//从request中解析出Locale
Locale resolveLocale(HttpServletRequest request);
//将Locale设置给某个request
void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale);
}
在DispatcherServlet.doService()
中定义了如下代码:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
//省略其他.....
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
//将localeResolver,themeResolver等设置到request-attribute中
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
}
将localeResolver设置到request-attribute中,这样我们在需要使用Locale时,直接从request中取出localeResolver然后解析出Locale.
SpringMVC主要在如下两个地方使用到Locale:
RequestContext的getMessage()和getThemeMessage()方法时。
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
手动修改Local
许多网站可以手动选择语言,这就需要提供手动修改Locale的接口。在SpringMVC中非常简单,只需要调用LocaleResolver.setLocale方法即可。
org.springframework.web.servlet.i18n.LocaleChangeInterceptor
,配置方式如下:
然后,我们可以通过在请求中增加参数locale来修改Locale了:如:http://localhost:8080?locale=zh_CN
ThemeResolver是用来解析主题的,它的接口定义如下:
//org.springframework.web.servlet.ThemeResolver
public interface ThemeResolver {
//根据request获取themeName
String resolveThemeName(HttpServletRequest request);
//将themeName设置到指定的request
void setThemeName(HttpServletRequest request, HttpServletResponse response, String themeName);
}
不同的主题其实就是:换了一套图片、显示效果以及样式文件等。
SpringMVC中一套主题对应一个.properties文件
,里面存放着跟当前主题相关的所有资源,如图片,css文件等: 例如:
### theme.propertie
logo.pic=/images/default/logo.jpg
logo.word=excelib
style=/css/default/style.css
将上面的文件命名为theme.properties,放到classpath下面就可以在页面中使用了。
例,在jsp页面中:
就可以获得配置的excelib
了。
现在所用的主题名就叫theme
,主题名就是文件名,所以创建主题非常简单,只需要准备好资源,创建一个以主题名命名的properties文件即可。
获取主题资源的逻辑,依然在RequestContext
中:
//org.springframework.web.servlet.support.RequestContext
public String getThemeMessage(String code, String defaultMessage) {
return getTheme().getMessageSource().getMessage(code, null, defaultMessage, this.locale);
}
public final Theme getTheme() {
if (this.theme == null) {
// Lazily determine theme to use for this RequestContext.
this.theme = RequestContextUtils.getTheme(this.request);
if (this.theme == null) {
// No ThemeResolver and ThemeSource available -> try fallback.
this.theme = getFallbackTheme();
}
}
return this.theme;
}
由此看出SpringMvc获取主题资源的大致逻辑 :
ThemeResolver默认使用FixedThemeResolver
,ThemeSource默认使用 WebApplicationContext
,我们也可以自己来配置:
我们可以参照切换Locale方式切换主题:
MultipartResolver
用于处理上传请求,处理方式是将普通的request包装成MultipartHttpServletRequest,
getFile()
方法,获取文件getFileMap
得到{fileName -> file}结构的Map.如果不使用MultipartResolver将request封装成MultipartHttpServletRequest,直接使用request,也是可以获取上传的文件的。
MultipartResolver接口定义如下:
//org.springframework.web.multipart.MultipartResolver
public interface MultipartResolver {
//判断是否为上传请求
boolean isMultipart(HttpServletRequest request);
//将request封装为MultipartHttpServletRequest
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
//处理完后,清理上传过程中产生的临时资源
void cleanupMultipart(MultipartHttpServletRequest request);
}
判断是否为上传请求:可以简单的判断是不是multipart/form-data 类型。
FlashMap
主要用于在redirect
中传递参数。而FlashMapManager
便是用来管理FlashMap
的。
FlashMapManager的接口定义如下:
//org.springframework.web.servlet.FlashMapManager
public interface FlashMapManager {
//接收从其他request传递过来的request-attributes的map , 只读(readonly)
String INPUT_FLASH_MAP_ATTRIBUTE = FlashMapManager.class.getName() + ".INPUT_FLASH_MAP";
//需要传递出去持有request-attributes的map
String OUTPUT_FLASH_MAP_ATTRIBUTE = FlashMapManager.class.getName() + ".OUTPUT_FLASH_MAP";
/**
* a. 获取"input" FlashMap,并从底层缓存中将它移除。
* b. 创建"output" FlashMap
* c. 清空过期的 FlashMap
*/
void requestStarted(HttpServletRequest request);
/**
* a. 开始计算"output" FlashMap在底层缓存中的过期时间
*
* 注: 如果"output" FlashMap或者不是由当前FlashMapManager创建的话,则不会保存到底层缓存中
*/
void requestCompleted(HttpServletRequest request);
}
例:
@RequestMapping("/submit")
public String submit(RedirectAttributes attr) {
//获取FlashMap方法1
((FlashMap) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest().getAttribute(FlashMapManager.OUTPUT_FLASH_MAP_ATTRIBUTE)).put("name", "特朗普传");
//获取FlashMap方法2:RedirectAttributes.addFlashAttribute:: 同方法一效果
attr.addFlashAttribute("orderId", "9901");
//获取FlashMap方法3:RedirectAttributes.addAttribute ::参数会拼接到url后面。
attr.addAttribute("price", "zh-cn");
return "redirect:detail";
}
@RequestMapping("/detail")
public String detail(Model model) {
//和普通方式获取attribute相同
System.out.println(model.asMap());
return "success";
}