前面介绍了spring mvc的各个组件,spring mvc通过注解便可以让控制器得到丰富的参数类型,那么它是如何做到的呢,其实它是spring mvc的消息转换机制完成的。
处理器在http请求到达控制器之前能够对http的各类消息进行处理。首先当一个请求到达DispatcherServlet的时候,需要找到对应的HandlerMapping,然后根据HandlerMapping去找到对应的HandlerAdapter执行处理器。处理器在要调用的控制器之前,需要先获取http发送过来的信息,然后将其转变为控制器的各种不同类型的参数,这就是各类注解能够得到丰富类型参数的原因。它首先用http的消息转换器(HttpMessageConverter)对消息转换,但这是一个比较原始的转换,它是String类型和文件类型比较简易的转换,它还需要进一步转换才能转换为POJO或者其它丰富的参数类型。为了拥有这样的能力,Spring 4提供了转换器和格式化器,这样通过注解的信息和参数的类型,它就能够把http发送过来的各种消息转换成为控制器所需要的各类参数了。
当处理器完成了这些参数的转换,它就会进行验证(数据校验),下一步就是调用开发者提供的控制器了,将之前转换成功的参数传递进去,进而完成控制器的逻辑,返回结果后,处理器如果可以找到对应处理结果类型的HttpMessageConverter的实现类,它就会调用对应的HttpMessageConverter的实现方法对控制器返回的结果进行http转换,这一步不是必须的,可以转换的前提是能够找到对应的转换器。
接下来就是关于视图解析和视图解析器的流程了。有时候要自定义一些特殊的转换规则,比如和第三方合作,第三方可能提供的并不是一个良好的json格式,而是一些特殊的规则,这时候就需要使用自定义的消息转换规则,把消息转为对应的java类型,从而简化开发。
对于spring mvc,在xml配置了<mvc:annotation-driven>,或者java配置的注解上加入@EnableEebMvc的时候,spring IoC容器会自定义生成一个关于转换器和格式化器的类实例-----FormattingConversionServiceFactoryBean,这样就可以从spring IoC容器中获取这个对象了,它是一个工厂,通过它可以获得DefaultFormattingConversionService类对象,它实现了转换器与格式化器的注册机(即ConverterRegistry与FormatterRegistry),我们可以通过它注册转换器与格式化器,SpringMvc默认已经注册了一些常用的转换器与处理器。
一对一转换器(Converter)
Converter是一对一的转换器,先看看Converter的源码:
import org.springframework.lang.Nullable;
/**
* 转换接口
* @param <S>源类型
* @param <T>目标类型
*/
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
T convert(S source);
}
spring mvc所提供的功能,能够满足一般的要求,但是有时候我们需要自定义实现转换器,此时只需要实现Converter接口,然后注册给转换服务类(FormattingConversionServiceFactoryBean类,实际是注册到DefaultFormattingConversionService类中)。
jsp页面提交的privileges类型为String[]
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/control/role/save" method="post">
<label>角色名称:</label><input type="text" name="name" required="required"/><br/>
<input type="checkbox" name="privileges" value="departmentsave"/>部门添加
<input type="checkbox" name="privileges" value="departmentremove"/>部门删除
<input type="checkbox" name="privileges" value="departmentedit"/>部门编辑
input type="checkbox" name="privileges" value="departmentlist"/>部门查看
<br/>
<input type="checkbox" name="privileges" value="rolesave"/>角色添加
<input type="checkbox" name="privileges" value="roleremove"/>角色删除
<input type="checkbox" name="privileges" value="roleedit"/>角色编辑
<input type="checkbox" name="privileges" value="rolelist"/>角色查看
<br/>
<input type="submit"/>
</form>
</body>
</html>
相关 POJO类:
package edu.uestc.avatar.pojo;
import java.util.HashSet;
import java.util.Set;
public class Role {
private Integer id;
private String name;
//角色所拥有的权限
private Set<Privilege> privileges = new HashSet<Privilege>();
//***********************setter and getter **************//
}
package com.wise.tiger.pojo;
public class Privilege {
private Integer id;
private String name;
public Privilege() {}
public Privilege(String name) {
this.name = name;
}
//***********************setter and getter **************//
}
Controller:
package edu.uestc.avatar.web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.wise.tiger.pojo.Role;
@Controller
@RequestMapping("control/role")
public class RoleManageController {
@RequestMapping("/save")
public String saveRole(Role role) {
role.getPrivileges().forEach(pri->System.out.println(pri.getName()));
return "role_list";
}
@RequestMapping("/saveUI")
public String saveUI() {
return "role_add";
}
}
自定义一对一类型转换器
package edu.uestc.avatar.converter;
//*****************import****************//
/**
* @Description:将http请求中的字符串数组转为pojo属性的Set<Privilege>
* @author: <a href="mailto:1020zhaodan@163.com">Adan</a>
* @date: 2019年5月31日 上午11:29:25
* @version:1.0-snapshot
*/
public class StringArray2SetRoleConverter implements Converter<String[], Set<Privilege>>{
@Override
public Set<Privilege> convert(String[] source) {
if(source == null) return null;
var ret = new HashSet<Privilege>();
for(var privilegeName : source) {
ret.add(new Privilege(privilegeName));
}
return ret;
}
}
注册转换器:在xml配置了<mvc:annotation-driven>,或者java配置的注解上加入@EnableEebMvc的时候,spring IoC容器会自定义生成一个关于转换器和格式化器的类实例-----FormattingConversionServiceFactoryBean,通过它可以生成一个ConversionService接口,实际为DefaultFormattingConversionService对象。
- xml方式
<!--首先在mvc:annotation-driven元素上指定转换服务类,然后通过配置其属性加载对应的转换器 --> <mvc:annotation-driven conversion-service="conversionService"/> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.wise.tiger.web.converter.StringArray2SetRoleConverter"/> </set> </property> </bean>
- java配置
@Configuration
@ComponentScan(basePackages = "com.wise.tiger.web")
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringArray2SetRoleConverter());
}
}
数组和集合转换器GenericConverter
上面的转换器是一种一对一的转换,它只能从一种类型转换成另一种类型,不能进行一对多转换,为了克服这个问题,Spring core还加入了另外一个转换器GenericConverter。
使用格式化器
有些数据需要格式化,比如日期、金额等,为了支持这些场景,Spring Context提供了相关的Formatter。它需要实现一个接口Formatter,而Formatter又扩展了两个接口Printer和Parser
通过print方法能将结果按照一定的格式输出字符串,通过parse方法能够将满足一定格式的字符串转换为对象,它的内部实际是委托给Converter机制去实现的,需要自定义的场景不多(了解用法即可),这里介绍两个注解
- @DateTimeFormat:进行日期格式的转换
- @NumberFormat:进行数字的格式转换
@Controller
@RequestMapping("/convert")
public class ConvertController{
@RequestMapping("/format")
public String format(@DateTimeFormat(ios=ISO.DATE)Date date,
@NumberFormat(pattern="#,###.##")Double amount){
..........
}
}
参数可以是一个POJO,而不单单是一个日期或者数字,只是要给POJO加入对应注解:
public class FormatPojo{
@DateTimeFormat(iso = ISO.DATE)
private LocalDate date1;
@NumberFormat(pattern = "##,###.00")
private BigDecimal amount;
}
@RequestMapping("/format")
public String format(FormatPojo bean){
..........
}