一、SpringMVC之初体验
(1)在web.xml中配置Servlet
(2) 创建Controller和view
(3) 创建SpringMVC的xml配置文件
二、创建SpringMVC之器
SpringMVC中核心Servlet的继承结构如下图所示:
GenericServlet和HttpServlet在JAVA中,剩下的HttpServletBean、FrameworkServlet和DispatcherServlet是SpringMVC中的。
SpringMVC中Servlet一共三个层次,分别是HttpServletBean、FrameworkServlet和DispatcherServlet。HttpServletBean直接继承Java中的HttpServlet,其作用是Servlet中配置的参数设置到相应的属性;FrameworkServlet初始化了webApplicationContext;DispatcherServlet初始化了自身的九个组件。
三、SpringMVC之用
1.HttpServletBean
HttpServletBean主要参与了创建工作,并没有涉及请求的处理。
2.FrameworkServlet
Servlet的处理过程:首先是从Servlet接口的service方法开始,然后再HttpServlet的service方法中根据请求的类型不同将请求路由到了doGet、doHead、doPost、doPut、doDelete、doOptions和doTrace七个方法,并且做了doHead、doOptions和doTrace的默认实现,其中doHead调用doGet,然后返回只有header没有body的response。
在FrameworkServlet中重写了service、doGet、doPost、doPut、doDelete、doOptions、doTrace方法(除了doHead的所有处理请求的方法)。在service方法中添加了对PATCH类型请求的处理,其他类型的请求直接交给了父类进行处理;doOptions和doTrace方法可以通过设置dispatchOptionsRequest和dispatchTraceRequest参数决定是由自己处理还是由父类处理(默认都是交给父类处理,doOptions会在父类的处理结果中添加PATCH类型);doGet,doPost,doPut和doDelete都是自己处理。所有需要自己处理的请求都交给processRequest方法进行统一处理。
ProcessRequest是FrameworkServlet类在处理请求中最核心的方法。processRequest中的核心语句是doService,这是一个模板方法,在DispatcherServlet中具体实现。除了异步请求和调用doService方法具体处理请求,processRequest自己主要做了两件事:
首先看下LocaleContext和RequestAttributes。LocaleContext里面存放着Locale(也就是本地化信息,如zh-cn等),RequestAttributes是spring的一个接口,通过它可以get/set/removeAttribute,根据scope参数判断操作request还是session。这里具体使用的是ServletRequestAttributes类,在ServletRequestAttributes里面还封装了request、response和session,而且都提供了get方法,可以直接获取。
当publishEvents设置为true时,请求处理结束后就会发出这个消息,无论请求处理成功与否都会发布。publishEvents可以在web.xml文件中配置SpringMVC的Servlet时配置,默认为true。我们可以通过监听这个事件来做一些事情,如记录日志。
3.DispatcherServlet
DispatcherServlet是SpringMVC最核心的类,整个处理过程的顶层设计都在这里面。
DispatcherServlet里面执行处理的入口方法应该是doService,不过doService并没有直接进行处理,而是交给了doDispatch进行具体的处理,在调用doDispatch之前,doService做了一些事:首先判断是不是include请求,如果是则对request的Attribute做个快照备份,等doDispatch处理完之后进行还原,在昨晚快照后又对request设置了一些属性。
对request设置的属性中,前面4个属性,webApplicationContext、localeResolver、themeResolver和themeSource在之后介绍的handler和view中需要使用。后面三个属性都和flashMap相关,主要用于Redirect转发时参数的传递,比如,为了避免重复提交表单,可以在处理完post请求后redirect到一个get的请求,这样即使用户刷新也不会有重复提交的问题。不过这里有个问题,前面的post请求时提交订单,提交完后redirect到一个现实订单的页面,显然在显示订单的页面需要知道订单的一些信息,但redirect本身是没有传递参数的功能的,按普通的模式如果想传递参数,只能将其写入url中,但是url有长度限制,另外有些场景中我们想传递的参数还不想暴露在url里,这时就可以用flashMap来进行传递了,我们只需要在redirect之前将需要传递的参数写入OUTPUT_FLASH_MAP_ATTRIBUTE,如下:
((FlashMap)((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)).put(“name”,”张三丰”);
这样在redirect之后的handler中spring就会自动将其设置到model里(先设置到INPUT_FLASH_MAP_ATTRIBUTE属性里,然后再放到model里)。当然这样操作还是有点麻烦,spring还给我们提供了更加简单的操作方法,我们只需要在handler方法的参数中定义RedirectAttributes类型的变量,然后把需要保存的属性设置到里面就行,之后的事情spring自动完成。RedirectAttributes有两种设置参数的方法addAttribute(key,value)和addFlashAttribute(key,value),用第一个方法设置的参数会拼接到url中,第二个方法设置的参数就是用我们刚才所讲的flashMap保存的。比如,一个提交订单的Controller可以这样写:
@RequestMapping(Value=”/submit”,method=RequestMethod.POST)
public String submit(RedirectAttributes attr) throws IOException{
((FlashMap)((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)).put(“name”,”张三丰”);
attr.addFlashAttribute(“ordersId”,”xxx”);
attr.addAttribute(“local”,”zh-cn”);
return “redirect:showorders”;
}
@RequestMapping(value=”/showorders”,method=RequestMethod.POST)
public String showOrders(Model model) throws IOExcepion{
doSomething……
return “orders”;
}
这里分别使用三种方法来传递redirect参数:
从Request获取outputFlashMap除了直接获取DispatcherServlet.OUTPUT_FLASH_MAP
_ATTRIBUTE属性,还可以使用RequestContextUtils来操作:RequestContextUtils.getOutputF
lashMap(request),这样也可以得到outputFlashMap,其实它内部还是从Request的属性获取的。
当用户提交http://xxx/submit请求后浏览器地址栏会自动跳转到http://xxx/showorders?
Local=zh-cn链接,而在showOrders的model里会存在[“name”,”张三丰”]和[“ordersId”,”xxx”]两个属性,而且对客户端是透明的,用户并不知道。
这就是flashMap的用法,inputFlashMap用于保存上次请求中转发过来的属性,outputFlashMap用于保存本次请求需要转发的属性,FlashMapManager用于管理他们。
doDispatch方法也非常整洁,从顶层设计了整个请求处理过程。doDispatch中最核心的代码只要4句,他们的任务分别是:一、根据request找到Handler;二、根据Hanler找到对应的HandlerAdapter;三、用HandlerAdapter处理Handler;四、调用processDispatchResult方法处理上面处理之后的结果(包含找到View并渲染输出给用户),对应的代码如下:
mappedHandler = this.getHandler(processedRequest);
HandlerAdapter err1 = this.getHandlerAdapter(mappedHandler.getHandler());
err = err1.handle(processedRequest, response, mappedHandler.getHandler());
this.processDispatchResult(processedRequest, response, mappedHandler, err,
(Exception)dispatchException);
这里有三个概念:HandlerMapping、Handler和HandlerAdapter。
Handler:也就是处理器,它直接对应着MVC的C也就是Controller层,它的具体表现形式有很多,可以是类,也可以是方法。我们标注了@RequestMapping的所有方法都可以看成一个Handler。只要实际处理请求就可以是Handler。
HandlerMapping:是用来查找Handler的,在SpringMVC中会处理很多请求,每个请求都需要一个Handler来处理,具体接收到一个请求后使用哪个Handler来处理呢?这就是HandlerMapping要做的事。
HandlerAdapter:就是一个适配器。因为SpringMVC的Handler可以是任意的形式,只要能够处理请求就OK,但是Servlet需要的处理方法的结构确实固定的,都是以request和response为参数的方法。HandlerAdapter要做的事就是让固定的Servlet处理方法调用灵活的Handler来进行处理。
通俗的解释就是Handler是用来干活的工具,HandlerMapping用于根据需要干的活找到相应的工具,HandlerAdapter就是使用工具干活的人。SpringMVC中也一样,在九大组件中HandlerAdapter也是最复杂的。
另外View和ViewResolver的原理与Handler和HandlerMapping的原理类似。View是用来展示数据的,而ViewResolver用来查找View。通俗地讲就是干完活后需要写报告,写报告又需要模板,View就是所需要的模板,模板就像公文里边的格式,内容就是Model里边的数据,ViewResolver就是用来选择使用哪个模板的。
使用HandlerMapping找到干活的Handler,找到使用Handler的HandlerAdapter,让HandlerAdapter使用Handler干活。干完活将结果通过View展示给用户。
4.doDispatch结构
doDispatch大体可以分为两部分:处理请求和渲染页面。开头部分先定义了几个变量,如下:
doDispatch中首先检查是不是上传请求,如果是上传请求,则将request请求转换为MultipartHttpServletRequest,并将multipartRequestParsed标志设置为true。其中使用到了MultipartResolver。
然后通过getHandler方法获取Handler处理器链,其中使用到了HandlerMapping,返回值为HandlerExecutionChain类型,其中包含着与当前request相匹配的Interceptor和Handler。
方法结构非常简单,HandlerMapping在后面详解,HandlerExecutionChain的类型类似于前面Tomcat讲过的Pipeline,Interceptor和Handler相当于那里边的Value和BaseValue,执行时先依次执行Interceptor的preHandler方法,最后执行Handler,返回的时候按相反的顺序执行Interceptor的postHandler方法。就好像要去一个地方,Interceptor要经过的收费站,Handler是目的地,去的时候和返回的时候都要经过加油站,但两次所经过的顺序是相反的。
接下来是处理GET、HEAD请求的Last-Modified。当浏览器第一次跟服务器请求资源(GET、Head请求)时,服务器在返回的请求头里面会包含一个Last-Modified的属性,代表本资源最后是什么时候修改的。在浏览器以后发送请求时会同时发送之前接收到的Last-Modified,服务器接收到待Last-Modified的请求后会用其值和自己实际资源的最后修改时间做对比,如果资源过期了则返回新的资源(同时返回新的Last-Modified),否则直接返回304状态码表示资源未过期,浏览器直接使用之前缓存的结果。
接下来依次调用相应Interceptor的preHandler。
处理完Interceptor的preHandler后就到了此方法最关键的地方——让HandlerAdapter使用Handler处理请求,Controller就是在这个地方执行的。
Handler处理完请求后,如果需要异步处理,则直接返回,如果不需要异步处理,当view为空时(如Handler返回值为void),设置默认view,然后执行相应Interceptor的postHandle。
设置默认view的过程中使用到了ViewNameTranslator。
到这里请求处理的内容就完成了,接下来使用processDispatchResult方法处理前面返回的结果,其中包括处理异常、渲染页面、触发Interceptor的afterCompletion方法三部分内容。
doDispatch有两层异常捕获,内层是捕获在对请求进行处理的过程中抛出的异常,外层主要是在处理渲染页面时抛出的。内层的异常,也就是执行请求处理时的异常会设置到dispatchException变量,然后在processDispatchResult方法中进行处理,外层则是处理processDispatchResult方法抛出的异常。
看到processDispatchResult处理异常的方式其实就是将相应的错误页面设置到View,在其中的processHandlerException方法中用到了HandlerExceptionResolver。
渲染页面具体在render方法中执行,render中首先对response设置了Local,过程中使用了LocaleResolver,然后判断View如果是String类型则调用resolveViewName方法使用ViewResolver得到实际的View,最后调用View的render方法对页面进行具体渲染,渲染的过程中使用到了ThemeResolver。
最后通过mappedHandler的triggerAfterConpletion方法触发Interceptor的
afterCompletion方法,这里的Interceptor也是按反方向执行的。到这里processDispatchResult方法就执行完了。
再返回doDispatch方法中,在最后的finally中判断是否请求启动了异步处理,如果启动了则调用相应异步处理的拦截器,否则如果是上传请求则删除上传请求过程中产生的临时资源。
doDispatch方法就分析完了。可以看到SpringMVC的处理方式是先在顶层设计好整体架构,然后将具体的处理交给不同的组件具体去实现的。doDispatch的流程图如下图所示,中间是doDispatcher的处理流程图,左边是Interceptor相关处理方法的调用位置,右边是doDisPatcher方法处理过程中涉及的组件。图中上半部分的处理请求对应着MVC中的Controller也就是C层,下半部分的processDispatchResult主要对应了MVC中的View也就是V层,M层也就是Model贯穿于整个过程中。
三个Servlet的处理过程大致功能如下:
HttpServletBean:没有参与实际请求的处理。
FrameworkServlet:将不同类型的请求合并到processRequest方法统一处理,
processRequest方法中做了三件事:
调用了doService模板方法具体处理请求。
将当前请求的LocaleContext和ServletRequestAttributes在处理请求前设置到了LocaleContextHolder和RequestContextHolder,并在请求处理完成后恢复。
请求处理完成后发布了ServletRequestHandledEvent消息。
DispatcherServlet:doService方法给request设置了一些属性并将请求交给了doDispatch 方法具体处理。
DispatcherServlet中的doDispatch方法完成SpringMVC中请求处理过程的顶层设计,它使用DispatcherServlet中的九大组件完成了具体的请求处理。
四、SpringMVC组件概览
1.HandlerMapping
HandlerMapping的作用是根据request找到相应的处理器Handler和Interceptors,HandlerMapping接口里面只有一个方法。
HandlerExecutionChain getHandler(HttpServletRequest var1) throws Exception;
方法的实现非常简单,只要使用Request返回HandlerExecutionChain就可以了。我们也可以自己定义一个HandlerMapping,然后自己实现getHandler方法。比如,定义一个Tudou-
HandlerMapping,将tudou开头的请求对应到digua的处理器去处理,只需要判断请求的url:
如果是以tudo开头那么就返回地瓜的Handler。如果想更进一步还可以再细分,如将tudou开头的Get请求对应到maidigua(卖地瓜),而Post请求对应到shoudigua(收地瓜),其他类型全部交给digua处理器,代码如下:
public class DiguaHandlerMapping implements HandlerMapping {
@Override
public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
String url = request.getRequestURI().toString();
String method = request.getMethod();
if(url.startsWith("/tudou")){
if(method.equalsIgnoreCase("GET")){
return "maidigua"对应的Handler;
}
else if(method.equalsIgnoreCase("POST")){
return "SHOUDIGUA"对应的Handler;
}
else {
return "digua"对应的handler;
}
}
return null;
}
}
这里的HandlerMapping只能查找”tudou”相关的Handler,而更一般的做法是维护一个对应多个请求的map,其实SimpleUrlHandlerMapping的基本原理就是这样。HandlerMapping编写出来后需要注册到SpingMVC的容器里面才可以使用,注册也非常简单,只需要在配置文件里配置一个Bean就可以了,SpringMVC会按照类型将它注册到HandlerMapping中。
如果有个专门处理tudoupian(土豆片)的处理器(处理以tudoupian开头的url),而且是在另外的HandlerMapping中进行的映射,这就涉及有限使用哪个HandlerMapping来查找处理器的问题,如果先使用TudouHandlerMapping就会将tudoupian的请求也交给tudou的处理器,这样就出问题了!这时候使用HandlerMapping的顺序就非常重要了,这里的顺序可以通过order属性来定义(当然HandlerMapping需要实现Order接口),order越小越先使用,比如:
查找Handler是按顺序便利所有的HandlerMapping,当找到一个HandlerMapping后立即停止查找并返回。
2.HandlerAdapter
HandlerAdapter里面一共有三个方法,supports(Object handler)判断是否可以使用某个Handler;Handler方法是用来具体使用Handler干活;getLastModified是获取资源的Last-Modified,Last-Modified是资源最后一次修改的时间。
之所以使用HandlerAdapter是因为SpringMVC并没有对处理器做任何限制,处理器可以以任何合理的方式来表现,可以是一个类,也可以是一个方法,还可以是别的合理的方式,从handle方法可以看出它是Object的类型。
接着前面的例子写一个HandlerAdapter,首先写一个MaiDiguaController处理器,然后再写对应的HandlerAdapter,代码如下:
public class MaiDiguaController {
public ModelAndView maidigua(HttpServletRequest request, HttpServletResponse response){
}
}
public class MaiDiguaHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object o) {
return o instanceof MaiDiguaController;
}
@Override
public ModelAndView handle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, Object o) throws Exception {
return ((MaiDiguaController)o).maidigua(httpServletRequest,httpServletResponse);
}
@Override
public long getLastModified(HttpServletRequest httpServletRequest, Object o) {
return -1L;
}
}
这里写了一个卖地瓜的Handler——MaiDiguaController,用来具体处理卖地瓜这件事,MaiDiguaHandlerAdapter使用MaiDiguaController完成卖地瓜这件事。
下面看一个SpingMVC自己的HandlerAdapter——SimpleControllerHandlerAdapter,代码如下:
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
public SimpleControllerHandlerAdapter() {
}
public boolean supports(Object handler) {
return handler instanceof Controller;
}
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return ((Controller)handler).handleRequest(request, response);
}
public long getLastModified(HttpServletRequest request, Object handler) {
return handler instanceof LastModified?((LastModified)handler).getLastModified(request):-1L;
}
}
可以看到这个Adapter也非常简单,是用来使用实现Controller接口的处理器干活的,干活的方法是直接调用处理器的handleRequest方法。
选择使用哪个HandlerAdapter的过程在getHandlerAdapter方法中,它的逻辑是遍历所有的Adapter,然后检查哪个可以处理当前的Handler,找到第一个可以处理Handler的Adapter后就停止查找并将其返回。既然需要挨个检查,那就需要有一个顺序,这里的顺序同样是通过Order属性来设置的。
HandlerAdapter需要注册到SpringMVC的容器里,注册方法和HandlerMapping一样,只要配置一个Bean就可以了。Handler是从HandlerMapping里返回的。
3.HandlerExceptionResolver
别的组件都是在正常情况下用来干活的,不过干活的过程中难免会出现问题,出现问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。render只负责将ModelAndView渲染成页面,具体ModelAndView是怎么来的render并不关心。这也是SpringMVC设计优秀的一个表现——分工明确互不干涉。通过前面doDispatcher的分析可以知道HandlerExceptionResolver只是用于解析对请求做处理的过程中产生的异常,而渲染环节产生的异常不归它管,现在我们就知道原因了:它是在render之前工作的,先解析出ModelAndView之后render才去渲染的,所以它就不能处理render过程中的异常了。
HandlerExceptionResolver结构非常简单,只有一个方法,只需要从异常解析出ModelAndView就可以了。具体实现可以维护一个异常为key、value为view的Map,解析是直接从Map里获取View,如果在Map里没有相应的异常可以返回默认的view。
4.ViewResolver
ViewResolver用来将String类型的视图名和Locale解析为View类型的视图,ViewResolver接口也非常简单,只有一个方法。
public interface ViewResolver {
View resolveViewName(String var1, Locale var2) throws Exception;
}
从接口方法的定义可以看出解析视图所需的参数是视图名和Locale,不过一般情况下我们只需要根据视图名找到对应的视图,然后渲染就行,并不需要对不同的区域使用不同的视图进行显示,如果需要国际化支持也只要将显示的内容或者主题使用国际化支持,不过SpringMVC确实有这个功能,可以让不同的区域使用不同的视图进行显示。ResourceBundleViewResolver就需要同时使用视图名和Locale来解析视图。ResourceBundleViewResolver需要将每一个视图名和对应的视图类型配置到相应的properties文件中,默认使用classpath下的views为baseName的配置文件,如views.properties、views_zh_CN.properties等,baseName和文件位置都可以设置。不同的Locale会使用不同的配置文件,而且它们之间没有任何关系,这样就可以让不同的区域使用不同的View来进行渲染了。
View是用来渲染页面的,通俗点来说就是要将程序返回的参数填入模板里,生成html文件。这里有两个问题:使用哪个模板?用什么技术填入参数?
这其实就是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交给不同的视图自己完成。我们最常使用的UrlBasedViewResolver系列的解析器都是针对单一视图类型进行解析的。只需要找到使用的模板就可以了,比如,InternalResourceViewResolver只针对jsp类型的视图,FreeMarkerViewResolver只针对FreeMarker,VelocityViewResolver只针对Velocity。而ResourceBundleViewResolver、XmlViewResolver、BeanNameViewResolver等解析器可以同时解析多种类型的视图。如前面说过的ResourceBundleViewResolver,它是根据properties配置文件来解析的,配置文件里就需要同时配置class和url两项内容,比如:
hello.(class)=org.springframework.Web.servlet.view.InternalResourceView
hello.url=/WEB-INF/go.jsp
这样就将hello配置到/WEB-INF/go.jsp模板的jsp类型的视图了,当Controller返回hello时会使用这个视图来进行渲染。这里的jsp类型并不是根据go.jsp的后缀来确定的,这个后缀只是个文件名,可以把它改成任意别的格式,视图的类型是根据.class的配置项来确定的。
XmlViewResolver与ResourceBundleViewResolver类似,只不过它是使用xml文件来配置的。BeanNameViewResolver是根据ViewName从ApplicationContext容器中查找相应的bean做View的,这个实现比较简单,下面是BeanNameViewResolver的源码。
public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {
private int order = 2147483647;
public BeanNameViewResolver() {}
public void setOrder(int order) {this.order = order;}
public int getOrder() {return this.order;}
public View resolveViewName(String viewName, Locale locale) throws BeansException {
ApplicationContext context = this.getApplicationContext();
if(!context.containsBean(viewName)) {
if(this.logger.isDebugEnabled()) {
this.logger.debug("No matching bean found for view name \'" + viewName + "\'");
}
return null;
} else if(!context.isTypeMatch(viewName, View.class)) {
if(this.logger.isDebugEnabled()) {
this.logger.debug("Found matching bean for view name \'" + viewName + "\' - to be ignored since it does not implement View");
}
return null;
} else {
return (View)context.getBean(viewName, View.class);
}
}
}
可以看到其原理就是根据viewName从spring容器中查找Bean,如果查找不到或者查到后不是View类型则返回null,否则返回容器中的bean。
ViewResolver的使用需要注册到SpringMVC的容器里,默认使用的是org.spring
-framewoek.web.serlvet.view.InternalResourceViewResolver。
5.RequestToViewNameTranslator
ViewResovler是根据ViewName查找View,但有的Handler处理完后并没有设置View,也没有设置viewName,这时就需要从request获取viewName了,而如何从request获取viewName就是RequestToViewNameTranslator要做的事情。RequestToViewNameTranslator接口定义如下:
public interface RequestToViewNameTranslator {
String getViewName(HttpServletRequest var1) throws Exception;
}
其中只有一个getViewName方法,只要通过request获取到viewName就可以了。我们自己定义一个Translator,其中判断如果是tudou的Get请求则返回“maidigua”否则返回“404”
作为viewName。
public class MaiDiguaRequestToViewNameTranslator implements RequestToViewName
-Translator {
@Override
public String getViewName(HttpServletRequest request) throws Exception {
if(request.getRequestURI().toString().startsWith("/tudou")&&
request.getMethod().equalsIgnoreCase("GET")){
return "maidigua";
}
return "404";
}
}
实际使用时应该设置规则而不是将某个具体请求与ViewName的对应关系硬编码到程序里面,那样才可以具备更好的通用性。
RequestToViewNameTranslator在SpringMVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。
6.LocaleResolver
解析视图需要两个参数:一个是视图名,另一个是Locale。视图名是处理器返回的(或者使用RequestToViewNameTranslator解析的默认视图名),Locale是从哪里来的呢?这就是LocaleResolver要做的事情。
LocaleResolver用于从request解析出Locale。Locale就是zh-cn之类,表示一个区域。有了这个就可以对不同区域的用户显示不同的结果,这就是il8n(国际化)的基本原理,LocaleResolver是il8n的基础。LocaleResolver接口定义如下:
public interface LocaleResolver {
Locale resolveLocale(HttpServletRequest var1);
void setLocale(HttpServletRequest var1, HttpServletResponse var2, Locale var3);
}
接口定义非常简单,只有2个方法,分别表示:从request解析出Locale和将特定的Locale设置给某个request。之前doService方法中,容器会将LocaleResolver设置到request的attribute中,代码如下:
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
这样就让我们再需要使用Locale的时候可以直接从request拿到localeResolver,然后解析出Locale。
SpringMVC中主要在两个地方用到了Locale:
ViewResolver解析视图的时候;
使用国际化资源或者主题的时候。
国际化资源和主题主要使用RequestContext的getMessage和getThemeMessage方法。
LocaleResolver的作用就是选择区域,有时候需要提供人为设置区域的功能,比如很多网站可以选择显示什么语言,这就需要提供人为修改Locale的机制。在SpringMVC中非常简单,只需要调用LocaleResolver的setLocale方法即可。我们可以使用Interceptor来做到这一点,这就是org.springframework.web.servlet.i18n.LocaleChangeInterceptor,配置方法如下:
LocaleChangeInterceptor"/> 这样就可以通过locale参数来修改Locale了,比如: http://localhost:8080?locale=zh_CN http://localhost:8080?locale=en 这里的“locale”也可以通过paramName设置为别的名称,如设置为“lang”。 LocaleChangeInterceptor" p:paramName=”lang”/> 7.ThemeResolver ThemeResolver是解析主题用的。ThemeResolver的接口定义如下: public interface ThemeResolver { String resolveThemeName(HttpServletRequest var1); void setThemeName(HttpServletRequest var1, HttpServletResponse var2, String var3); } 不同的主题就是换了一套图片、显示效果以及样式等。SpringMVC中一套主题对应一个properties文件,里面存放着跟当前主题相关的所有资源,如图片、css样式表等,例如: #theme.properties logo.pic=/images/default/logo.jpg logo.word=excelib style=/css/default/style.css 将上面的文件命名为theme.properties,放到classpath下面就可以在页面中使用了,如果在jsp页面中,使用 /tags”%>)。现在所用的主题名就叫theme,主题名也就是文件名,所以创建主题非常简单,只需要准备好资源,然后新建一个以主题名为文件名的properties文件并将资源设置进去就可以了。另外,SpringMVC的主题也支持国际化,也就是说同一个主题不同的区域也可以显示不同的风格,比如,可以定义以为主题文件 theme.properties theme_zh_CN.properties Theme_en_US.properties 这样即使同样使用theme的主题,不同的区域也会调用不同主题文件里的资源进行显示。 SpringMVC中跟主题相关的类主要有ThemeResolver、ThemeSource和Theme。从上面的接口可以看出,ThemeResolver的作用是从request解析出主题名;ThemeSource则是根据主题名找到具体的主题;Theme是ThemeSource找出的一个具体的主题,可以通过它获取主题里具体的资源。获取主题的资源依然在RequestContext中,代码如下: //org.springframework.web.servlet.support.RequestContext public String getThemeMessage(String code, Object[] args, String defaultMessage) { return this.getTheme().getMessageSource().getMessage(code, args, defaultMessage, this.locale); } public Theme getTheme() { if(this.theme == null) { this.theme = RequestContextUtils.getTheme(this.request); if(this.theme == null) { this.theme = this.getFallbackTheme(); } } return this.theme; } 可以看到这里首先通过RequestContextUtils获取到Theme,然后获取对应的资源,再看一下RequestContextUtils是怎么获取Theme的: //org.springframework.web.servlet.support.RequestContextUtils public static Theme getTheme(HttpServletRequest request) { ThemeResolver themeResolver = getThemeResolver(request); ThemeSource themeSource = getThemeSource(request); if(themeResolver != null && themeSource != null) { String themeName = themeResolver.resolveThemeName(request); return themeSource.getTheme(themeName); } else { return null; } } 从这里就可以清楚地看到ThemeResolver和ThemeSource的作用。ThemeResolver的默认实现是org.springframework.web.servlet.theme.FixedThemeResolver,这里边使用的默认主题名就叫“theme”,这也就是前面使用theme主题时不用配置也可以使用的原因。从DispatcherServlet中可以看到ThemeResolver默认使用的是WebApplicationContext。 在讲SpringMVC容器创建时介绍过WebApplicationContext是在FrameworkServlet中创建的,默认使用的是XmlWebApplicationContext,其父类是AbstractRefreshableWebApplicaion- Context,这个类实现了ThemeSource接口,其实现方式是在内部封装了一个ThemeSource属性,然后将具体工作交给它去干。 现在我们就把整个原理弄明白了:主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这个是ThemeResolver的工作,然后用资源名称找到主题(可以理解为一个配置文件),这个ThemeSource的工作,最后使用主题获取里面具体的资源就可以了。ThemeResolver默认使用的是FixedThemeResolver,ThemeSource默认使用的是WebApplicationContext(其实是AbstractRefreshableWebApplicationContext里的ThemeSource),不过我们也可以自己来配置,例如: ResourceBundleThemeSource” p:basenamePrefix=”com.excelib.themes.”/> CookieThemeResolver” p:defaultThemeName=”default”/> 这里不仅配置了themeResolver和themeSource,而且还配置了默认主题名为“default”,以及配置文件的位置在com.excelib.themes包下(配置时最后要有点“.”)。 SpringMVC中主题的切换和Locale的切换使用相同的模式,也是使用Interceptor。配置如下: p:paramName="theme"/> 可以通过paramName设置修改主题的参数名,默认使用“theme”。下面的请求可以切换为summer主题: http://localhost:8080?theme=summer 8.MultipartResolver MultipartResolver用于处理上传请求,处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取到File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map,这样就使得上传请求的处理变得非常简单。当然,这里做的其实是锦上添花的事情,如果上传的请求不用MultipartResolver封装成MultipartHttpServletRequest,直接用原来的request也是可以的,所以在SpringMVC中此组件没有提供默认值。MultipartResolver定义如下: public interface MultipartResolver { boolean isMultipart(HttpServletRequest var1); MultipartHttpServletRequest resolveMultipart(HttpServletRequest var1) throws MultipartException; void cleanupMultipart(MultipartHttpServletRequest var1); } 这里一共三个方法,作用分别是判断是不是上传请求、将request包装成MultipartHttp- ServletRequest、处理完后清理上传过程中产生的临时资源。 9.FlashMapManager FlashMap相关的内容在前面已经介绍过了,主要用在redirect中传递参数。而FlashMapManager是用来管理FlashMap的,定义如下: public interface FlashMapManager { FlashMap retrieveAndUpdate(HttpServletRequest var1, HttpServletResponse var2); void saveOutputFlashMap(FlashMap var1, HttpServletRequest var2, HttpServletResponse var3); } retrieveAndUpdate方法用于恢复参数,并将恢复过的和超时的参数从保存介质中删除; saveOutputFlashMap用于将参数保存起来。 默认实现是org.springframework.web.servlet.support.SessionFlashMapManager,它是将参数保存到session中。 整个redirect的参数通过FlashMap传递的过程分三步: -Servlet的时候已经介绍过了,可以直接使用request.getAttribute(DispatcherServlet. OUTPUT_FLASH_MAP_ATTRIBUTE)拿到outputFlashMap,然后将参数put进去,也可以将需要传递的参数设置到处理器的RedirectAttributes类型的参数中,当处理器处理完请求时,如果是redirect类型的返回值RequestMappingHandlerAdapter会将其设置到outputFlashMap中。 -putFlashMap方法,将outputFlashMap中的参数设置到Session中。 -Update方法从session中获取inoutFlashMap并设置到Request的属性中备用,同时从session中删除。 SpringMVC的九大组件分别是HandlerMapping、HandlerAdapter、HandlerExceptionRe -solver、ViewResolver、RequestToViewNameTranslator、LocaleResolver、ThemeResolver、 MultipartResolver、FlashMapManager。