Springmvc学习及使用约定
第一部分 Springmvc学习
一.springmvc概述
1.简介
Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架。使用了前端控制器模式来进行设计,再根据请求映射规则分发给相应的页面控制器(动作/处理器)进行处理。
2.核心架构
Spring MVC整体架构请求流程如图:
具体执行步骤如下:
1、 首先用户发送请求到前端控制器,前端控制器根据请求信息(如URL)来决定选择哪一个页面控制器进行处理并把请求委托给它,即以前的控制器的控制逻辑部分;
2、 页面控制器接收到请求后,进行功能处理,首先需要收集和绑定请求参数到一个对象,这个对象在Spring Web MVC中叫命令对象,并进行验证,然后将命令对象委托给业务对象进行处理;处理完毕后返回一个ModelAndView(模型数据和逻辑视图名);
3、 前端控制器收回控制权,然后根据返回的逻辑视图名,选择相应的视图进行渲染,并把模型数据传入以便视图渲染;
4、 前端控制器再次收回控制权,将响应返回给用户;至此整个结束。
Spring MVC主要的核心组件包括:
DispatcherServlet 前端控制器
HandlerMapping 处理器规则映射
HandlerAdapter 处理器适配器
Controller 处理器或页面控制器
HandlerExceptionResolver 处理器异常解析
ViewResolver 视图解析器
Spring Web MVC核心架构图如下:
核心架构的具体流程步骤如下:
1、 首先用户发送请求——>DispatcherServlet,前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制;
2、 DispatcherServlet——>HandlerMapping, HandlerMapping将会把请求映射为HandlerExecutionChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器)对象,通过这种策略模式,很容易添加新的映射策略;
3、 DispatcherServlet——>HandlerAdapter,HandlerAdapter将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器;
4、 HandlerAdapter——>处理器功能处理方法的调用,HandlerAdapter将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理;并返回一个ModelAndView对象(包含模型数据、逻辑视图名);
5、 ModelAndView的逻辑视图名——> ViewResolver, ViewResolver将把逻辑视图名解析为具体的View,通过这种策略模式,很容易更换其他视图技术;
6、 View——>渲染,View会根据传进来的Model模型数据进行渲染,此处的Model实际是一个Map数据结构,因此很容易支持其他视图技术;
7、返回控制权给DispatcherServlet,由DispatcherServlet返回响应给用户,到此一个流程结束。
此处我们只是讲了核心流程,没有考虑拦截器、本地解析、文件上传解析等
3.开发步骤
3.1配置前端控制器
准备好spring框架的jar及框架依赖包之后,在web.xml中添加如下配置: <servlet>
<servlet-name> dispatcher </servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name> dispatcher </servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
load-on-startup:表示启动容器时初始化该Servlet;
url-pattern:表示哪些请求交给Spring Web MVC处理, “/app/*” 是用来定义默认servlet映射的。也可以如“*.html”表示拦截所有以html为扩展名的请求。
自此请求已交给Spring Web MVC框架处理,因此我们需要配置Spring的配置文件,默认DispatcherServlet会加载WEB-INF/[DispatcherServlet的Servlet名字]-servlet.xml配置文件。本示例为WEB-INF/ dispatcher-servlet.xml。
3.2配置组件项
在dispatcher-servlet.xml文件中加入< mvc:annotation-driven/>标签可自动启用HandlerMapping、HandlerAdapter的默认相关实例,这样在添加了@Controller和@RequstMapping标注的类和方法将被自动扫描注册;也可在配置中另行配置为其它的实现类;
接着再配置ViewResolver,可根据需要支持不同的视图,如jsp, freemarker, volicity, pdf等.
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<!-- <property name="prefix" value="/"/> -->
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
InternalResourceViewResolver:用于支持Servlet、JSP视图解析;
viewClass:JstlView表示JSP模板页面需要使用JSTL标签库,classpath中必须包含jstl的相关jar包;
prefix和suffix:查找视图页面的前缀和后缀(前缀[逻辑视图名]后缀),比如传进来的逻辑视图名为hello,则该该jsp视图页面应该存放在“WEB-INF/pages/hello.jsp”;
3.3建立控制器
package com.fengbo.web.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
@Controller
public class HelloWorldController {
@RequestMapping("/helloworld")
public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) throws Exception {
//1、收集参数、验证参数
//2、绑定参数到命令对象
//3、将命令对象传入业务对象进行业务处理
//4、选择下一个页面
ModelAndView mv = new ModelAndView();
//添加模型数据 可以是任意的POJO对象
mv.addObject("message", "Hello World!");
//设置逻辑视图名,视图解析器会根据该名字解析到具体的视图页面
mv.setViewName("hello");
return mv;
}
}
ModelAndView:包含了视图要实现的模型数据和逻辑视图名;“mv.addObject("message", "Hello World!");
”表示添加模型数据,此处可以是任意POJO对象;“mv.setViewName("hello");”表示设置逻辑视图名为“hello”,视图解析器会将其解析为具体的视图,如前边的视图解析器InternalResourceViewResolver会将其解析为“WEB-INF/pages/hello.jsp”。
3.4建立视图页面
创建 /WEB-INF/jsp/hello.jsp视图页面:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Hello World</title>
</head>
<body>
${message}
</body>
</html>
${message}:表示显示由HelloWorldController处理器传过来的模型数据。
二.注解式控制器
2.1注解式控制器简介
控制器的建立可以通过实现Controller接口实现,也可以通过@Controller注解将某个类声明为Controller,本节主要讲解注解式控制器的使用。
注解式控制器通过@Controller 和 @RequestMapping注解定义我们的处理器类。需要通过处理器映射DefaultAnnotationHandlerMapping和处理器适配器AnnotationMethodHandlerAdapter来开启支持@Controller 和 @RequestMapping注解的处理器。Springmvc提供了一组强大的注解:
@Controller:用于标识是处理器类;
@RequestMapping:请求到处理器功能方法的映射规则;
@RequestParam:请求参数到处理器功能处理方法的方法参数上的绑定;
@ModelAttribute:请求参数到命令对象的绑定;
@SessionAttributes:用于声明session级别存储的属性,放置在处理器类上,通常列出模型属性(如@ModelAttribute)对应的名称,则这些属性会透明的保存到session中;
@InitBinder:自定义数据绑定注册支持,用于将请求参数转换到命令对象属性的对应类型;
@CookieValue:cookie数据到处理器功能处理方法的方法参数上的绑定;
@RequestHeader:请求头(header)数据到处理器功能处理方法的方法参数上的绑定;
@RequestBody:请求的body体的绑定(通过HttpMessageConverter进行类型转换);
@ResponseBody:处理器功能处理方法的返回值作为响应体(通过HttpMessageConverter进行类型转换);
@ResponseStatus:定义处理器功能处理方法/异常处理器返回的状态码和原因;
@ExceptionHandler:注解式声明异常处理器;
@PathVariable:请求URI中的模板变量部分到处理器功能处理方法的方法参数上的绑定,从而支持RESTful架构风格的URI;
2.2 请求映射规则
通过使用@ RequestMapping注解可以设置Controller的请求映射规则,可以在类上和方法上同时注解,可以通过对各类请求信息的限定设置各种复杂的映射规则,。
在类上使用@ RequestMapping注解一般是用于窄化功能处理方法的映射的。
如下示例:
package com.fengbo.web.controller;
@Controller
@RequestMapping(value="/user") //①处理器的通用映射前缀
public class HelloWorldController2 {
@RequestMapping(value = "/hello2") //②相对于①处的映射进行窄化
public ModelAndView helloWorld() {
//省略实现
}
}
①类上的@RequestMapping(value="/user") 表示处理器的通用请求前缀;
②处理器功能处理方法上的是对①处映射的窄化。
因此http://localhost:8080/dynamicmeasurment/hello2 无法映射到HelloWorldController2的 helloWorld功能处理方法;而http://localhost:8080/dynamicmeasurment/user/hello2是可以的。
窄化请求映射可以认为是方法级别的@RequestMapping继承类级别的@RequestMapping。
窄化请求映射还有其他方式,如在类级别指定URL,而方法级别指定请求方法类型或参数等等。
通过设置@RequestMapping的各种属性,可限定各种请求信息,先看看它的属性对应的各种http请求信息的类型:
http请求信息包含六部分信息:
①请求方法,如GET或POST,表示提交的方式;
②URL,请求的地址信息;
③协议及版本;
④请求头信息(包括Cookie信息);
⑤回车换行(CRLF);
⑥请求内容区(即请求的内容或数据),如表单提交时的参数数据、URL请求参数(?abc=123 ?后边的)等。
此处我们可以看到有①、②、④、⑥一般是可变的,因此我们可以这些信息进行请求到处理器的功能处理方法的映射,因此请求的映射分为如下几种:
URL路径映射:使用URL映射请求到处理器的功能处理方法;
请求方法映射限定:如限定功能处理方法只处理GET请求;
请求参数映射限定:如限定只处理包含“abc”请求参数的请求;
请求头映射限定:如限定只处理“Accept=application/json”的请求
2.2.1 URL路径映射限定
2.2.1.1 普通URL路径映射
@RequestMapping(value={"/test1", "/user/create"}):多个URL路径可以映射到同一个处理器的功能处理方法。
2.2.1.2 URI模板模式映射
@RequestMapping(value="/users/{userId}"):{×××}占位符, 请求的URL可以是 “/users/123456”或“/users/abcd”,通过@PathVariable可以提取URI模板模式中的{×××}中的×××变量。
@RequestMapping(value="/users/{userId}/create"):这样也是可以的,请求的URL可以是“/users/123/create”。
@RequestMapping(value="/users/{userId}/topics/{topicId}"):这样也是可以的,请求的URL可以是“/users/123/topics/123”。
2.2.1.3 Ant风格的URL路径映射
@RequestMapping(value="/users/**"):可以匹配“/users/abc/abc”,但“/users/123”将会被URI模板模式映射中的“/users/{userId}”模式优先映射到。
@RequestMapping(value="/product?"):可匹配“/product1”或“/producta”,但不匹配“/product”或“/productaa”;
@RequestMapping(value="/product*"):可匹配“/productabc”或“/product”,但不匹配“/productabc/abc”;
@RequestMapping(value="/product/*"):可匹配“/product/abc”,但不匹配“/productabc”;
@RequestMapping(value="/products/**/{productId}"):可匹配“/products/abc/abc/123”或“/products/123”,也就是Ant风格和URI模板变量风格可混用。
2.2.1.4正则表达式风格的URL路径映射
从Spring3.0开始支持正则表达式风格的URL路径映射,格式为{变量名:正则表达式},这样我们就可以通过@PathVariable提取模式中的{×××:正则表达式匹配的值}中的×××变量了。
@RequestMapping(value="/products/{categoryCode:\\d+}-{pageNumber:\\d+}"):可以匹配“/products/123-1”,但不能匹配“/products/abc-1”,这样可以设计更加严格的规则。
正则表达式风格的URL路径映射是一种特殊的URI模板模式映射:
URI模板模式映射是{userId},不能指定模板变量的数据类型,如是数字还是字符串;
正则表达式风格的URL路径映射,可以指定模板变量的数据类型,可以将规则写的相当复杂。
2.2.1.5组合使用是“或”的关系
@RequestMapping(value={"/test1", "/user/create"}) 组合使用是或的关系,即“/test1”或“/user/create”请求URL路径都可以映射到@RequestMapping指定的功能处理方法。
2.2.3 请求方法映射限定
展示表单一般为GET请求方法;提交表单一般为POST请求方法。URL路径映射方式对任意请求方法是全盘接受的,因此我们需要某种方式来告诉相应的功能处理方法只处理如GET请求方法的请求或POST请求方法的请求。
示例如下:
package com.fengbo.web.controller;
//省略import
@Controller
@RequestMapping("/customers/**") //①处理器的通用映射前缀
public class RequestMethodController {
//②类级别的@RequestMapping窄化
@RequestMapping(value="/create", method = RequestMethod.GET)
public String showForm() {
System.out.println("===============GET");
return "customer/create";
}
//③类级别的@RequestMapping窄化
@RequestMapping(value="/create", method = RequestMethod.POST)
public String submit() {
System.out.println("================POST");
return "redirect:/success";
}
}
①处理器的通用映射前缀(父路径):表示该处理器只处理匹配“/customers/**”的请求;
②对类级别的@RequestMapping进行窄化,表示showForm可处理匹配“/customers/**/create”且请求方法为“GET”的请求;
③对类级别的@RequestMapping进行窄化,表示submit可处理匹配“/customers/**/create”且请求方法为“POST”的请求。
@RequestMapping(value="/methodOr", method = {RequestMethod.POST, RequestMethod.GET}):即请求方法可以是 GET 或 POST。
提示:
1、一般浏览器只支持GET、POST请求方法。
2、除了GET、POST,还有HEAD、OPTIONS、PUT、DELETE、TRACE。
3、DispatcherServlet默认开启对 GET、POST、PUT、DELETE、HEAD的支持;
4、如果需要支持OPTIONS、TRACE,请添加DispatcherServlet在web.xml的初始化参数:dispatchOptionsRequest 和 dispatchTraceRequest 为true。
2.2.4 请求参数数据映射限定
2.2.4.1 请求数据中有指定参数名
package com.fengbo.web.controller;
//省略import
@Controller
@RequestMapping("/parameter1") //①处理器的通用映射前缀
public class RequestParameterController1 {
@RequestMapping(params="create", method=RequestMethod.GET)
public String showForm() {
System.out.println("===============showForm");
return "parameter/create";
}
//③进行类级别的@RequestMapping窄化
@RequestMapping(params="create", method=RequestMethod.POST)
public String submit() {
System.out.println("================submit");
return "redirect:/success";
}
}
②@RequestMapping(params="create", method=RequestMethod.GET) :表示请求中有“create”的参数名且请求方法为“GET”即可匹配,如可匹配的请求URL“http://×××/parameter1?create”;
③@RequestMapping(params="create", method=RequestMethod.POST):表示请求中有“create”的参数名且请求方法为“POST”即可匹配;
此处的create请求参数名表示你请求的动作,即你想要的功能的一个标识,常见的CRUD(增删改查)我们可以使用如下请求参数名来表达:
◇(create请求参数名 且 GET请求方法) 新增页面展示、(create请求参数名 且 POST请求方法)新增提交;
◇(update请求参数名 且 GET请求方法) 新增页面展示、(update请求参数名 且 POST请求方法)新增提交;
◇(delete请求参数名 且 GET请求方法) 新增页面展示、(delete请求参数名 且 POST请求方法)新增提交;
◇(query请求参数名 且 GET请求方法) 新增页面展示、(query请求参数名 且 POST请求方法) 新增提交;
◇(list请求参数名 且 GET请求方法) 列表页面展示;
◇(view请求参数名 且 GET请求方法) 查看单条记录页面展示。
2.2.4.2 请求数据中没有指定参数名
@RequestMapping(params="!create", method=RequestMethod.GET)//进行类级别的@RequestMapping窄化
@RequestMapping(params="!create", method=RequestMethod.GET):表示请求中没有“create”参数名且请求方法为“GET”即可匹配,如可匹配的请求URL“http://×××/parameter1?abc”。
2.2.4.3 请求数据中指定参数名=值
package com.fengbo.web.controller;
//省略import
@Controller
@RequestMapping("/parameter2") //①处理器的通用映射前缀
public class RequestParameterController2 {
@RequestMapping(params="submitFlag=create", method=RequestMethod.GET)
public String showForm() {
System.out.println("===============showForm");
return "parameter/create";
}
//③进行类级别的@RequestMapping窄化
@RequestMapping(params="submitFlag=create", method=RequestMethod.POST)
public String submit() {
System.out.println("===============submit");
return "redirect:/success";
}
}
②@RequestMapping(params="submitFlag=create", method=RequestMethod.GET):表示请求中有“submitFlag=create”请求参数且请求方法为“GET”即可匹配,如请求URL为http://×××/parameter2?submitFlag=create;
③@RequestMapping(params="submitFlag=create", method=RequestMethod.POST):表示请求中有“submitFlag=create”请求参数且请求方法为“POST”即可匹配。
2.2.4.4 请求数据中指定参数名!=值
//请求参数submitFlag 不等于 create
@RequestMapping(params="submitFlag!=create", method=RequestMethod.GET)
@RequestMapping(params="submitFlag!=create", method=RequestMethod.GET):表示请求中的参数“submitFlag!=create”且请求方法为“GET”即可匹配,如可匹配的请求URL“http://×××/parameter1?submitFlag=abc”。
2.2.4.5 组合使用是“且”的关系
@RequestMapping(params={"test1", "test2=create"}) //②进行类级别的@RequestMapping窄化
@RequestMapping(params={"test1", "test2=create"}):表示请求中的有“test1”参数名 且 有“test2=create”参数即可匹配,如可匹配的请求URL“http://×××/parameter3?test1&test2=create。
2.2.5 请求头数据映射限定
2.2.5.1 请求头数据中有指定参数名
@RequestMapping(value="/header/test1", headers = "Accept"):表示请求的URL必须为“/header/test1”且请求头中必须有Accept参数才能匹配。
@RequestMapping(value="/header/test1", headers = "abc"):表示请求的URL必须为“/header/test1”且请求头中必须有abc参数才能匹配。
2.2.5.2 请求头数据中没有指定参数名
@RequestMapping(value="/header/test2", headers = "!abc"):表示请求的URL必须为“/header/test2”且请求头中必须没有abc参数才能匹配。
2.2.5.3 请求头数据中指定参数名=值
@RequestMapping(value="/header/test3", headers="Content-Type=application/json"):表示请求的URL必须为“/header/test3”且请求头中必须有“Content-Type=application/json”参数即可匹配。
当你请求的URL为“/header/test3”但 如果请求头中没有或不是“Content-Type=application/json”参数(如“text/html”其他参数),将返回“HTTP Status 415”状态码【表示不支持的媒体类型(Media Type),也就是MIME类型】,即我们的功能处理方法只能处理application/json的媒体类型。
@RequestMapping(value="/header/test4", headers = "Accept=application/json"):表示请求的URL必须为“/header/test4” 且 请求头中必须有“Accept =application/json”参数即可匹配。
当你请求的URL为“/header/test4” 但 如果请求头中没有“Accept=application/json”参数(如“text/html”其他参数),将返回“HTTP Status 406”状态码【不可接受,服务器无法根据Accept头的媒体类型为客户端生成响应】,即客户只接受“application/json”媒体类型的数据,即我们的功能处理方法的响应只能返回“application/json”媒体类型的数据。
@RequestMapping(value="/header/test5", headers = "Accept=text/*") :表示请求的URL必须为“/header/test5” 且 请求头中必须有如“Accept=text/plain”参数即可匹配。
Accept=text/*:表示主类型为text,子类型任意,如“text/plain”、“text/html”等都可以匹配。
@RequestMapping(value="/header/test6", headers = "Accept=*/*") :表示请求的URL必须为“/header/test6” 且 请求头中必须有任意Accept参数即可匹配。
2.2.5.4 请求头数据中指定参数名!=值
@RequestMapping(value="/header/test7", headers = "Accept!=text/vnd.wap.wml"):表示请求的URL必须为“/header/test7” 且 请求头中必须有“Accept”参数但值不等于“text/vnd.wap.wml”即可匹配。
2.2.5.5 组合使用是“且”的关系
@RequestMapping(value="/header/test8",headers={"Accept!=text/vnd.wap.wml","abc=123"}):表示请求的URL必须为“/header/test8” 且 请求头中必须有“Accept”参数但值不等于“text/vnd.wap.wml”且 请求中必须有参数“abc=123”即可匹配。
2.2.6 生产者/消费者映射限定
Spring3.1开始支持消费者、生产者限定,而且必须使用如下HandlerMapping和HandlerAdapter才支持:
<!--Spring3.1开始的注解 HandlerMapping -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--Spring3.1开始的注解 HandlerAdapter -->
<bean
class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
2.2.6.1 功能处理方法是消费者
@RequestMapping(value = "/consumes", consumes = {"application/json"}):此处使用consumes来指定功能处理方法能消费的媒体类型,其通过请求头的“Content-Type”来判断。
此种方式相对使用@RequestMapping的“headers = "Content-Type=application/json"”更能表明你的目的。
2.2.6.2 功能处理方法是生产者
@RequestMapping(value = "/produces", produces = "application/json"):表示将功能处理方法将生产json格式的数据,此时根据请求头中的Accept进行匹配,如请求头“Accept:application/json”时即可匹配;
@RequestMapping(value = "/produces", produces = "application/xml"):表示将功能处理方法将生产xml格式的数据,此时根据请求头中的Accept进行匹配,如请求头“Accept:application/xml”时即可匹配。
此种方式相对使用@RequestMapping的“headers = "Accept=application/json"”更能表明你的目的。
2.2.6.3 窄化时是覆盖而非继承
如类级别的映射为 @RequestMapping(value="/narrow", produces="text/html"),方法级别的为@RequestMapping(produces="application/xml"),此时方法级别的映射将覆盖类级别的,因此请求头“Accept:application/xml”是成功的,而“text/html”将报406错误码,表示不支持的请求媒体类型。
只有生产者/消费者 模式 是 覆盖,其他的使用方法是继承,如headers、params等都是继承。
2.2.6.4 组合使用是“或”的关系
@RequestMapping(produces={"text/html", "application/json"}) :将匹配“Accept:text/html”或“Accept:application/json”。
2.3 方法返回值
Spring mvc处理方法支持如下的返回方式:ModelAndView, Model, ModelMap, Map,View, String, void。下面将对具体的一一进行说明:
ModelAndView:
@RequestMapping("/show1")
public ModelAndView show1(HttpServletRequest request,
HttpServletResponse response) throws Exception {
ModelAndView mav = new ModelAndView("/demo2/show");
mav.addObject("account", "account -1");
return mav;
}
通过ModelAndView构造方法可以指定返回的页面名称,也可以通过setViewName()方法跳转到指定的页面 , 使用addObject()设置需要返回的值,addObject()有几个不同参数的方法,可以默认和指定返回对象的名字。 调用addObject()方法将值设置到一个名为ModelMap的类属性,ModelMap是LinkedHashMap的子类。
Model 是一个接口, 其实现类为ExtendedModelMap,继承了ModelMap类。
返回Map时相当于request.setAttribute方法。
View 可以返回pdf excel等。
String 指定返回的视图页面名称,结合设置的返回地址路径加上页面名称后缀即可访问到。
注意:如果方法声明了注解@ResponseBody ,则会直接将返回值输出到页面。
void如果返回值为空,则响应的视图页面对应为访问地址。当返回值类型不为void而实际返回值为null时同样取视图为当前请求url对应的视图。
三.数据绑定
3.1 请求处理方法支持的参数类型
我们知道标注了 @RequestMapping 注解的 Controller 方法就成为了请求处理方法,Spring MVC 允许极其灵活的请求处理方法签名方式。对于方法入参来说,它允许多种类型的入参,并且可以按任意顺序定义请求处理方法的入参(在spring3.1版本之前,除了 Errors 和 BindingResult 必须紧跟在命令对象/表单参数后面以外),Spring MVC 会根据反射机制自动将对应的对象通过入参传递给请求处理方法。接下来先看一下功能处理方法支持的参数类型:
1.Java 基本数据类型和String:默认情况下将按名称匹配的方式绑定到 URL 参数上,可以通过 @RequestParam 注解改变默认的绑定规则。
2.request/response/session:既可以是 Servlet API 的也可以是 Portlet API 对应的对象,Spring 会将它们绑定到 Servlet 和 Portlet 容器的相应对象上。
3.org.springframework.web.context.request.WebRequest:内部包含了 request 对象
4.java.util.Locale:绑定到 request 对应的 Locale 对象上
5.java.io.InputStream/java.io.Reader:可以借此访问 request 的内容
6.java.io.OutputStream / java.io.Writer:可以借此操作 response 的内容
7.任何标注了 @RequestParam 注解的入参:被标注 @RequestParam 注解的入参将绑定到特定的 request 参数上。
8.java.util.Map / org.springframework.ui.ModelMap:它绑定 Spring MVC 框架中每个请求所创建的潜在的模型对象,它们可以被 Web 视图对象访问(如 JSP)
9.命令/表单对象(注:一般称绑定使用 HTTP GET 发送的 URL 参数的对象为命令对象,而称绑定使用 HTTP POST 发送的 URL 参数的对象为表单对象):它们的属性将以名称匹配的规则绑定到 URL 参数上,同时完成类型的转换。而类型转换的规则可以通过 @InitBinder 注解或通过 HandlerAdapter 的配置进行调整
10.org.springframework.validation.Errors / org.springframework.validation.BindingResult: 为属性列表中的命令/表单对象的校验结果,注意3.1以前版本时,检验结果参数必须紧跟在命令/表单对象的后面
11.org.springframework.web.bind.support.SessionStatus:可以通过该类型 status 对象显式结束表单的处理,这相当于触发 session 清除其中的通过 @SessionAttributes 定义的属性。
除第1种类型的参数外,Spring Web MVC框架会自动帮助我们把相应的参数与当前请求绑定。
InputStream/OutputStream 和 Reader/Writer两组不能同时使用,只能使用其中的一组。
对于HttpSession参数的访问不是线程安全的,如果需要线程安全,需要设置AnnotationMethodHandlerAdapter或RequestMappingHandlerAdapter的synchronizeOnSession属性为true,即可线程安全的访问session。
对于命令/表单类参数,Spring Web MVC能够自动将请求参数绑定到功能处理方法的命令/表单对象上。
对于ModelMap/Model/Map类参数,Spring Web MVC 提供Model、Map或ModelMap让我们能去暴露渲染视图需要的模型数据。不管参数类型是三种类型中的哪一种,它们都是同一个实例,此外,功能处理方法的返回值中的模型数据(如ModelAndView)会合并功能处理方法形式参数中的模型数据(如Model),但如果两者之间有同名的,返回值中的模型数据会覆盖形式参数中的模型数据。
对于Errors/BindingResult类参数,Spring3.1之前(使用AnnotationMethodHandlerAdapter)错误对象必须紧跟在命令对象/表单对象之后。
3.2注解方式的数据绑定
Spring MVC提供了如下注解支持将请求参数绑定到相应的入参:
@RequestParam绑定单个请求参数值;
@PathVariable绑定URI模板变量值;
@CookieValue绑定Cookie数据值
@RequestHeader绑定请求头数据;
@ModelAttribute绑定参数到命令对象;
@SessionAttributes绑定命令对象到session;
@RequestBody绑定请求的内容区数据并能进行自动类型转换等。
@RequestPart绑定“multipart/data”数据,除了能绑定@RequestParam能做到的请求参数外,还能绑定上传的文件等。
3.2.1 @RequestParam绑定单个请求参数值
@RequestParam用于将请求参数区数据映射到功能处理方法的参数上。
@RequestMapping("/requestparam1*")
public String requestparam1(@RequestParam("username") String username)请求中包含username参数(如/requestparam1?username=zhang),则自动传入。
接下来我们看一下@RequestParam注解主要有哪些参数:
value:参数名字,即入参的请求参数名字,如username表示请求的参数区中的名字为username的参数的值将传入;
required:是否必须,默认是true,表示请求中一定要有相应的参数,否则将报404错误码;
defaultValue:默认值,表示如果请求中没有同名参数时的默认值,默认值可以是SpEL表达式,如“#{systemProperties['java.vm.version']}”。
public String requestparam4(@RequestParam(value="username",required=false) String username)
表示请求中可以没有名字为username的参数,如果没有默认为null,此处需要注意如下几点:
原子类型:必须有值,否则抛出异常,如果允许空值请使用包装类代替。
Boolean包装类型类型:默认Boolean.FALSE,其他引用类型默认为null。
public String requestparam5(
@RequestParam(value="username", required=true, defaultValue="zhang") String username)
表示如果请求中没有名字为username的参数,默认值为“zhang”。
如果请求中有多个同名的应该如何接收呢?如给用户授权时,可能授予多个权限,首先看下如下代码:
public String requestparam7(@RequestParam(value="role") String roleList)
如果请求参数类似于url?role=admin&rule=user,则实际roleList参数入参的数据为“admin,user”,即多个数据之间使用“,”分割;我们应该使用如下方式来接收多个请求参数:
public String requestparam7(@RequestParam(value="role") String[] roleList)
或
public String requestparam8(@RequestParam(value="list") List<String> list)
3.2.2 @PathVariable绑定URI模板变量值
@PathVariable用于将请求URL中的模板变量映射到功能处理方法的参数上。
@RequestMapping(value="/users/{userId}/topics/{topicId}")
public String test(
@PathVariable(value="userId") int userId,
@PathVariable(value="topicId") int topicId)
如请求的URL为“控制器URL/users/123/topics/456”,则自动将URL中模板变量{userId}和{topicId}绑定到通过@PathVariable注解的同名参数上,即入参后userId=123、topicId=456。
3.2.3 @CookieValue绑定Cookie数据值
@CookieValue用于将请求的Cookie数据映射到功能处理方法的参数上。
public String test(@CookieValue(value="JSESSIONID", defaultValue="") String sessionId)
如上配置将自动将JSESSIONID值入参到sessionId参数上,defaultValue表示Cookie中没有JSESSIONID时默认为空。
public String test2(@CookieValue(value="JSESSIONID", defaultValue="") Cookie sessionId)
传入参数类型也可以是javax.servlet.http.Cookie类型。
3.2.4 @RequestHeader绑定请求头数据
@RequestHeader用于将请求的头信息区数据映射到功能处理方法的参数上。
@RequestMapping(value="/header")
public String test(
@RequestHeader("User-Agent") String userAgent,
@RequestHeader(value="Accept") String[] accepts)
如上配置将自动将请求头“User-Agent”值入参到userAgent参数上,并将“Accept”请求头值入参到accepts参数上。
3.2.5 @ModelAttribute绑定请求参数到命令对象
@ModelAttribute一个具有如下三个作用:
①绑定请求参数到命令对象:放在功能处理方法的入参上时,用于将多个请求参数绑定到一个命令对象,从而简化绑定流程,而且自动暴露为模型数据用于视图页面展示时使用;
②暴露表单引用对象为模型数据:放在处理器的一般方法(非功能处理方法)上时,是为表单准备要展示的表单引用对象,如注册时需要选择的所在城市等,而且在执行功能处理方法(@RequestMapping注解的方法)之前,自动添加到模型对象中,用于视图页面展示时使用;
③暴露@RequestMapping方法返回值为模型数据:放在功能处理方法的返回值上时,是暴露功能处理方法的返回值为模型数据,用于视图页面展示时使用。
一、绑定请求参数到命令对象
如用户登录,我们需要捕获用户登录的请求参数(用户名、密码)并封装为用户对象,此时我们可以使用@ModelAttribute绑定多个请求参数到我们的命令对象。
public String test1(@ModelAttribute("user") UserModel user)
此处注解@ModelAttribute("user")的作用是将该绑定的命令对象以“user”为名称添加到模型对象中供视图页面展示使用。我们此时可以在视图页面使用${user.username}来获取绑定的命令对象的属性。
绑定请求参数到命令对象支持对象图导航式的绑定,如请求参数包含“?username=zhang&password=123&workInfo.city=bj”自动绑定到user中的workInfo属性的city属性中。
URI模板变量也能自动绑定到命令对象中,当你请求的URL中包含“bool=yes&schooInfo.specialty=computer&hobbyList[0]=program&hobbyList[1]=music&map[key1]=value1&map[key2]=value2&state=blocked”会自动绑定到命令对象上。
当URI模板变量和请求参数同名时,URI模板变量具有高优先权。
二、暴露表单引用对象为模型数据
@ModelAttribute("cityList")
public List<String> cityList() {
return Arrays.asList("北京", "山东");
}
如上代码会在执行功能处理方法之前执行,并将其自动添加到模型对象中,在功能处理方法中调用Model 入参的containsAttribute("cityList")将会返回true。
@ModelAttribute("user") //①
public UserModel getUser(@RequestParam(value="username", defaultValue="") String username) {
//TODO 去数据库根据用户名查找用户对象
UserModel user = new UserModel();
user.setRealname("zhang");
return user;
}
如你要修改用户资料时一般需要根据用户的编号/用户名查找用户来进行编辑,此时可以通过如上代码查找要编辑的用户。
也可以进行一些默认值的处理。
@RequestMapping(value="/model1") //②
public String test1(@ModelAttribute("user") UserModel user, Model model)
此处我们看到①和②有同名的命令对象,那Spring Web MVC内部如何处理的呢:
(1、首先执行@ModelAttribute注解的方法,准备视图展示时所需要的模型数据;@ModelAttribute注解方法形式参数规则和@RequestMapping规则一样,如可以有@RequestParam等;
(2、执行@RequestMapping注解方法,进行模型绑定时首先查找模型数据中是否含有同名对象,如果有直接使用,如果没有通过反射创建一个,因此②处的user将使用①处返回的命令对象。即②处的user等于①处的user。
三、暴露@RequestMapping方法返回值为模型数据
public @ModelAttribute("user2") UserModel test3(@ModelAttribute("user2") UserModel user)
大家可以看到返回值类型是命令对象类型,而且通过@ModelAttribute("user2")注解,此时会暴露返回值到模型数据(名字为user2)中供视图展示使用。那哪个视图应该展示呢?此时Spring Web MVC会根据RequestToViewNameTranslator进行逻辑视图名的翻译。
此时又有问题了,@RequestMapping注解方法的入参user暴露到模型数据中的名字也是user2,其实我们能猜到:
(3、@ModelAttribute注解的返回值会覆盖@RequestMapping注解方法中的@ModelAttribute注解的同名命令对象。
四、匿名绑定命令参数
public String test4(@ModelAttribute UserModel user, Model model)
此时我们没有为命令对象提供暴露到模型数据中的名字,此时的名字是什么呢?Spring Web MVC自动将简单类名(首字母小写)作为名字暴露,如“cn.javass.chapter6.model.UserModel”暴露的名字为“userModel”。
public @ModelAttribute List<String> test6()
或
public @ModelAttribute List<UserModel> test7()
对于集合类型(Collection接口的实现者们,包括数组),生成的模型对象属性名为“简单类名(首字母小写)”+“List”,如List<String>生成的模型对象属性名为“stringList”,List<UserModel>生成的模型对象属性名为“userModelList”。
其他情况一律都是使用简单类名(首字母小写)作为模型对象属性名,如Map<String, UserModel>类型的模型对象属性名为“map”。
3.2.6 @SessionAttributes绑定命令对象到session
有时候我们需要在多次请求之间保持数据,一般情况需要我们明确的调用HttpSession的API来存取会话数据,如多步骤提交的表单。Spring Web MVC提供了@SessionAttributes进行请求间透明的存取会话数据。
//1、在控制器类头上添加@SessionAttributes注解
@SessionAttributes(value = {"user"}) //①
public class SessionAttributeController{
//2、@ModelAttribute注解的方法进行表单引用对象的创建
@ModelAttribute("user") //②
public UserModel initUser()
//3、@RequestMapping注解方法的@ModelAttribute注解的参数进行命令对象的绑定
@RequestMapping("/session1") //③
public String session1(@ModelAttribute("user") UserModel user, ModelMap model)
//4、通过SessionStatus的setComplete()方法清除@SessionAttributes指定的会话数据
@RequestMapping("/session2") //③
public String session(@ModelAttribute("user") UserModel user, SessionStatus status) {
if(true) { //④
status.setComplete();
}
return "success";
}
@SessionAttributes(value = {"user"})含义:
@SessionAttributes(value = {"user"}) 标识将模型数据中的名字为“user” 的对象存储到会话中(默认HttpSession),此处value指定将模型数据中的哪些数据(名字进行匹配)存储到会话中,此外还有一个types属性表示模型数据中的哪些类型的对象存储到会话范围内,如果同时指定value和types属性则那些名字和类型都匹配的对象才能存储到会话范围内。
包含@SessionAttributes的执行流程如下所示:
① 首先根据@SessionAttributes注解信息查找会话内的对象放入到模型数据中;
② 执行@ModelAttribute注解的方法:如果模型数据中包含同名的数据,则不执行@ModelAttribute注解方法进行准备表单引用数据,而是使用①步骤中的会话数据;如果模型数据中不包含同名的数据,执行@ModelAttribute注解的方法并将返回值添加到模型数据中;
③ 执行@RequestMapping方法,绑定@ModelAttribute注解的参数:查找模型数据中是否有@ModelAttribute注解的同名对象,如果有直接使用,否则通过反射创建一个;并将请求参数绑定到该命令对象;
此处需要注意:如果使用@SessionAttributes注解控制器类之后,③步骤一定是从模型对象中取得同名的命令对象,如果模型数据中不存在将抛出HttpSessionRequiredException Expected session attribute ‘user’(Spring3.1)
或HttpSessionRequiredException Session attribute ‘user’ required - not found in session(Spring3.0)异常。
④ 如果会话可以销毁了,如多步骤提交表单的最后一步,此时可以调用SessionStatus对象的setComplete()标识当前会话的@SessionAttributes指定的数据可以清理了,此时当@RequestMapping功能处理方法执行完毕会进行清理会话数据。
3.2.7 @Value绑定SpEL表示式
@Value用于将一个SpEL表达式结果映射到到功能处理方法的参数上。
public String test(@Value("#{systemProperties['java.vm.version']}") String jvmVersion)
四.数据转换
Spring支持两种转换框架,通过实现JAVA标准的PropertyEditor接口,可以实现自定义的转换器;PropertyEditor只能用于字符串和JAVA对象的转换,不适用于任意两个JAVA类型之间的转换;有鉴于此,Spring3.0在核心模型中添加了一个通用的类型转换模块,位于org.springframework.core.convert包中,ConversionService是Spring类型转换体系的核心接口,它内建了很多的转换器,可完成大多数Java类型的转换工作,除了包括将Spring对象转换为各种基础类型的对象外,还包括String、Number、Array、Collection、Map、Properties及Object之间的转换器。
4.1 PropertyEditor
可以在控制器中使用@InitBinder添加控制器范围的自定义编辑器,也可以通过WebBindingInitializer装配在全局范围使用的编辑器。
package com.fengbo.web;
import org.springframework.web.bind.annotation.InitBinder;
import com.fengbo.UserEditor;
@Controller
@RequestMapping("/user");
public class UserController {
@InitBiner ①
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(User.class, new UserEditor()); ②
}
}
Spring MVC使用WebDataBinder处理请求消息和处理方法入参的绑定工作。在②处通过其registerCustomEditor()方法为User注册一个自定义的编辑器,UserEditor是实现PropertyEditor接口的编辑器。如果希望在全局范围内使用UserEditor编辑器,则可实现WebBindingInitializer接口并在该实现类中注册UserEditor。
package com.fengbo.web;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.WebRequest;
import com.fengbo.User;
import com.fengbo.UserEditor;
public class MyBindingInitializer implements WebBindingInitializer {
public void initBinder(WebDataBinder binder, WebRequest request) {
binder.registerCustomEditor(User.class, new UserEditor()); ①
}
}
在initBinder()接口方法中注册UserEditor编辑器。接下来,还需要在Web上下文中通过AnnotationMethodHandlerAdapter装配MyBindingInitializer:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean class="com.fengbo.web.MyBindingInitializer"/>
</property>
</bean>
4.2 ConversionService
Spring在org.springframework.core.convert.converter包中定义了3种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到ConversionServiceFactoryBean中。这3种类型的转换器接口分别为:
Converter<S, T>
GenericConverter
ConverterFactory
Converter接口是Spring最简单的一个转换器接口,仅包括一个接口方法。接口定义如下:
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
T convert(S source)负责将S类型的对象转换为T类型的对象。如果希望将一种类型的对象转换为另一种类型及其子类的对象,举例来说,将String转换为Number及Number子类(Integer、Long、Double等)对象,就需要一系列的Converter,如StringToInteger、StringToLong及StringToDouble等。Spring提供了一个将相同系列多个“同质”Converter封闭在一起的ConverterFactory接口,定义如下:
package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
S为转换的源类型,R为目标类型的基类,而T为扩展于R基类的类型。如Spring的StringToNumberConverterFactory就实现了ConverterFactory接口,封装了String转换到各种数据类型的Converter。
Converter只是负责将一个类型的对象转换为另一个类型的对象,并没有考虑类型对象所在宿主类上下文的信息,因此并不能完成“复杂”类型转换的工作。GenericConverter接口会根据源类对象及目标类对象所在宿主类中的上下文信息进行类型转换工作,其接口定义如下:
package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
ConvertiblePair封装了源类型和目标类型,组成一个“对子”,而TypeDescriptor包含了需转换类型对象所在宿主类的信息,因此GenericConverter的convert()接口方法可以利用这些上下文信息进行类型转换的工作。
ConditionalGenericConverter扩展于GenericConverter接口,并添加了一个接口方法:
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType)
该接口方法根据源类型及目标类型所在宿主类上下文信息决定是否要进行了类型转换,只有matches()方法返回true时,才调用convert()方法完成类型转换。正是因为在转换之前有一个是否要进行转换的条件判断动作,因此该接口命名为ConditionalGenericConverter。
实现了自定义转换器之后,可以利用org.springframework.context.support.ConversionServiceFactoryBean在Spring的上下文中定义一个ConversionService。Spring将自动识别出上下文中的ConversionService,并在Bean属性配置及Spring MVC方法处理方法入参绑定等场合使用它进行了数据的转换。具体配置如下:
<mvc:annotation-driven conversion-service="conversionService"/> ①
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.fengbo.StringToUserConverter"/> ②
</list>
</property>
</bean>
在①处,我们使用mvc命名空间的<mvc:annotation-driven/>标签,该标签可简化Spring MVC的相关配置。它会自动注册一个默认的ConversionService实现,即FormattingConversionServiceFactoryBean,以满足大多数类型转换需求。由于我们要注册一个自定义的StringToUserConverter,因此需要显式定义一个ConversionService覆盖<mvc:annotation-driven/>中的默认实现,这是通过设置<mvc:annotation-driven/>的conversion-service属性来完成的。它的converters属性可接受Converter、ConverterFactory、GenericConverter等接口的实现类,并把这些转换器的转换逻辑统一封装到一个ConversionService实例对象中。
使用示例如下:
先定义一个负责将格式化的String转换为User对象的自定义转换器。
package com.fengbo;
import org.springframework.core.convert.converter.Converter;
public class StringToUserConverter implements Converter<String, User> {
public User convert(String source) {
User user = new User();
if(source != null) {
String[] items = source.split(":");
user.setUserName(items[0]);
user.setPassword(items[1]);
user.setRealName(items[2]);
}
return user;
}
在定义和配置好StringToUserConverter后,就可以在任何控制器的处理方法中使用这个转换器了,我们在UserController中添加一个使用到StringToUserConverter的handle()方法。
@RequestMapping(value="/handle")
public String handle(@RequestParam("user") User user, ModelMap modelMap) {
modelMap.put("user", user);
return "/user/showUser";
启动Web容器,发送一个如下URL的请求:
http://localhost:8080/fengbo/handle?user=tom:1234:tomson
user请求参数的"tom:1234:tomson"值将会被StringToUserConverter正式地转换并绑定到handle方法的User入参中。
}
对于同一个类型来说,如果既在ConversionService装配自定义的转换器,又通过WebBindingInitializer装配了自定义编辑器,同时还在控制器中通过@InitBinder装配了自定义编辑器,那么Spring MVC将按如下优先顺序查找对应类型的编辑器:
查询通过@InitBinder装配的自定义编辑器;
查询通过ConversionService装配的自定义转换器;
查询通过WebBindingInitializer装配的自定义编辑器。
五.格式化
Spring 为我们提供了注解驱动的属性对象格式化功能,前面提到的ConversionService默认实现类FormattingConversionServiceFactoryBean在内部会自动注册NumberFormatAnnotationFormatterFactory,JodaDateTimeFormatAnnotationFormatterFactory,因此在装配了FormattingConversionServiceFactoryBean后,就可以在Spring MVC入参绑定及模型数据输出时使用注解驱动的格式化功能了。
在实体类上相应的属性上加上格式化注解后,就不用再对相应的入参进行格式化。
注解驱动格式化实例如下:
在User中添加两个新属性,并标注上格式化注解
package com.fengbo;
import java.util.Date;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
public class User {
@DateTimeFormat(pattern="yyyy-MM-dd") ① //可将如"1980-09-01"的字符串转换到
private Date birthday; //Date类型的birthday属性中
@NumberFormat(pattern="#,###,##") ② //可将如"4,500,00"的字符串转换到long类型
private long salary; //的salary属性中
}
birthday属性标注了@DateTimeFormat,指定其格式为"yyyy-MM-dd",而salary属性标了@NumberFormat,并指定其格式为"#,###,##"。
@DateTimeFormat注解可对java.util.Date、java.util.Calender、java.long.Long及Joda时间类型的属性进行标。它支持以下几个互斥的属性,具体说明如下:
iso:类型为DateTimeFormat.ISO 常用可选值包括:
DateTimeFormat.ISO.DATE:格式为yyyy-MM-dd。
DateTimeFormat.ISO.DATE_TIME:格式为yyyy-MM-dd hh:mm:ss.SSSZ
DateTimeFormat.ISO.TIME:格式为hh:mm:ss.SSSZ
DateTimeFormat.ISO.NONE:表示不应使用ISO格式时间。
pattern:类型为String,使用自定义的时间格式化串,"yyyy/mm/dd h:mm:ss"。
style:类型为String,通过样式指定日期时间的格式,由两位字符组成,第1位表示日期的样式,第2位表示时间的格式,以下为常用可选值:
S:短日期/时间的样式
M:中日期/时间的样式
L:长日期/时间的样式
F:完整日期/时间的样式
-:忽略日期或时间的样式
而@NumberFormat可对类似数字类型的属性进行标注,它拥有两个互斥的属性,具体如下:
pattern:类型为String,使用自定义的数字格式化串,如“##,###.##”。
style:类型为NumberFormat.Style,以下为常用可选值:
NumberFormat.Style.CURRENCY:货币类型
NumberFormat.Style.NUMBER:正常数字类型
NumberFormat.Style.PERCENT:百分数类型
六.校验
Spring MVC支持JSR303验证规范,也有自己的独立验证框架Commons Validator;JSR303通过使用注解的方式完成自动验证,Commons Validator验证支持服务端和客户端的双重注解,通过配置文件设置验证规则,再针对每个业务实体实现Validator接口,编写独立的验证器。考虑到服务端编写验证的逻辑相对多一些,及能够对前端js的控制程度更灵活,在服务端我们选择用JSR303规范完成验证,客户端采用单独的js验证框架,如jsvalidation或jquery-validation。
Hibernate Validator是JSR303的一个参考实现。所以需要在项目中加入hibernate-validator的依赖。<mvc:annotation-driven/>会自动注册好一个默认的LocalValidatorFactoryBean,它会自动查询类路径下的hibernate-validator实现。
在JSR 303及hibernate的实现中,定义了如下验证注解:
@AssertFalse | Boolean,boolean | 验证注解的元素值是false |
@AssertTrue | Boolean,boolean | 验证注解的元素值是true |
@NotNull | 任意类型 | 验证注解的元素值不是null |
@Null | 任意类型 | 验证注解的元素值是null |
@Min(value=值) | BigDecimal,BigInteger, byte, short, int, long,等任何Number或CharSequence(存储的是数字)子类型 | 验证注解的元素值大于等于@Min指定的value值 |
@Max(value=值) | 和@Min要求一样 | 验证注解的元素值小于等于@Max指定的value值 |
@DecimalMin(value=值) | 和@Min要求一样 | 验证注解的元素值大于等于@ DecimalMin指定的value值 |
@DecimalMax(value=值) | 和@Min要求一样 | 验证注解的元素值小于等于@ DecimalMax指定的value值 |
@Digits(integer=整数位数, fraction=小数位数) | 和@Min要求一样 | 验证注解的元素值的整数位数和小数位数上限 |
@Size(min=下限, max=上限) | 字符串、Collection、Map、数组等 | 验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小 |
@Past | java.util.Date, java.util.Calendar; Joda Time类库的日期类型 | 验证注解的元素值(日期类型)比当前时间早 |
@Future | 与@Past要求一样 | 验证注解的元素值(日期类型)比当前时间晚 |
@NotBlank | CharSequence子类型 | 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的首位空格 |
@Length(min=下限, max=上限) | CharSequence子类型 | 验证注解的元素值长度在min和max区间内 |
@NotEmpty | CharSequence子类型、Collection、Map、数组 | 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) |
@Range(min=最小值, max=最大值) | BigDecimal,BigInteger,CharSequence, byte, short, int, long等原子类型和包装类型 | 验证注解的元素值在最小值和最大值之间 |
@Email(regexp=正则表达式, flag=标志的模式) | CharSequence子类型(如String) | 验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式 |
@Pattern(regexp=正则表达式, flag=标志的模式) | String,任何CharSequence的子类型 | 验证注解的元素值与指定的正则表达式匹配 |
@Valid | 任何非原子类型 | 指定递归验证关联的对象; 如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加@Valid注解即可级联验证 |
此处只列出Hibernate Validator提供的大部分验证约束注解,请参考hibernate validator官方文档了解其他验证约束注解和进行自定义的验证约束注解定义。
在项目中放入hibernate-validator依赖后,使用注解为模型信息指定验证信息:
package com.fengbo;
import javax.validation.constraints.*;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
public class User {
private String userId;
@Pattern(regexp="w{4,30}")
private String userName;
@Pattern(regexp="S{6,30}")
private String password;
@Length(min=2, max=100)
private String realName;
@Past
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birthday;
@DecimalMin(value="1000.00")
@DecimalMax(value="100000.00")
@NumberFormat(pattern="#,###.##")
private long salary;
@NotNull(message="{temp.not.empty}")
private String temp;
}
注:当表单中需要一些非持久化属性时,可在实体类中增加一个前端处理的非持久化属性。
通过在命令对象上注解@Valid来告诉Spring MVC此命令对象在绑定完毕后需要进行JSR-303验证,如果验证失败会将错误信息添加到errors错误对象中。
package com.fengbo.web;
import javax.validation.Valid;
import org.springframework.validation.BindingResult;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value="/handle")
public String handle(@Valid @ModelAttribute("user")User user,
BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "/user/register";
}
else {
return "/user/showUser";
}
}
}
3.1版本之前,Spring MVC是通过对处理方法签名的规约来保存检验结果的:前一个表单/命令对象的校验结果保存在其后的入参中,这个保存校验结果的入参必须是BindingResult或Errors类型,概括来说,需校验的表单/命令对象和其绑定结果对象或错误是成对出现的,它们之间不允许声明其他的入参。
在处理方法内部可以通过BindingResult或Errors入参对象获取错误信息,首先通过bindingResult.hasErrors()方法判断入参对象是否存在校验错误,然后可通过接口方法获取更多信息,如下是几个常用的方法:
FieldError getFieldError(String field):根据属性名获取对应的校验错误。
List<FieldError> getFieldErrors():获取所有的属性校验错误。
Object getFieldValue(String field):获取属性值。
int getErrorCount():获取错误数量。
BindingResult接口扩展了Errors接口。
除了使用注解较验外,也可以手工校验,有时候还需要进行一些更复杂的业务规则校验:
package com.fengbo.web;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.BindingResult;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping(value="/handle")
public String handle(@ModelAttribute("user")User user,
BindingResult bindingResult) {
ValidationUtils.rejectIfEmptyOrWhitespaces(bindingResult, "userName", "required");
if("aaaa".equalsIgnoreCase(user.getUserName))) {
bindingResult.rejectValue("userName", "reserved");
}
if(bindingResult.hasErrors()) {
return "/user/register";
}
else {
return "/user/showUser";
}
}
}
在页面中显示错误
Spring MVC除了会将表单/命令对象的校验结果保存在对应的BindingResult或Error对象中外,还会将所有校验结果保存到“隐含模型”中。也就是说,即使处理方法的签名中没有BindingResult和Errors,它们始终可以从“隐含模型”对象中获取。并通过HttpServletRequest暴露给视图对象。
Spring MVC通过<form:errors path="*"/>标签在页面顶部显示所有的校验错误信息,而通过<form:errors path="propName"/>可显示某个属性的校验错误信息。
实际开发中,我们希望本地化的显示更具可读性的错误信息。Spring MVC支持对错误信息进行本地化显示。
当使用Spring MVC标签显示错误信息时,Spring MVC会查看Web上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息。
错误对象的错误码与国际化消息的键名是一致的,可以在国际化资源文件中为错误代码定义相应在本地化消息。
第二部分 项目规范
1.结构
div+css 每个区块标注注释 title等命名规范,浏览器页面缓存项设置
2.模板 decorate模式
3.常用页面jquery组件
表格,分页,日期等
4.提示消息传递及显示
5.请求参数js检验及js常用校验方法,js规范