基础知识介绍:
在SpringBoot里,若Controller层里方法的形参前使用了@RequestBody注解,那么该参数将会被RequestResponseBodyMethodProcessor解析器进行解析,若此时Content-Type为application/x-www-form-urlencoded,那么会报Unsupported Media Type错误,这就要求:请求的Content-Type必须为application/json了。
一般的,若Controller层里方法的形参前(不管有没有其它注解,只要)没使用@RequestBody注解,那么该参数几乎都是符合ServletModelAttributeMethodProcessor解析器要求的,进而会使用ServletModelAttributeMethodProcessor解析器进行解析;当请求的Content-Type为application/x-www-form-urlencoded时,几乎用的都是ServletModelAttributeMethodProcessor解析器。
注:参数符合RequestResponseBodyMethodProcessor解析器要求的条件是
parameter.hasParameterAnnotation(RequestBody.class)。可详见
RequestResponseBodyMethodProcessor类源码。
注:参数符合ServletModelAttributeMethodProcessor解析器要求的条件是
parameter.hasParameterAnnotation(ModelAttribute.class) ||
this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())。
可详见ModelAttributeMethodProcessor类源码。
需求介绍及实现方式说明:
声明:本文通过实现下述需求,进行示例。
需求介绍:
实现被@RequestBody注解的参数,既能接收Content-Type为application/json的请求的参数值,又能接收Content-Type为application/x-www-form-urlencoded的请求的参数值。
实现方式概述:
自定义参数解析器,当参数前有@RequestBody时,使用该解析器;在该解析器的内部,判断Content-Type,若Content-Type为application/x-www-form-urlencoded,那么采用ServletModelAttributeMethodProcessor解析器;否者采用RequestResponseBodyMethodProcessor解析器。
注:即相当于对RequestResponseBodyMethodProcessor解析器进行了简单的封装。
具体实现代码示例:
自定义参数解析器:
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.annotation.RequestBody;
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;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;
import javax.servlet.http.HttpServletRequest;
/**
* 自定义参数解析器
*
* 注: 自定义参数解析器, 实现对RequestResponseBodyMethodProcessor的扩展
*
* 提示: 此解析器要实现的功能是: 若controller方法的参数前, 使用了@RequestBody注解, 那么解析此参数时,
* 1、若Content-Type为application/x-www-form-urlencoded,
* 那么走ServletModelAttributeMethodProcessor解析器
* 2、若Content-Type不为application/x-www-form-urlencoded,
* 那么走本应该走的RequestResponseBodyMethodProcessor解析器
*
* @author JustryDeng
* @date 2019/8/19 19:47
*/
public class MyHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
/**
* 解析Content-Type为application/json的默认解析器是RequestResponseBodyMethodProcessor
*/
private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor;
/**
* 解析Content-Type为application/x-www-form-urlencoded的默认解析器是ServletModelAttributeMethodProcessor
*/
private ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor;
/**
* 全参构造
*/
public MyHandlerMethodArgumentResolver(RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor,
ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor) {
this.requestResponseBodyMethodProcessor = requestResponseBodyMethodProcessor;
this.servletModelAttributeMethodProcessor = servletModelAttributeMethodProcessor;
}
/**
* 当参数前有@RequestBody注解时, 解析该参数 会使用此 解析器
*
* 注:此方法的返回值将决定:是否使用此解析器解析该参数
*/
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.hasParameterAnnotation(RequestBody.class);
}
/**
* 解析参数
*/
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory)
throws Exception {
final String applicationXwwwFormUrlencoded = "application/x-www-form-urlencoded";
HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
if (request == null) {
throw new RuntimeException(" request must not be null!");
}
String contentType = request.getContentType();
/*
* 如果ContentType是application/x-www-form-urlencoded,那么使用ServletModelAttributeMethodProcessor解析器
*
* 注:其实默认的,当系统识别到参数前有@RequestBody注解时,就会走RequestResponseBodyMethodProcessor解析器;这里就
* 相当于在走默认的解析器前走了个判断而已。
*/
if (applicationXwwwFormUrlencoded.equals(contentType)) {
return servletModelAttributeMethodProcessor.resolveArgument(methodParameter,
modelAndViewContainer, nativeWebRequest, webDataBinderFactory);
}
return requestResponseBodyMethodProcessor.resolveArgument(methodParameter,
modelAndViewContainer, nativeWebRequest, webDataBinderFactory);
}
}
注册该参数解析器:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* 配置注册参数解析器
*
* @author JustryDeng
* @date 2019/8/20 10:13
*/
@Configuration
public class ConfigArgumentResolvers {
private final RequestMappingHandlerAdapter requestMappingHandlerAdapter;
public ConfigArgumentResolvers(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
this.requestMappingHandlerAdapter = requestMappingHandlerAdapter;
}
@PostConstruct
private void addArgumentResolvers() {
// 获取到的是不可变的集合
List<HandlerMethodArgumentResolver> argumentResolvers =
requestMappingHandlerAdapter.getArgumentResolvers();
MyHandlerMethodArgumentResolver myHandlerMethodArgumentResolver =
getMyHandlerMethodArgumentResolver(argumentResolvers);
// ha.getArgumentResolvers()获取到的是不可变的集合,所以我们需要新建一个集合来放置参数解析器
List<HandlerMethodArgumentResolver> myArgumentResolvers =
new ArrayList<>(argumentResolvers.size() + 1);
// 将自定义的解析器,放置在第一个; 并保留原来的解析器
myArgumentResolvers.add(myHandlerMethodArgumentResolver);
myArgumentResolvers.addAll(argumentResolvers);
requestMappingHandlerAdapter.setArgumentResolvers(myArgumentResolvers);
}
/**
* 获取MyHandlerMethodArgumentResolver实例
*/
private MyHandlerMethodArgumentResolver getMyHandlerMethodArgumentResolver(
List<HandlerMethodArgumentResolver> argumentResolversList) {
// 解析Content-Type为application/json的默认解析器
RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor = null;
// 解析Content-Type为application/x-www-form-urlencoded的默认解析器
ServletModelAttributeMethodProcessor servletModelAttributeMethodProcessor = null;
if (argumentResolversList == null) {
throw new RuntimeException("argumentResolverList must not be null!");
}
for (HandlerMethodArgumentResolver argumentResolver : argumentResolversList) {
if (requestResponseBodyMethodProcessor != null && servletModelAttributeMethodProcessor != null) {
break;
}
if (argumentResolver instanceof RequestResponseBodyMethodProcessor) {
requestResponseBodyMethodProcessor = (RequestResponseBodyMethodProcessor)argumentResolver;
continue;
}
if (argumentResolver instanceof ServletModelAttributeMethodProcessor) {
servletModelAttributeMethodProcessor = (ServletModelAttributeMethodProcessor)argumentResolver;
}
}
if (requestResponseBodyMethodProcessor == null || servletModelAttributeMethodProcessor == null) {
throw new RuntimeException("requestResponseBodyMethodProcessor and "
+ " servletModelAttributeMethodProcessor must not be null!");
}
return new MyHandlerMethodArgumentResolver(requestResponseBodyMethodProcessor,
servletModelAttributeMethodProcessor);
}
测试一下:
测试项目结构及相关测试类说明:
项目结构:
注:ConfigArgumentResolvers与MyHandlerMethodArgumentResolver在上文中已给出,下面只给
出Person与DemoController的内容。
Person:
DemoController:
基本功能测试(工具postman):
-
application/x-www-form-urlencoded访问/xyz:
-
application/json访问/abc:
-
application/json访问/foo:
postman效果如图所示:
后端控制台输出:
注:这说明当请求Content-Type为application/json时,@RequestParam获取不到请求体json串里面的值。
自定义参数解析器后的功能测试(工具postman):
-
application/x-www-form-urlencoded访问/abc:
-
application/x-www-form-urlencoded访问/foo:
postman效果如图所示:
后端控制台输出:
由此可见:自定义参数解析器,示例成功!