Spring MVC进阶应用

Spring MVC 进阶应用

参数绑定

客户端发送请求时传递的参数默认是键值对格式,Spring MVC通过参数绑定组件将请求的参数串进行类型转换。
Spring MVC使用Controller方法的形参接收请求传来的参数。

1. Spring MVC默认内置的形参类型

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • Model 一个接口,可以将数据填充到request域对象中。
  • ModelMap Model接口的实现。

这5个对象可以直接通过形参注入并使用。

2. 简单数据类型绑定

前端页面表单中的name或者URL中的key与Controller方法中的形参名称一致,即可完成绑定。
如果名称不一致,可以使用@RequestParam注解指定名称完成绑定。
例如:

1
2
3
4
5
URL: http://xxxxx?SID=1
Controller:
// 将SID的数据封装到形参id中。
public String update (@RequestParam("SID") Integer id,Model model)

RequestParam注解

  • value:参数名,即传入参数的名称。
  • required:默认为true,表示请求中要有相应的参数,否则报错:Status 400 - Required Integer parameter ‘XXXX’ is not present
  • defaultValue:默认值,没有同名参数时,则使用默认值。

3. JavaBean对象类型绑定

前端页面表单中的name或者URL中的key与JavaBean中的属性名进行匹配。
如果参数名称一致,则将参数绑定到JavaBean中的属性上。
如果JavaBean中包装了对象类型,传入参数的名称则需要按照 对象.属性 的格式编写,并且属性要与JavaBean中包装的对象中的属性名称一致。

4. 集合类型绑定

数组类型绑定
使用checkbox复选框时,会将参数绑定到一个数组中。
例如:

1
2
3
4
5
6
7
前端页面:
<input type="checkbox" name="cId" value="1">
<input type="checkbox" name="cId" value="2">
Controller:
// 使用一个数组接收复选框的参数
public String delete (Integer[] cId,Model model)

List集合类型绑定
Controller不能直接在形参中定义List,需要在包装类中定义List。
页面中的编写格式: list名[index].属性名
例如:

1
2
3
4
5
<c:forEach items="${list}" var="user" varStatus="status">
<tr>
<td><input type="text" name="list[${status.index}].name" value="${user.name}" /></td>
</tr>
</c:forEach>

Map集合类型绑定
Controller不能直接在形参中定义Map,需要在包装类中定义Map。
页面中的编写格式:map名[‘key’]
例如:

1
2
3
4
5
<c:forEach items="${list}" var="user" varStatus="status">
<tr>
<td><input type="text" name="map['name']" value="${user.name}" /></td>
</tr>
</c:forEach>

Converter

当请求参数中含有日期类型时,需要自定义一个类型转换器转换成我们需要的日期格式。
创建一个类实现Converter接口就可以自定义一个类型转换器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Converter<需要转换的数据的类型,转换的类型>
public class DateConverter implements Converter<String, Date> {
@Override
public Date convert(String source) {
try{
// 转换日期格式
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return format.parse(source);
}catch(Exception e){
e.printStackTrace();
}
// 出现异常,则返回null
return null;
}
}

之后需要在处理器适配器中注册Converter

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 注册注解方式的适配器与映射器 -->
<mvc:annotation-driven conversion-service="conversionService" />
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<list>
<!-- 日期类型转换器 -->
<bean class="com.sun.ssm.converter.DateConverter"></bean>
</list>
</property>
</bean>

Validation

Spring MVC本身没有实现表现层校验的部分,它本身支持JSR 303校验规范,而这个规范的官方参考实现是hibernate validator。

所以Spring MVC想要实现校验,需要导入Hibernate-validator jar包。

在配置文件中配置validator校验器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 配置校验器 -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<!-- 校验器提供者 -->
<property name="providerClass" value="org.hibernate.validator.HibernateValidator" />
<!-- 指定校验使用的资源文件,在文件中配置校验错误信息,
如果不指定,默认使用classpath下的ValidationMessages.properties
-->
<property name="validationMessageSource" ref="messageSource" />
</bean>
<!-- 校验错误信息的资源文件 -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<!-- 指定文件路径 -->
<property name="basenames">
<list>
<value>classpath:validationMessages</value>
</list>
</property>
<!-- 指定文件的编码 -->
<property name="fileEncodings" value="utf8"/>
<!-- 资源文件内容缓存的时间,单位秒 -->
<property name="cacheSeconds" value="120" />
</bean>

在处理器适配器中注册Validator

