SpringMVC参数校验(针对@RequestBody返回400

From https://ryan-miao.github.io/2017/05/20/spring400/

前言

习惯别人帮忙做事的结果是自己不会做事了。一直以来,spring帮我解决了程序运行中的各种问题,我只要关心我的业务逻辑,设计好我的业务代码,返回正确的结果即可。直到遇到了400

spring返回400的时候通常没有任何错误提示,当然也通常是参数不匹配。这在参数少的情况下还可以一眼看穿,但当参数很大是,排除参数也很麻烦,更何况,既然错误了,为什么指出来原因呢。好吧,springmvc把这个权力交给了用户自己。

springmvc异常处理

最开始的时候也想过自己拦截会出异常的method来进行异常处理,但显然不需要这么做。spring提供了内嵌的以及全局的异常处理方法,基本可以满足我的需求了。

1. 内嵌异常处理

如果只是这个controller的异常做单独处理,那么就适合绑定这个controller本身的异常。

具体做法是使用注解@ExceptionHandler.

在这个controller中添加一个方法,并添加上述注解,并指明要拦截的异常。

@RequestMapping(value = "saveOrUpdate", method = RequestMethod.POST)
public String saveOrUpdate(HttpServletResponse response, @RequestBody Order order){
CodeMsg result = null;
try {
result = orderService.saveOrUpdate(order);
} catch (Exception e) {
logger.error("save failed.", e);
return this.renderString(response, CodeMsg.error(e.getMessage()));
}
return this.renderString(response, result);
} @ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public CodeMsg messageNotReadable(HttpMessageNotReadableException exception, HttpServletResponse response){
LOGGER.error("请求参数不匹配。", exception);
return CodeMsg.error(exception.getMessage());
}

这里saveOrUpdate是我们想要拦截一样的请求,而messageNotReadable则是处理异常的代码。

@ExceptionHandler(HttpMessageNotReadableException.class)表示我要拦截何种异常。在这里,由于springmvc默认采用jackson作为json序列化工具,当反序列化失败的时候就会抛出HttpMessageNotReadableException异常。具体如下:

{
"code": 1,
"msg": "Could not read JSON: Failed to parse Date value '2017-03-' (format: \"yyyy-MM-dd HH:mm:ss\"): Unparseable date: \"2017-03-\" (through reference chain: com.test.modules.order.entity.Order[\"serveTime\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Failed to parse Date value '2017-03-' (format: \"yyyy-MM-dd HH:mm:ss\"): Unparseable date: \"2017-03-\" (through reference chain: com.test.modules.order.entity.Order[\"serveTime\"])",
"data": ""
}

这是个典型的jackson反序列化失败异常,也是造成我遇见过的400原因最多的。通常是日期格式不对。

另外,@ResponseStatus(HttpStatus.BAD_REQUEST)这个注解是为了标识这个方法返回值的HttpStatus code。我设置为400,当然也可以自定义成其他的。

2. 批量异常处理

看到大多数资料写的是全局异常处理,我觉得对我来说批量更合适些,因为我只是希望部分controller被拦截而不是全部。

springmvc提供了@ControllerAdvice来做批量拦截。

第一次看到注释这么少的源码,忍不住多读几遍。

Indicates the annotated class assists a "Controller".

表示这个注解是服务于Controller的。

Serves as a specialization of {@link Component @Component}, allowing for implementation classes to be autodetected through classpath scanning.

用来当做特殊的Component注解,允许使用者扫描发现所有的classpath

It is typically used to define {@link ExceptionHandler @ExceptionHandler},
* {@link InitBinder @InitBinder}, and {@link ModelAttribute @ModelAttribute}
* methods that apply to all {@link RequestMapping @RequestMapping} methods.

典型的应用是用来定义xxxx.

One of {@link #annotations()}, {@link #basePackageClasses()},
* {@link #basePackages()} or its alias {@link #value()}
* may be specified to define specific subsets of Controllers
* to assist. When multiple selectors are applied, OR logic is applied -
* meaning selected Controllers should match at least one selector.

这几个参数指定了扫描范围。

the default behavior (i.e. if used without any selector),
* the {@code @ControllerAdvice} annotated class will
* assist all known Controllers.

默认扫描所有的已知的的Controllers。

Note that those checks are done at runtime, so adding many attributes and using
* multiple strategies may have negative impacts (complexity, performance).

注意这个检查是在运行时做的,所以注意性能问题,不要放太多的参数。

说的如此清楚,以至于用法如此简单。

@ResponseBody
@ControllerAdvice("com.api")
public class ApiExceptionHandler extends BaseClientController {
private static final Logger LOGGER = LoggerFactory.getLogger(ApiExceptionHandler.class); /**
*
* @param exception UnexpectedTypeException
* @param response
* @return
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(UnexpectedTypeException.class)
public CodeMsg unexpectedType(UnexpectedTypeException exception, HttpServletResponse response){
LOGGER.error("校验方法太多,不确定合适的校验方法。", exception);
return CodeMsg.error(exception.getMessage());
} @ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public CodeMsg messageNotReadable(HttpMessageNotReadableException exception, HttpServletResponse response){
LOGGER.error("请求参数不匹配。", exception);
return CodeMsg.error(exception.getMessage());
} @ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException .class)
public CodeMsg ex(MethodArgumentNotValidException exception, HttpServletResponse response){
LOGGER.error("请求参数不合法。", exception);
BindingResult bindingResult = exception.getBindingResult();
String msg = "校验失败";
return new CodeMsg(CodeMsgConstant.error, msg, getErrors(bindingResult));
} private Map<String, String> getErrors(BindingResult result) {
Map<String, String> map = new HashMap<>();
List<FieldError> list = result.getFieldErrors();
for (FieldError error : list) {
map.put(error.getField(), error.getDefaultMessage());
}
return map;
}
}

3. Hibernate-validate

使用参数校验如果不catch异常就会返回400. 所以这个也要规范一下。

3.1 引入hibernate-validate
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.0.2.Final</version>
</dependency>

<mvc:annotation-driven validator="validator" />
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<property name="validationMessageSource" ref="messageSource"/>
</bean>
3.2 使用
  1. 在实体类字段上标注要求
public class AlipayRequest {
@NotEmpty
private String out_trade_no;
private String subject;
@DecimalMin(value = "0.01", message = "费用最少不能小于0.01")
@DecimalMax(value = "100000000.00", message = "费用最大不能超过100000000")
private String total_fee;
/**
* 订单类型
*/
@NotEmpty(message = "订单类型不能为空")
private String business_type; //....
}
  1. controller里添加@Valid
@RequestMapping(value = "sign", method = RequestMethod.POST)
public String sign(@Valid @RequestBody AlipayRequest params
){
....
}

3.错误处理

前面已经提到,如果不做处理的结果就是400,415. 这个对应Exception是MethodArgumentNotValidException,也是这样:

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(Exception.class)
public CodeMsg ex(MethodArgumentNotValidException exception, HttpServletResponse response){
LOGGER.error("请求参数不合法。", exception);
BindingResult bindingResult = exception.getBindingResult();
String msg = "校验失败";
return new CodeMsg(CodeMsgConstant.error, msg, getErrors(bindingResult));
} private Map<String, String> getErrors(BindingResult result) {
Map<String, String> map = new HashMap<>();
List<FieldError> list = result.getFieldErrors();
for (FieldError error : list) {
map.put(error.getField(), error.getDefaultMessage());
}
return map;
}

返回结果:

{
"code": 1,
"msg": "校验失败",
"data": {
"out_trade_no": "不能为空",
"business_type": "订单类型不能为空"
}
}

大概有这么几个限制注解:

/**
* Bean Validation 中内置的 constraint
* @Null 被注释的元素必须为 null
* @NotNull 被注释的元素必须不为 null
* @AssertTrue 被注释的元素必须为 true
* @AssertFalse 被注释的元素必须为 false
* @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
* @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
* @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
* @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
* @Size(max=, min=) 被注释的元素的大小必须在指定的范围内
* @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
* @Past 被注释的元素必须是一个过去的日期
* @Future 被注释的元素必须是一个将来的日期
* @Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
* Hibernate Validator 附加的 constraint
* @NotBlank(message =) 验证字符串非null,且长度必须大于0
* @Email 被注释的元素必须是电子邮箱地址
* @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
* @NotEmpty 被注释的字符串的必须非空
* @Range(min=,max=,message=) 被注释的元素必须在合适的范围内
*/

SpringMVC参数校验(针对`@RequestBody`返回`400`)的更多相关文章

  1. SpringMVC参数校验

    使用SpringMVC时配合hibernate-validate进行参数的合法性校验,能节省一定的代码量. 使用步骤 1.搭建Web工程并引入hibernate-validate依赖 <depe ...

  2. springmvc 参数校验/aop失效/@PathVariable 参数为空

    添加依赖 <!-- 参数校验 --> <dependency> <groupId>org.hibernate.validator</groupId> & ...

  3. SpringMVC参数校验,包括JavaBean和基本类型的校验

    该示例项目使用SpringBoot,添加web和aop依赖. SpringMVC最常用的校验是对一个javaBean的校验,默认使用hibernate-validator校验框架.而网上对校验单个参数 ...

  4. 【SpringMVC】添加操作时返回400

    本博客老魏原创,如需转载请留言 问题描述: springmvc向数据库添加新的记录时,发生400错误,控制台没有抛出异常. 问题原因: 视图中的提交数据的某一个字段不不匹配导致. 解决方法: 不要怀疑 ...

  5. 实用———springmvc接收参数校验

    https://www.cnblogs.com/funyoung/p/8670550.html https://www.cnblogs.com/monkeydai/p/10068547.html He ...

  6. SpringBoot 如何进行参数校验,老鸟们都这么玩的!

    大家好,我是飘渺. 前几天写了一篇 SpringBoot如何统一后端返回格式?老鸟们都是这样玩的! 阅读效果还不错,而且被很多号主都转载过,今天我们继续第二篇,来聊聊在SprinBoot中如何集成参数 ...

  7. Validation(3)--全局参数异常校验捕获及返回XML解决

    @RestControllerAdvice原创直接上代码,后面再说怎么用1.这个是一个Form,用来接收参数的,一个简单的NotEmpty注解校验,merchantName这个参数是必传的: pack ...

  8. springmvc、springboot 参数校验

    参数校验在项目中是必不可少的,不仅前端需要校验,为了程序的可靠性,后端也需要对参数进行有效性的校验.下面将介绍在springmvc或springboot项目中参数校验的方法 准备工作: 引入校验需要用 ...

  9. 补习系列(4)-springboot 参数校验详解

    目录 目标 一.PathVariable 校验 二.方法参数校验 三.表单对象校验 四.RequestBody 校验 五.自定义校验规则 六.异常拦截器 参考文档 目标 对于几种常见的入参方式,了解如 ...

随机推荐

  1. Ubuntu系统下搭建PPTP类型VPN环境

    step1: 安装pptpd 很简单的命令:sudo apt-get install pptpd step2: 修改pptpd的配置 有三个文件需要修改: (1)修改/etc/pptpd.conf,添 ...

  2. webqq的注册登记和聊天页面--运用jsonp跨域

    简介: 我们知道,ajax用于数据交互,但它不能跨域,跨域是指从一个域名的网页去请求另一个域名的资源.比如从http://www.baidu.com/ 页面去请求 http://www.google. ...

  3. iOS开发学习路径的一些建议

    结合自己情况聊下iOS学习建议,这里不讲大道理,说说具体怎么做.欢迎大家拍砖. 1.第一点要求 ,能比较顺畅的阅读官方的文档 如果你连官方的文档读起来都非常困难,那你还谈什么提高和进阶,咱们学习iOS ...

  4. 【转】JDBC连接数据库

    创建一个以JDBC连接数据库的程序,包含7个步骤: 1.加载JDBC驱动程序: 在连接数据库之前,首先要加载想要连接的数据库的驱动到JVM(Java虚拟机), 这通过java.lang.Class类的 ...

  5. 前端学PHP之日期与时间

    前面的话 在Web程序开发时,时间发挥着重要的作用,不仅在数据存储和显示时需要日期和时间的参与,好多功能模块的开发,时间通常都是至关重要的.网页静态化需要判断缓存时间.页面访问消耗的时间需要计算.根据 ...

  6. 每天一个Linux命令(13)--less命令

    less 工具也是对文件或其它输出进行分页显示的工具,应该说是Linux正统馋看文件内容的工具,功能极其强大.less 的用法比起  more 更加有弹性.  在 more 的时候,我们没有办法向前面 ...

  7. 自定义view(二)

    这里是自定义view(二),上一篇关于自定义view的一些基本知识,比如说自定义view的步骤.会涉及到哪些函数以及如何实现自定义属性,同时实现了一个很基础的自定义控件,一个自定义的计时器,需要看的人 ...

  8. Linux的capability深入分析

    Linux的capability深入分析详见:http://blog.csdn.net/u014338577/article/details/48791953 lxd中对容器能力的限制: 普通用户不能 ...

  9. 使用docker-compose 大杀器来部署服务 上

    使用docker-compose 大杀器来部署服务 上 我们都听过或者用过 docker,然而使用方式却是仅仅用手动的方式,这样去操作 docker 还是很原始. 好吧,可能在小白的眼中噼里啪啦的对着 ...

  10. Django之Cookie

    Cookie 在浏览器端(客户端)保存的键值对,特性:每次http请求都会携带.           举个例子:{"name":身份证号} 1丶获取cookie request.C ...