SpringMVC(四)自定义参数转换规则
SpringMVC(四)自定义参数转换规则
处理器获取参数逻辑
当一个请求到来时,在处理器执行的过程中,它首先会从HTTP请求和上下文环境来得到参数,如果是简易的参数它会以简单的转换器进行转换,而这些简单的转换器是SpringMVC自身已经提供了的。但是如果转换HTTP请求体(Body),它就会调用HttpMessageConverter
接口的方法对请求体的信息进行转换,首先它会判断能否对请求体进行转换,如果可以就会将其转换为Java类型。
HttpMessageConverter接口源码
package org.springframework.http.converter;
public interface HttpMessageConverter<T> {
//是否可读,其中clazz为Java类型,mediaType为http请求类型
boolean canRead(Class<?> var1, @Nullable MediaType var2);
//判断clazz类型是否能够转换为mediaType媒体类型
boolean canWrite(Class<?> var1, @Nullable MediaType var2);
//可支持的媒体类型列表
List<MediaType> getSupportedMediaTypes();
//当canRead()验证通过后,读入http请求信息
T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException;
//当canWrite()方法验证通过后,写入响应
void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException;
}
上面的HttpMessageConverter
接口只是将HTTP请求体转换为对应的Java对象,而对于HTTP参数和其他内容,还没有讨论。例如,以性别参数来说,前端可能传递给控制器的是一个整数,而控制器参数却是一个枚举,这样就需要提供自定义的参数转换规则。
在SpringMVC中,是通过WebDataBinder
机制来获取参数的,它的主要作用是解析http请求的上下文,然后再控制器的调用之前转换参数并且提供验证的功能,为调用控制器的方法做准备。处理器会从HTTP请求中读取数据,然后通过三种接口来进行各类参数转换,者三种接口是Converter
,Fomatter
,GenericConverter
。在SpringMVC的机制中这三种接口的实现类都采用了***的机制,默认的情况下SpringMVC已经在***内注册了许多的转换器,这样就可以实现大部分的数据类型的转换,所以在大部分的情况下下无需开发者再提供转换器。当下需要自定义转换规则时,只需要在***上注册自己的转换器就可以了。
实际上,WebDataBinder
机制还有一个重要的功能,那就是验证转换结果。
可以看到控制器的参数是处理器通过Converter
、Formatter
和GenericConverter
这三个接口转换出来的。
- Converter:普通的转换器,例如有一个Integer类型的控制器参数,而从HTTP对应的为字符串,对应的Convert就会将字符串转换为Integer类型。
- Formatter:格式化转换器,类似日期字符串就是通过它按照约定的格式转换为日期。
- GenericConverter:将HTTP参数转换为数组。
转换器注册
对于数据类型转换,SpringMVC提供了一个服务机制去管理,它就是ConversionService
接口。在默认情况下下,会使用这个接口的子类DefaultFormattingConversionService
对象来管理这些转换器类。
可以看出,Converter、Formatter和GenericConverter可以通过***接口进行注册,这样处理器就可以获取对应的转换器来实现参数的转换。
上面讨论的是普通的SpringMVC的参数转换规则,而在spring boot中还提供了特殊的机制来管理这些转换器。Spring Boot的自动配置类WebMvcAutoConfiguration
还定义了一个内部类WebMvcAutoConfigurationAdapter
,代码如下
WebMvcAutoConfigurationAdapter源码
public void addFormatters(FormatterRegistry registry) {
//在IoC容器中获取Converter类型的Bean,然后获得迭代器
Iterator var2 = this.getBeansOfType(Converter.class).iterator();
//遍历迭代器,然后注册到服务类中
while(var2.hasNext()) {
Converter<?, ?> converter = (Converter)var2.next();
registry.addConverter(converter);
}
//在IoC容器中获取GenericConverter类型的Bean,然后获得迭代器
var2 = this.getBeansOfType(GenericConverter.class).iterator();
//遍历迭代器,然后注册到服务类中
while(var2.hasNext()) {
GenericConverter converter = (GenericConverter)var2.next();
registry.addConverter(converter);
}
//在IoC容器中获取Formatter类型的Bean,然后获得迭代器
var2 = this.getBeansOfType(Formatter.class).iterator();
//遍历迭代器,然后注册到服务类中
while(var2.hasNext()) {
Formatter<?> formatter = (Formatter)var2.next();
registry.addFormatter(formatter);
}
}
可以看到,在spring boot的初始化中,会将对应用户自定义的Convert
、Formatter
和GenericConverter
的实现类所传就的spring bean自动地注册到DefaultFormattingConversionService
对象中。这样对于开发者,只需要自定义Convert
、Formatter
和GenericConverter
的接口Bean,spring boot就通过这个方法将它们注册到ConversionService
对象中。其中,格式化Formatter
接口在实际开发中使用率较低。
一对一转换器(Converter)
Converter是一对一转换器,也就是从一种类型转换为另外一种类型,其接口定义十分简单。如下
Converter接口源码
package org.springframework.core.convert.converter;
import org.springframework.lang.Nullable;
@FunctionalInterface
public interface Converter<S, T> {
//转换方法,S代表原类型,T代表目标类型
@Nullable
T convert(S var1);
}
这个接口类型有原类型(S)和目标类型(T)两种,它们通过convert方法进行转换。
例如,http的类型为字符串(String)型,而控制器参数为Long型,那么就可以通过Spring内部提供的StringToNumber进行转换。
示例:假设前端要传递一个用户信息,这个用户信息的格式是{id}-{personName}-{note}
,而控制器的参数是Person对象。这里需要一个从String转换为Person的转换器。
package com.lay.mvc.converter;
import com.lay.mvc.entity.Person;
import org.springframework.core.convert.converter.Converter;
/**
* @Description:自定义字符串用户转换器
* @Author: lay
* @Date: Created in 16:26 2018/11/13
* @Modified By:IntelliJ IDEA
*/
@Component
public class StringToPersonConverter implements Converter<String, Person> {
//转换方法
@Override
public Person convert(String s) {
Person person=new Person();
String[] strArr=s.split("-");
Long id=Long.parseLong(strArr[0]);
String personName=strArr[1];
String note=strArr[2];
person.setId(id);
person.setPersonName(personName);
person.setNote(note);
return person;
}
}
这里类标注了注解@Component
,并且实现了Converter
接口,这样Spring就会将这个类扫描并且装配到IoC容器中。
控制器验证
/**
*
* @Description: 测试转换器
* @param: [person]
* @return: com.lay.mvc.entity.Person
* @auther: lay
* @date: 16:37 2018/11/13
*/
@GetMapping("/converter")
@ResponseBody
public Person getPersonByConverter(Person person){
return person;
}
测试http://localhost:8080/my/converter?person=1-xiaohuahua-beautiful
GenericConverter集合和数组转换
GenericConverter
是数组转换器。因为SpringMVC自身提供了一些数组转换器,需要自定义的并不多,所以这里只介绍SpringMVC自定义的数组转换器。
假设需要同时新增多个用户,这样便需要传递一个用户列表(List)给控制器。此时SpringMVC会使用StringToCellectionConverter
转换它,这个类实现了GenericConverter
接口,并且是SpringMVC内部已经注册的数据转换器。它首先会把字符串用逗号分隔称为一个个的子字符串,然后根据原类型为String
,目标类型泛型为Person
类,找到对应的Converter
进行转换,将字符串转换为Person对象。
/**
*
* @Description: 测试数组转换器
* @param: List
* @return: java.util.List<com.lay.mvc.entity.Person>
* @auther: lay
* @date: 16:47 2018/11/13
*/
@GetMapping("/converterList")
@ResponseBody
public List<Person> personList(List<Person> personList){
return personList;
}
这里参数使用了一个个逗号分隔,StringToCollectionConverter在处理时就通过逗号分隔,然后通过之前自定义的转换器StringToPerson将其变为用户对象,在组成一个列表List传递给控制器。