1
2
<!-- 注册注解方式的适配器与映射器 -->
<mvc:annotation-driven conversion-service="conversionService" validator="validator" />

在JavaBean中可以使用注解制定校验规则。

1
2
3
4
5
6
ValidationMessages.properties:
user.username.size.error=用户名长度格式为1-10之间
JavaBean:
@Size(min=1,max=10,message="{user.username.size.error}")
private String username;

Controller中在需要检验的形参前使用@Validated注解,并在形参中注入一个BindingResult对象接收校验错误信息。
@Validated注解和BindingResult顺序是固定的。(@Validated在前)

1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping("/updateUser")
public String updateUser(@Validated User user,BindingResult bindingResult,Model model) {
// 判断是否有校验错误信息,如果有则代表校验未通过
if(bindingResult.hasErrors()){
// 获得校验错误信息集合
List<ObjectError> allErrors = bindingResult.getAllErrors();
// 将校验错误信息集合存入request域中
model.addAttribute("errors", allErrors);
//检验未通过,重新跳转到修改页面
return "updateUser";
}
}

分组校验

因为校验规则是在JavaBean中定义的,所以当同一个JavaBean需要被多个Controller使用时,可能根据需求需要不同的校验。

在这种情况下,可以使用一个标识作用的接口进行分组。

定义一个空接口作为分组的标识:

1
2
3
public interface ValidationGroup {
}

在JavaBean中使用分组:

1
2
@Size(min=1,max=10,message="{user.username.size.error}",groups={ValidationGroup.class})
private String username;

在Controller中使用分组校验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RequestMapping("/updateUser")
public String updateUser(@Validated(value={ValidationGroup.class}) User user,
BindingResult bindingResult,Model model) {
// 判断是否有校验错误信息,如果有则代表校验未通过
if(bindingResult.hasErrors()){
// 获得校验错误信息集合
List<ObjectError> allErrors = bindingResult.getAllErrors();
// 将校验错误信息集合存入request域中
model.addAttribute("errors", allErrors);
//检验未通过,重新跳转到修改页面
return "updateUser";
}
}

主要的验证注解如下:

注解 适用的数据类型 说明
@AssertFalse Boolean, boolean 验证注解的元素值是false
@AssertTrue Boolean, boolean 验证注解的元素值是true
@DecimalMax(value=x) BigDecimal, BigInteger, String, byte,short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number andCharSequence. 验证注解的元素值小于等于@ DecimalMax指定的value值
@DecimalMin(value=x) BigDecimal, BigInteger, String, byte,short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number andCharSequence. 验证注解的元素值小于等于@ DecimalMin指定的value值
@Digits(integer=整数位数, fraction=小数位数) BigDecimal, BigInteger, String, byte,short, int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of Number andCharSequence. 验证注解的元素值的整数位数和小数位数上限
@Future java.util.Date, java.util.Calendar; Additionally supported by HV, if theJoda Time date/time API is on the class path: any implementations ofReadablePartial andReadableInstant. 验证注解的元素值(日期类型)比当前时间晚
@Max(value=x) BigDecimal, BigInteger, byte, short,int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type ofCharSequence (the numeric value represented by the character sequence is evaluated), any sub-type of Number. 验证注解的元素值小于等于@Max指定的value值
@Min(value=x) BigDecimal, BigInteger, byte, short,int, long and the respective wrappers of the primitive types. Additionally supported by HV: any sub-type of CharSequence (the numeric value represented by the char sequence is evaluated), any sub-type of Number. 验证注解的元素值大于等于@Min指定的value值
@NotNull Any type 验证注解的元素值不是null
@Null Any type 验证注解的元素值是null
@Past java.util.Date, java.util.Calendar; Additionally supported by HV, if theJoda Time date/time API is on the class path: any implementations ofReadablePartial andReadableInstant. 验证注解的元素值(日期类型)比当前时间早
@Pattern(regex=正则表达式, flag=) String. Additionally supported by HV: any sub-type of CharSequence. 验证注解的元素值与指定的正则表达式匹配
@Size(min=最小值, max=最大值) String, Collection, Map and arrays. Additionally supported by HV: any sub-type of CharSequence. 验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小
@Valid Any non-primitive type(引用类型) 验证关联的对象,如账户对象里有一个订单对象,指定验证订单对象
@NotEmpty CharSequence,Collection, Map and Arrays 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@Range(min=最小值, max=最大值) CharSequence, Collection, Map and Arrays,BigDecimal, BigInteger, CharSequence, byte, short, int, long and the respective wrappers of the primitive types 验证注解的元素值在最小值和最大值之间
@NotBlank CharSequence 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Length(min=下限, max=上限) CharSequence 验证注解的元素值长度在min和max区间内
@Email CharSequence 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

