本文代码已整理上传
github
上篇文章中已经实现了同一个url不同视图的返回, 可以返回 json 数据, 也可以返回 html,
但是对于 spring mvc 接收 request 参数来说,
如果接收的是 json 数据, 需要在参数前加@RequestBody注解
接收 form 表单提交的数据则不需加注解
如何做到自动接收json参数或者from表单参数?
对于http request来说, 我们可以根据请求的 content-type
来判断请求传递的参数是什么格式的
content-type 是 application/json
的 request 的 body 是 json 数据
content-type 是 application/x-www-form-urlencoded
的 request 是表单提交
对于spring mvc, 对接收参数对处理是 HandlerMethodArgumentResolver
实现处理的,
所以思路就是: 找到处理json的HandlerMethodArgumentResolver和处理表单的HandlerMethodArgumentResolver,
然后自定义 自己处理表单和json的HandlerMethodArgumentResolver, 根据 request 的content-type 在选择对应的参数Resolver
自定义的 Resolver相当于 json 的Resolver 和 form表单的Resolver的组合
form 表单的 HandlerMethodArgumentResolver
先在GoodsController中写一个方法, 参数是通过 表单提交接收, 然后断点调试
@Controller
@RequestMapping("/goods")
public class GoodsController {
private static final List GOODS_LIST = new ArrayList<>();
static {
GOODS_LIST.add(new Goods("998765", "哇哈哈矿泉水", 2.0));
GOODS_LIST.add(new Goods("568925", "蒙牛真果粒", 4.7));
}
@RequestMapping("/list")
public String list(GoodsCondition condition, Model model) {
model.addAttribute("data", GOODS_LIST);
return "goods";
}
// goods 参数 以表单形式接收
@PostMapping
public void add(Goods goods) {
GOODS_LIST.add(goods);
}
}
然后使用接口测试工具(可以使用postman
), 测试上面的接口, 表单方式
通过DispatchServlet的doDispatch方发找到处理参数的地方,
处理参数的地方在org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues() 方法
然后委托给HandlerMethodArgumentResolverComposite去处理参数, 这是个所有ArgumentResolver的集合, 本身不处理, 负责找到合适的ArgumentResolver
HandlerMethodArgumentResolverComposite#getArgumentResolver() 这个方法就是根据需要绑定的参数找到ArgumentResolver, 所以在这里找到 form 表单的处理Resolver就可以了
断点走到这里, 这个ServletModelAttributeMethodProcessor就是我们要找的表单处理器,里面的属性annotationNotRequired的值是true
处理 json 的 HandlerMethodArgumentResolver
将上面的add方法的参数中加上 @RequestBody注解
@PostMapping
public void add(@RequestBody Goods goods) {
GOODS_LIST.add(goods);
}
再用接口测试工具, 以json数据提交
再次断点到HandlerMethodArgumentResolverComposite#getArgumentResolver()看最终的结果
可以看到: 处理json参数的是 RequestResponseBodyMethodProcessor, 里面的属性里有很多HttpMessageConverter
接下来, 就是将这两个 组合起来
自定义 HandlerMethodArgumentResolver
创建JsonAndFormArgumentResolver为自定义的resolver
按照上面的分析, 需要在JsonAndFormArgumentResolver内获取 当前的request来判断再使用具体的resolver, 如何获取当前request呢?需要在过滤器中加入 RequestContextFilter, 这是spring提供的, 将当前 request 保存起来
// 配置filters
@Override
protected Filter[] getServletFilters() {
// 字符
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding(String.valueOf(StandardCharsets.UTF_8));
encodingFilter.setForceEncoding(true);
// 请求拦截器
RequestContextFilter requestContextFilter = new RequestContextFilter();
return new Filter[]{encodingFilter, requestContextFilter};
}
使用 spring boot的同学可以过滤此步骤, 都已经自动配置了
然后, 贴出自定义 resolver的代码
/**
* User : liulu
* Date : 2018/4/4 09:50
* version $Id: JsonAndFormArgumentResolver.java, v 0.1 Exp $
*/
@NoArgsConstructor
public class JsonAndFormArgumentResolver implements HandlerMethodArgumentResolver {
@Setter
private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor;
@Setter
private ModelAttributeMethodProcessor modelAttributeMethodProcessor;
public JsonAndFormArgumentResolver(ModelAttributeMethodProcessor methodProcessor, RequestResponseBodyMethodProcessor bodyMethodProcessor){
this.modelAttributeMethodProcessor = methodProcessor;
this.requestResponseBodyMethodProcessor = bodyMethodProcessor;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return modelAttributeMethodProcessor.supportsParameter(parameter)
|| requestResponseBodyMethodProcessor.supportsParameter(parameter);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if (HttpMethod.GET.matches(request.getMethod().toUpperCase())) {
return modelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
if (request.getContentType().contains(MediaType.APPLICATION_JSON_VALUE)) {
return requestResponseBodyMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
return modelAttributeMethodProcessor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
}
配置自定义 HandlerMethodArgumentResolver
类是写好了, 但是总归是要配置才能生效的,配置写在 SpringMvcConfig
中
配置如下:
@Bean
public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
return new WebMvcConfigurerAdapter() {
@Override
public void addArgumentResolvers(List argumentResolvers) {
argumentResolvers.add(new JsonAndFormArgumentResolver());
}
};
}
@Bean
public BeanPostProcessor jsonAndFormArgumentResolverProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof RequestMappingHandlerAdapter) {
JsonAndFormArgumentResolver jsonAndFormArgumentResolver = null;
ModelAttributeMethodProcessor modelAttributeMethodProcessor = null;
RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor = null;
for (HandlerMethodArgumentResolver argumentResolver : ((RequestMappingHandlerAdapter) bean).getArgumentResolvers()) {
if (argumentResolver instanceof JsonAndFormArgumentResolver) {
jsonAndFormArgumentResolver = (JsonAndFormArgumentResolver) argumentResolver;
continue;
}
if (argumentResolver instanceof RequestResponseBodyMethodProcessor) {
requestResponseBodyMethodProcessor = (RequestResponseBodyMethodProcessor) argumentResolver;
continue;
}
if (argumentResolver instanceof ModelAttributeMethodProcessor) {
modelAttributeMethodProcessor = (ModelAttributeMethodProcessor) argumentResolver;
}
}
if (jsonAndFormArgumentResolver != null) {
jsonAndFormArgumentResolver.setModelAttributeMethodProcessor(modelAttributeMethodProcessor);
jsonAndFormArgumentResolver.setRequestResponseBodyMethodProcessor(requestResponseBodyMethodProcessor);
}
}
return bean;
}
};
}
第一个bean是让配置的自定义参数处理器生效,
第二个bean是初始化自定义参数处理器中的参数,
到此,自动接收json参数或者from表单参数已经完成, 将参数中的 @RequestBody 注解去掉
再运行刚才的表单提交和json提交, 都可以正常绑定参数