22 迈向Spring MVC的旅程
【参考】Java Web开发历程。
1) Servlet独行天下的时代。
一个Servlet对应处理一个Web请求。Servlet什么都做。
2) 繁盛一时的JSP时代。
将Servlet中的视图渲染逻辑以独立的单元抽取出来,JSP成为Java Web应用程序事实上的模板化视图标准。
然而,JSP过于强大,可以完成Servlet的所有工作,完全替代了Servlet而一统天下了。
3) Servlet与JSP联盟的时代。
由Servlet来管理处理流程,由JSP来负责视图的渲染(本应如此)。
无Controller时代。
4) Web框架时代。
Spring MVC,请求驱动的Web框架。
【强制】MVC。
MVC(Model-View-Controller,模型-视图-控制器)。
1) 控制器:负责接收视图发送的请求并进行处理。它会根据请求条件通知模型进行应用程序状态的更新,之后选择合适的视图显示给用户。
2) 模型:通常封装了应用的逻辑以及数据状态。当控制器通知模型进行状态更新的时候,模型封装的相应逻辑将被调用。执行完成后,模型通常会通过事件机制通知视图状态更新完毕,从而视图可以显示最新的数据状态。
3) 视图:面向用户的接口。当用户通过视图发起某种请求的时候,视图将这些请求转发给控制器进行处理。处理流程经过控制器和模型后,最终视图将收到模型的状态更新通知,然后视图将结合模型数据,更新自身的显示。
23 Spring MVC初体验
23.1 鸟瞰Spring MVC
【推荐】Spring MVC框架的控制器的实现策略。
引入Front Controller和Page Controller的概念。Front Controller负责接收所有的Web请求,委派给下一级的Page Controller进行处理。
Front Controller概念落地:org.springframework.web.servlet.DispatcherServlet类。
Page Controller概念落地:org.springframework.web.servlet.mvc.Controller接口。
【强制】DispatcherServlet的处理流程。
1) 在Web请求到达DispatcherServlet之后,DispatcherServlet将寻求具体的HandlerMapping实例,以获取对应当前Web请求的具体处理类(Controller),然后调用Controller中的处理方法来处理当前Web请求。
Spring MVC引入org.springframework.web.servlet.HandlerMapping,来专门管理Web请求到具体的处理类之间的映射关系。
2) Controller中的处理方法执行完毕后,将返回一个org.springframework.web.servlet.ModelAndView实例。
ModelAndView包含如下两部分信息:
视图的逻辑名称。DispatcherServlet将根据该视图的逻辑名称,来决定为用户显示哪个视图。
模型数据。视图渲染过程中需要将这些模型数据并入视图的显示中。
3) DispatcherServlet根据ModelAndView返回的信息,在ViewResolver的帮助下查找具体的View实现类,然后委派该具体的View实现类来处理最终的视图渲染工作。
Spring MVC引入org.springframework.web.servlet.View接口,来统一地抽象视图的生成策略。
Spring MVC引入org.springframework.web.servlet.ViewResolver,来管理逻辑视图名称与具体的View实例之间的映射对应关系。
24 近距离接触Spring MVC主要角色
24.1 HandlerMapping
【推荐】HandlerMapping简介。
HandlerMapping帮助DispatcherServlet进行Web请求的URL到具体处理类的匹配。
org.springframework.web.servlet.HandlerMapping的定义如下:
public interface HandlerMapping {
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
HandlerExecutionChain是一个数据载体,包含两方面的数据,一个是用于处理Web请求的Handler,另一个则是一组随同Handler一起返回的HandlerInterceptor。
【参考】可用的HandlerMapping。
Spring MVC默认提供了多个HandlerMapping的实现供我们使用。
1) BeanNameUrlHandlerMapping。默认使用。
2) SimpleUrlHandlerMapping。
3) ControllerClassNameHandlerMapping。
4) DefaultAnnotationHandlerMapping。
【推荐】责任链模式。
在基于Spring MVC的Web应用程序中,我们可以为DispatcherServlet提供多个HandlerMapping供其使用。
DispatcherServlet在选用HandlerMapping的过程中,将根据我们所指定的一系列HandlerMapping的优先级进行排序,然后优先使用优先级在前的HandlerMapping。
如果当前的HandlerMapping能够返回可用的Handler,DispatcherServlet则使用当前返回的Handler进行Web请求的处理,而不再继续询问其他的HandlerMapping。否则,DispatcherServlet将继续按照各个HandlerMapping的优先级进行访问,直到获取一个可用的Handler为止。
24.2 Controller
【推荐】Controller定义。
org.springframework.web.servlet.mvc.Controller定义如下:
public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
【参考】可用的Controller。
直接实现Controller接口当然没有问题,但更多时候我们会寻求Spring MVC提供的更细粒度的Controller框架类。(笔者:现在都用注解开发了)
1) AbstractController。
该类通过模板方法模式解决了如下几个通用关注点:
管理当前Controller所支持的请求方法类型(GET/POST)。
管理页面的缓存设置,即是否允许浏览器缓存当前页面。
管理执行流程在会话(Session)上的同步。
2) MultiActionController。
继承自AbstractController。针对同一对象的CRUD操作,可以将Web请求交给MultiActionController来统一处理,而不用分别为每个Web请求单独实现一个处理类。
3) BaseCommandController。
继承自AbstractController。提供了自动数据绑定和通过Validator的数据校验功能。
4) AbstractFormController。
继承自BaseCommandController。在BaseCommandController的基础上,发展了一套模块化的表单(form)处理流程。
5) SimpleFormController。
继承自AbstractFormController。专门面向单一表单的处理。
SimpleFormController与生俱来的三种主要功能:
自动数据绑定:帮助我们自动提取HttpServletRequest中的相应参数,然后转型为需要的对象类型。我们唯一需要做的,就是为数据绑定提供一个目标对象,这个对象在Spring中称为Command对象。
通过Validator的数据校验。
表单处理:使用了模板方法模式。对GET请求和POST请求分别采用不同的处理方式。
6) AbstractWizardFormController。
继承自AbstractFormController。处理组成向导流程的所有页面所发起的Web请求。
7) AbstractCommandController。
继承自BaseCommandController。
24.3 ModelAndView
【推荐】ModelAndView简介。
通常,Controller在将Web请求处理完成后,会返回一个ModelAndView实例。
ModelAndView实例包含两部分内容:
1) 视图。
可以是逻辑视图名称(String),也可以是具体的View实例。
如果是逻辑视图名称,DispatcherServlet将寻求ViewResolver的帮助,根据逻辑视图名称获取一个可用的View实例。
2) 模型数据。
以org.springframework.ui.ModelMap的形式来保存模型数据,通过构造方法传入或者通过实例方法添加的模型数据都会添加到这个ModelMap中。
ModelMap中保存的模型数据将会在视图渲染阶段由具体的View实现类来获取并使用。
24.4 ViewResolver
【推荐】ViewResolver简介。
ViewResolver根据Controller所返回的ModelAndView中的逻辑视图名,为DispatcherServlet返回一个可用的View实例。
org.springframework.web.servlet.ViewResolver的定义如下:
public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}
【参考】可用的ViewResolver。
1) 面向单一视图类型的ViewResolver。
UrlBasedViewResolver及其子类。
通常只需要指定一个视图模板所在的位置,该类ViewResolver就会按照逻辑视图名,抓取相应的模板文件、构造对应的view实例并返回。
2) 面向多视图类型的ViewResolver。
我们需要通过某种配置方式明确指定逻辑视图名与具体视图之间的映射关系。
主要实现类有三个:ResourceBundleViewResolver、XmlViewResolver、BeanNameViewResolver。
24.5 View
【推荐】View定义。
org.springframework.web.servlet.View的定义如下:
public interface View {
String getContentType();
void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
【推荐】View实现原理。
总的来说,当前绝大多数的视图渲染技术都是构建在模板的原理上。
一个View实现类所要做的,就是使用相应的技术API将模板和最终提供的模型数据合并到一起,最终输出结果页面给客户端。
【参考】可用的View实现类。
1) 抽象类AbstractView。
定义了大多数View实现类都需要的一些属性和简单的模板化的实现流程。
2) 抽象类AbstractUrlBasedView。
AbstractView的一个主要的扩展类。
3) 使用JSP技术的View。
面向JSP技术的主要View实现类是:org.springframework.web.servlet.view.InternalResourceView。
还有:org.springframework.web.servlet.view.JstlView、org.springframework.web.servlet.view.TilesView、org.springframework.web.servlet.view.TilesJstlView。
4) 使用通用模板技术的View。
面向Velocity:VelocityView。
面向Freemarker:FreeMarkerView。
5) 面向二进制文档格式的View。
面向Excel:AbstractExcelView、AnstractJExcelView。
面向PDF:AbstractPdfView。
6) 面向JsperReport的View。
7) 使用XSLT技术的View。
8) 自定义View。
25 认识更多Spring MVC家族成员
25.1 文件上传与MultipartResolver
【参考】MultipartResolver定义。
public interface MultipartResolver {
boolean isMultipart(HtteServletRequest request);
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
void cleanupMultipart(MultipartHttpServletRequest request);
}
【参考】可用的MultipartResolver。
1) CommonsMultipartResolver。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" p:maxUploadSize="1000000">
</bean>
2) CosMultipartREsolver。
<bean id="multipartResolver" class="org.springframework.web.multipart.cos.CosMultipartREsolver" p:maxUploadSize="1000000">
</bean>
【参考】文件上传分析。
1) 当Web请求到达DispatcherServlet并等等处理的时候,DispatcherServlet首先会检查能否从WebApplicationContext中找到一个名为multipartResolver的MultipartResolver实例。(笔者:没有就GG)
2) DispatcherServlet通过multipartResolver的isMultipart(request)方法检查当前Web请求是否为multipart类型,如果是,调用multipartResolver的resolveMultipart(request)方法,返回一个MultipartHttpServletRequest对象(装饰器模式);如果不是,返回最初的HttpServletRequest。
3) 我们可以在某个Controller中,通过MultipartHttpServletRequest直接获取MultipartFile类所封装的上传后的文件:MultipartFile file = request.getFile("fileParameter");
25.2 Handler与HandlerAdaptor
【推荐】适配器模式。
在Spring MVC中,任何可以用于Web请求处理的处理对象统称为Handler,Controller是Handler的一种特殊类型。
为了能够以统一的方式调用各种类型的Handler,DispatcherServlet将不同Handler的调用职责转交给了一个称为HandlerAdaptor的角色。无论我们想在Spring MVC中使用什么类型的Handler,只要同时为DispatcherServlet提供对应该Handler的HandlerAdaptor实现就行,DispatcherServlet无须任何变动。
org.springframework.web.servlet.HandlerAdaptor的定义如下:
public interface HandlerAdaptor {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
【参考】可用的HandlerAdaptor。
基于注解。
25.3 框架内处理流程拦截与HandlerInterceptor
【推荐】HandlerInterceptor简介。
HandlerInterceptor可以在Handler的执行前后对处理流程进行拦截操作。
org.springframework.web.servlet.HandlerInterceptor的定义如下:
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;
}
【参考】可用的HandlerInterceptor。
1) org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor。
用户角色验证。
2) org.springframework.web.servlet.mvc.WebContentInterceptor。
检查请求方法类型是否在支持方法之列。
检查必要的Session实例。
检查缓存时间并通过设置相应HTTP头(Header)的方式控制缓存行为。
3) 自定义HandlerInterceptor。
【参考】HandlerInterceptor与Filter的对比。
Servlet标准组件Filter,也可以对Web请求的处理流程进行拦截并做相应的处理。
而Filter和HandlerInterceptor的本质区别在于,二者在Spring MVC应用的结构中所处的位置不同。
Filter序列在servlet层面对DispatcherServlet进行拦截,而HandlerInterceptor则位于DispatcherServlet内部,对Handler的执行进行拦截。
通常情况下,使用Filter对于Web应用程序中的一些普遍关注点进行统一处理是比较适合的,一旦需要细化处理流程的拦截逻辑,应该求助于更细粒度的HandlerInterceptor。
25.4 框架内的异常处理与HandlerExceptionResolver
【推荐】HandlerExceptionResolver简介。
如果Handler执行过程中没有任何异常,将以ModelAndView的形式返回后继流程要用的视图和模型数据信息,而一旦出现任何异常,HandlerExceptionResolver将接收异常情况的处理,处理完成后,将同样以ModelAndView的形式返回后继处理流程要使用的视图和模型数据信息。只不过,HandlerExceptionResolver所返回的ModelAndView中所包含的信息是错误信息页面和相关异常的信息。
org.springframework.web.servlet.HandlerExceptionResolver的定义如下:
public interface HandlerExceptionResolver {
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}
【参考】可用的HandlerExceptionResolver。
Spring MVC框架只提供了org.springframework.web.servlet.handler.SimpleMappingExceptionResolver。
25.5 其他成员
【参考】其他成员。
1) 国际化视图与LocalResolver。
2) 主题(Theme)与ThemeResolver。
26 Spring MVC中基于注解的Controller
Spring MVC在Spring 2.5发布中新添加了一种基于注解的Controller形式。借助于Spring 2.5一同发布的容器内<context:component-scan>功能支持,基于注解的Controller几乎可以达到XML零配置,进而极大地提高我们的开发效率。
26.1 基于注解的Controller
【强制】基于注解的Controller。
实际上,基于注解的Controller就是一个普通的POJO,只是使用某些注解附加了一些相关的元数据而已。
代码示例:
@Controller
@RequestMapping("/helloAnnoController.anno")
public class MyController {
@RequestMapping(method={ RequestMethod.GET, RequestMethod.POST })
public String processWebRequest() {
return "anno/helloAnnoController";
}
}
不需要继承某个基类或者实现某个接口,原则上只需要在WebApplicationContext中添加配置:
<context:component-scan base-package="mvc.controller">
对于/helloAnnoController.anno形式的请求,Spring MVC将使用MyController作为处理请求的Handler,并调用标注了@RequestMapping的processWebRequest方法进行当前Web请求的处理。
【参考】由哪个基于注解的Controller来处理Web请求?
我们需要为基于注解的Controller提供相应的HandlerMapping以处理Web请求到Handler的映射关系。
Spring 2.5提供了一个这样的HandlerMapping:org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping。它会先扫描应用程序的Classpath,通过反射获取所有标注了@Controller的对象,然后通过反射获取@RequestMapping的相应信息与请求信息进行匹配,并最终返回匹配后的结果。
在Spring MVC中,DefaultAnnotationHandlerMapping将在DispatcherServlet初始化的时候就被默认启用。
【参考】调用基于注解的Controller的哪个方法来处理Web请求?
我们需要提供相应的HandlerAdaptor以调用并执行自定义handler的处理逻辑。
Spring 2.5中为基于注解的Controller提供的HandlerAdaptor实现类是org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter。
默认情况下,2.5版本的DispatcherServlet将在初始化的时候就实例化一个AnnotationMethodHandlerAdapter。
26.2 近看基于注解的Controller
【推荐】request/response/session。
只要我们在方法定义中声明一个HttpServletRequest形式的方法参数,就可以在该处理方法中直接使用对应当前Web请求的request对象。对于response和session也是一样的。
@RequestMapping(..)
public void processMethod(HttpServletRequest request, ..){
...
}
其他类似的方法参数还有InputStream、Reader、OutputStream、Writer、ModelMap等。
【强制】请求参数到方法参数的绑定。
1) 默认绑定行为。
根据名称匹配原则进行的数据绑定。当请求中的参数名与方法参数名称一致的时候,相应的参数值将被绑定到对应的方法参数上。
参数类型可以是JavaBean对象引用,只要同样保证请求参数名称能够与指定的JavaBean对应的属性名称匹配,数据绑定就能正确完成。
2) 使用@RequestParam明确地指定绑定关系。
如果我们允许@RequestParam指定的请求参数不存在,可以将@RequestParam的required属性设为false。
3) 添加自定义数据绑定规则。
【推荐】使用@ModelAttribute访问模型数据。
如果将@ModelAttribute标注在某个方法上,该方法所返回的数据将被添加到模型数据中。
如果将@ModelAttribute标注到处理方法的方法参数上的话,从模型数据中获取值绑定到方法参数。
如@ModelAttribute("command"),"command"将作为键值 对模型数据进行"存取"。
【推荐】通过@SessionAttribute管理Session数据。
@SessionAttribute只应用在类型声明上。
如@SessionAttribute("command")标注在类型声明上,在此类中被添加到模型数据的"command"对象将被存入Session中。
Session中的数据也可以通过 将@ModelAttribute标注到处理方法的方法参数上 获取。
27 Spring MVC扩展篇
【参考】Convention Over Configuration。
惯例优于配置。它所倡导的理念是,如果系统中某些映射关系可以通过事先约定的规则确定,那么优先使用事先约定的规则来确定当前映射关系,这样可以避免将所有的映射关系通过配置的方式来表述,进而大大减少配置量,提高开发效率。