上篇文章写了SpringBoot 参数接收只看这一篇文章就够了,只是写了使用方法,没有写为什么,原理是什么,这篇文章也是之前的预先的计划,稍微花点时间整理下,
知其然知其所以然,才算是能彻底掌握,但是说实在话,都是工具,会用是硬道理。有精力了再去搞原理。
1、原理
百度上看到一张图,还蛮好的,直接拿来用了,侵权删。
Spring容器管理的RequestMappingHandlerAdapter对象会自动帮我们分解参数并组装成所需要的对象。
RequestMappingHandlerAdapter完全按照名称匹配且只能组装在request的参数域中提供参数的对象。
上图我花了两个红色框,一个HttpMessageConverter 和 HandlerMethodArgumentResolver
这两个也是今天的重点。
2、HandlerMethodArgumentResolver
2.1 基础概念
HandlerMethodArgumentResolver 方法参数解析器,是Spring Web(SpringMVC)组件中的众多解析器之一,主要用来对Controller中方法的参数进行处理。
2.2 内置resolver
参数 | Resolver |
HttpServletRequest | ServletRequestMethodArgumentResolver |
HttpServletResponse | ServletResponseMethodArgumentResolver |
@RequestParam | RequestParamMapMethodArgumentResolver |
@PathVariable | PathVariableMapMethodArgumentResolver |
@RequestHeader | RequestHeaderMapMethodArgumentResolver |
@RequestBody | RequestResponseBodyMethodProcessor |
@ModelAttribute | ModelAttributeMethodProcessor |
@RequestPart | RequestPartMethodArgumentResolver |
@CookieValue | ServletCookieValueMethodArgumentResolver |
HttpEntity/RequestEntity | HttpEntityMethodProcessor |
2.3 接口说明
public interface HandlerMethodArgumentResolver{/**
* 给定的方法参数parameter是否受此解析程序支持。
* @param parameter:要检查的方法参数
**/
booleansupportsParameter(MethodParameterparameter);
/**
* 将方法参数从给定请求解析为参数值。
* @param parameter: 请求参数
* @param mavContainer: 容器
* @param webRequest: 请求
* @param binderFactory: 用于创建一个WebDataBinder用于数据绑定、校验
**/
Object resolveArgument(MethodParameterparameter,@NullableModelAndViewContainermavContainer,
NativeWebRequestwebRequest,@NullableWebDataBinderFactorybinderFactory)throwsException;}
3、HttpMessageConverter
3.1 基础概念
负责将请求信息转换为一个对象(类型为 T)
3.2 内置Converter
MappingJackson2HttpMessageConverter 负责读、写JSON格式数据(利用Jackson)
AllEncompassingFormHttpMessageConverter 负责读、写Form表单数据
Jaxb2RootElementHttpMessageConverter 负责读、写XML格式数据(使用JAXB)
ByteArrayHttpMessageConverter 负责读、写二进制格式数据
StringHttpMessageConverter 负责读、写字符串格式数据
ResourceHttpMessageConverter 负责读、写资源文件数据
SourceHttpMessageConverter 负责读、写资源数据
3.3 接口说明
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
return !this.canRead(clazz, (MediaType)null) && !this.canWrite(clazz, (MediaType)null) ? Collections.emptyList() : this.getSupportedMediaTypes();
}
T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}
getSupportedMediaTypes:获取支持的MediaType集合(如:text/html,text/plain,application/json)
canRead:判断是否能读,针对请求
read:将请求数据进行格式转换(canRead方法返回值为true时调用)
canWrite:判断是否能写,针对响应
write:将响应数据进行格式转换(canWrite方法返回值为true时调用)
4、自定义HandlerMethodArgumentResolver
自定义的过程基本上就是继承接口,然后加入到系统里
4.1、创建springboot项目
直接跟着指引,下一步就可以完成了,并没有太多的技术含量,这里也不再赘述
4.2、创建自定义HandlerMethodArgumentResolver
import com.example.webdemo.domain.po.Person;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
public class PersonArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(Person.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
String s = webRequest.getParameter("person");
String[] split = s.split(":");
Person person = new Person();
person.setName(split[0]);
person.setAge(split[1]);
return person;
}
}
Person定义
@Data
public class Person {
private String name;
private String age;
}
4.3、将自定义Resolver加入到系统中
@Configuration
public class MyWebmvcConfiguration implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new PersonArgumentResolver());
}
}
4.4 测试接口
@RestController
@RequestMapping
public class TestController{
@RequestMapping("/up")public Stringtest1(Personperson){
System.out.println(person);return"Hello";
}
}
测试脚本
curl --request GET \
--url 'http://localhost:16002/up?person=chongxin: 1'
记得在PersonArgumentResolver里面打个断点哦
5、自定义Converter
5.1、创建springboot项目
直接沿用上面的吧,稍微的删除下
5.2、自定义MessageConverter
public class SecondHttpMessageConverter extends AbstractHttpMessageConverter<Person> {
public SecondHttpMessageConverter() {
super(new MediaType("application", "x-xiangcai", Charset.forName("UTF-8")));
}
@Override
protected boolean supports(Class<?> clazz) {
return Person.class.isAssignableFrom(clazz);
}
@Override
protected Person readInternal(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
String s = StreamUtils.copyToString(inputMessage.getBody(), Charset.defaultCharset());
String[] split = s.split(":");
Person person = new Person();
person.setName(split[0]);
person.setAge(split[1]);
return person;
}
@Override
protected void writeInternal(Person person, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
}
}
5.3 加入配置中
@Configuration
public class MyWebmvcConfiguration implements WebMvcConfigurer{
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>>converters){
SecondHttpMessageConvertersecondHttpMessageConverter=newSecondHttpMessageConverter();
converters.add(secondHttpMessageConverter);
}}
5.4 测试代码
@RestController
public class TestController {
@RequestMapping("/up")
public String test1(@RequestBody Person person) {
System.out.println(person);
return "Hello";
}
}
注意看这里的代码和上面是不同的,不要使用上面的接口哦
测试脚本
curl --request POST \
--url http://localhost:16002/up \
--header 'Content-Type: application/x-xiangcai' \
--header 'content-type: text/plain' \
--data 'chongxin: 1'
注意这里使用的是post,并且设置了Content-Type: application/x-xiangcai
5.5 注意点
处理过程会按集合顺序匹配合适的消息转换器,如果有合适的,就会使用该消息转换器处理(读、写),后续的消息转换器不再执行。
自定义的消息转换器要想生效,必须放到集合中相同类型的消息转换器前面,原因参考第二点。
WebMvcConfigurer.configureMessageConverters方法会覆盖默认消息转换器集合
WebMvcConfigurer.extendMessageConverters方法不会覆盖默认消息转换器集合
6、总结
6.1 converter和Resolver的区别:
Converter 主要是用来做数据body的计息,针对@RequestBody
Resolver 主要是用来做数据类型转换,主要是用来解析参数,针对基于键值对的
6.2 自定义套路
继承相应的接口,在Configuration加入列表中
6.3 调用顺序
从数据流图中可以看到先是使用resolver,然后调用converter
6.4 最后贴下debug的图
方便你找到断点,贯穿整个流程
赠人玫瑰,手留余香,感谢点赞