Spring MVC参数校验详解(关于`@RequestBody`返回`400`)

网友投稿 1143 2023-04-23


Spring MVC参数校验详解(关于`@RequestBody`返回`400`)

前言

工作中发现一个定律,如果总是习惯别人帮忙做事的结果是自己不会做事了。一直以来,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 ySvGeto 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("请求参数不匹配,request的json格式不正确", exception);

return CodeMsg.error(exception.getMessage());

}

@ResponseStatus(HttpStatus.BAD_REQUEST)

@ExceptionHandler(Exception.class)

public CodeMsg ex(MethodArgumentNotValidySvGeException exception, HttpServletResponse response){

LOGGER.error("请求参数不合法。", exception);

BindingResult bindingResult = exception.getBindingResult();

String msg = "校验失败";

return new CodeMsg(CodeMsgConstant.error, msg, getErrors(bindingResult));

}

private Map getErrors(BindingResult result) {

Map map = new HashMap<>();

List list = result.getFieldErrors();

for (FieldError error : list) {

map.put(error.getField(), error.getDefaultMessage());

}

return map;

}

}

3. Hibernate-validate

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

3.1 引入hibernate-validate

org.hibernate

hibernate-validator

5.0.2.Final

3.2 使用

在实体类字段上标注要求

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;

//....

}

controller里添加@Valid

@RequestMapping(value = "sign", method = RequestMethod.POST)

public String sign(@Valid @RequestBody AlipayRequest params

){

....

}

4.错误处理

前面已经提到,如果不做处理的结果就是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 getErrors(BindingResult result) {

Map map = new HashMap<>();

List list = result.getFieldErrors();

for (FieldError error : list) {

map.put(error.getField(), error.getDefaultMessage());

}

return map;

}

返回结果:

{

"code": 1,

"msg": "校验失败",

"data": {

"out_trade_no": "不能为空",

"business_type": "订单类型不能为空"

}http://

}

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

/**

* 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=) 被注释的元素必须在合适的范围内

*/

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对我们的支持。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:微信小程序 本地图片按照屏幕尺寸处理
下一篇:ThreadLocal简介_动力节点Java学院整理
相关文章

 发表评论

暂时没有评论,来抢沙发吧~