数据回显

Spring MVC默认支持数据回显。

Spring MVC会在Controller返回之前,自动将形参中的JavaBean放入Request域中,默认名称为类名首字母小写。
只要前端页面动态获取数据的key与JavaBean名称一致,即可完成数据回显。
如果名称不一致,也可以用@ModelAttribute注解指定形参放入Request域中的key名。

文件上传

Spring MVC使用Multipart解析器完成文件上传,而Multipart解析器需要依赖以下2个jar包:

  • commons-fileupload
  • commons-io

配置Multipart解析器

1
2
3
4
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 限制上传文件的最大长度 -->
<property name="maxUploadSize" value="10*1024*1024"/>
</bean>

需要在Controller形参中注入一个MultipartFile对象接收文件,该对象名字要与传入的文件名相同。
文件上传示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 判断是否上传了文件
if(file!= null){
//原始文件名称
String fileName = file.getOriginalFilename();
//判断文件名是否为空
if(fileName != null && !"".equals(fileName))
{
// 文件存放的路径
String path = "D:\\temps\\";
// 判断文件存放路径是否存在
File dirFile = new File(path);
// 文件夹不存在,自动创建一个文件夹
if(!dirFile.exists()) {
dirFile.mkdirs();
}
// 使用UUID拼接一个唯一文件名
String newFileName = UUID.randomUUID()+fileName.substring(fileName.lastIndexOf("."));
//新的文件
File newFile = new File(path+newFileName);
//把上传的文件保存成一个新的文件
file.transferTo(newFile);
}
}

Interceptor

Spring MVC的拦截器是针对处理器进行拦截的,当HandlerMapping查找处理器时,会被拦截器拦截。

可以通过实现HandlerIntercepter接口,自定义一个拦截器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MyHandlerInterceptor implements HandlerInterceptor{
// Handler执行前调用,返回值为true则放行,false则不放行。
// 应用场景:登录验证,权限授权
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,
Object handler) throws Exception {
return false;
}
// Handler开始执行,并且在返回ModelAndView之前调用
// 应用场景:操作ModelAndView对象
@Override
public void postHandle(HttpServletRequest request,HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
// Handler执行完后调用
// 应用场景:异常处理、日志
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}

配置拦截器

1
2
3
4
5
6
7
8
<!-- 配置全局拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- /**表示所有URL和子URL路径 -->
<mvc:mapping path="/**" />
<bean class="com.sun.interceptor.MyHandlerInterceptor" />
</mvc:interceptor>
</mvc:interceptors>

拦截器的执行顺序

  • preHandle()为正向顺序执行,postHandle()和afterCompletion()为逆向顺序执行。
  • 如果第一个拦截器返回值为false,那它之后所有的拦截器都不会执行。
  • 如果一个拦截器返回值为false,那么它的postHandle()和afterCompletion()都不会执行。
  • 只要有一个拦截器的返回值为false,则所有拦截器的postHandle()都不会执行。

RESTful

一种软件架构风格,设计风格而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

实现REST风格需要在DispatcherServlet中将url映射模式改为 /

1
2
3
4
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

但是当映射模式为/时会把静态资源一起拦截,需要配置才能访问静态资源。

1
2
3
<mvc:resources location="/js/" mapping="/js/**"/>
<mvc:resources location="/css/" mapping="/css/**"/>
<mvc:resources location="/jquery/" mapping="/jquery/**"/>

Springmvc会把mapping映射到ResourceHttpRequestHandler,这样静态资源在经过DispatcherServlet转发时就可以找到对应的Handler了。

在Controller中实现RESTful

1
2
@RequestMapping("/update/{id}")
public String update(@PathVariable Integer id,Model model)
  • @PathVariable:将URL中的模板变量映射到形参上,如果形参与模板变量名称不一致,可以使用value=” “设置为模板变量的名称完成映射。
  • {id}:模板变量。例如URL为 xxxx/update/1,模板变量update/{id}就可以将1封装到{id}中。

JSON

Spring MVC默认使用MappingJacksonHttpMessageConverter对json数据进行转换,所以需要导入Jackson的jar包。

Spring MVC可以使用2个注解完成JSON数据的交互操作。

  • @RequestBody:如果请求参数传入的是json数据,使用RequestBody可以将json转换为java对象。
  • @ResponseBody:将返回值的java对象转换为json输出。
分享