Spring MVC參數(shù)校驗詳解(關(guān)于`@RequestBody`返回`400`)
前言
工作中發(fā)現(xiàn)一個定律,如果總是習(xí)慣別人幫忙做事的結(jié)果是自己不會做事了。一直以來,spring幫我解決了程序運行中的各種問題,我只要關(guān)心我的業(yè)務(wù)邏輯,設(shè)計好我的業(yè)務(wù)代碼,返回正確的結(jié)果即可。直到遇到了400。
spring返回400的時候通常沒有任何錯誤提示,當(dāng)然也通常是參數(shù)不匹配。這在參數(shù)少的情況下還可以一眼看穿,但當(dāng)參數(shù)很大是,排除參數(shù)也很麻煩,更何況,既然錯誤了,為什么指出來原因呢。好吧,springmvc把這個權(quán)力交給了用戶自己。話不多說了,來一起看看詳細(xì)的介紹吧。
springmvc異常處理
最開始的時候也想過自己攔截會出異常的method來進(jìn)行異常處理,但顯然不需要這么做。spring提供了內(nèi)嵌的以及全局的異常處理方法,基本可以滿足我的需求了。
1. 內(nèi)嵌異常處理
如果只是這個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("請求參數(shù)不匹配。", exception); return CodeMsg.error(exception.getMessage()); }
這里saveOrUpdate是我們想要攔截一樣的請求,而messageNotReadable則是處理異常的代碼。
@ExceptionHandler(HttpMessageNotReadableException.class)表示我要攔截何種異常。在這里,由于springmvc默認(rèn)采用jackson作為json序列化工具,當(dāng)反序列化失敗的時候就會拋出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)
這個注解是為了標(biāo)識這個方法返回值的HttpStatus code。我設(shè)置為400,當(dāng)然也可以自定義成其他的。
2. 批量異常處理
看到大多數(shù)資料寫的是全局異常處理,我覺得對我來說批量更合適些,因為我只是希望部分controller被攔截而不是全部。
springmvc提供了@ControllerAdvice來做批量攔截。
第一次看到注釋這么少的源碼,忍不住多讀幾遍。
Indicates the annotated class assists a "Controller".
表示這個注解是服務(wù)于Controller的。
Serves as a specialization of {@link Component @Component}, allowing for implementation classes to be autodetected through classpath scanning.
用來當(dāng)做特殊的Component注解,允許使用者掃描發(fā)現(xiàn)所有的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.
典型的應(yīng)用是用來定義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.
這幾個參數(shù)指定了掃描范圍。
the default behavior (i.e. if used without any selector), * the {@code @ControllerAdvice} annotated class will * assist all known Controllers.
默認(rèn)掃描所有的已知的的Controllers。
Note that those checks are done at runtime, so adding many attributes and using * multiple strategies may have negative impacts (complexity, performance).
注意這個檢查是在運行時做的,所以注意性能問題,不要放太多的參數(shù)。
說的如此清楚,以至于用法如此簡單。
@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("請求參數(shù)不匹配,request的json格式不正確", exception); return CodeMsg.error(exception.getMessage()); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(Exception.class) public CodeMsg ex(MethodArgumentNotValidException exception, HttpServletResponse response){ LOGGER.error("請求參數(shù)不合法。", 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
使用參數(shù)校驗如果不catch異常就會返回400. 所以這個也要規(guī)范一下。
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 使用
在實體類字段上標(biāo)注要求
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.錯誤處理
前面已經(jīng)提到,如果不做處理的結(jié)果就是400,415. 這個對應(yīng)Exception是MethodArgumentNotValidException,也是這樣:
@ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(Exception.class) public CodeMsg ex(MethodArgumentNotValidException exception, HttpServletResponse response){ LOGGER.error("請求參數(shù)不合法。", 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; }
返回結(jié)果:
{ "code": 1, "msg": "校驗失敗", "data": { "out_trade_no": "不能為空", "business_type": "訂單類型不能為空" } }
大概有這么幾個限制注解:
/** * Bean Validation 中內(nèi)置的 constraint * @Null 被注釋的元素必須為 null * @NotNull 被注釋的元素必須不為 null * @AssertTrue 被注釋的元素必須為 true * @AssertFalse 被注釋的元素必須為 false + * @Min(value) 被注釋的元素必須是一個數(shù)字,其值必須大于等于指定的最小值 * @Max(value) 被注釋的元素必須是一個數(shù)字,其值必須小于等于指定的最大值 * @DecimalMin(value) 被注釋的元素必須是一個數(shù)字,其值必須大于等于指定的最小值 * @DecimalMax(value) 被注釋的元素必須是一個數(shù)字,其值必須小于等于指定的最大值 * @Size(max=, min=) 被注釋的元素的大小必須在指定的范圍內(nèi) * @Digits (integer, fraction) 被注釋的元素必須是一個數(shù)字,其值必須在可接受的范圍內(nèi) * @Past 被注釋的元素必須是一個過去的日期 * @Future 被注釋的元素必須是一個將來的日期 * @Pattern(regex=,flag=) 被注釋的元素必須符合指定的正則表達(dá)式 * Hibernate Validator 附加的 constraint * @NotBlank(message =) 驗證字符串非null,且長度必須大于0 * @Email 被注釋的元素必須是電子郵箱地址 * @Length(min=,max=) 被注釋的字符串的大小必須在指定的范圍內(nèi) * @NotEmpty 被注釋的字符串的必須非空 * @Range(min=,max=,message=) 被注釋的元素必須在合適的范圍內(nèi) */
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
SpringAop中AspectJ框架的切入點表達(dá)式
這篇文章主要介紹了SpringAop中AspectJ框架的切入點表達(dá)式,AspectJ是一個基于Java語言的AOP框架,Spring2.0以后新增了對AspectJ切點表達(dá)式支持,@AspectJ 是AspectJ1.5新增功能,通過JDK5注解技術(shù),允許直接在Bean類中定義切面,需要的朋友可以參考下2023-08-08java實現(xiàn)數(shù)據(jù)結(jié)構(gòu)單鏈表示例(java單鏈表)
這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)實現(xiàn)單鏈表示例,需要的朋友可以參考下2014-03-03spring?cloud?eureka?服務(wù)啟動失敗的原因分析及解決方法
這篇文章主要介紹了spring?cloud?eureka?服務(wù)啟動失敗的原因解析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03