@validated注解異常返回JSON值方式
@validated注解異常返回JSON值
@ControllerAdvice
public class ValidParamExceptionHandler {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Map<String, Object> allExceptionHandler(Exception e){
Map<String, Object> map = new HashMap<>(2);
if(e instanceof BindException) {
BindException ex = (BindException)e;
BindingResult bindingResult = ex.getBindingResult();
StringBuilder errMsg = new StringBuilder(bindingResult.getFieldErrors().size() * 16);
errMsg.append("Invalid request:");
for (int i = 0 ; i < bindingResult.getFieldErrors().size() ; i++) {
if(i > 0) {
errMsg.append(",");
}
FieldError error = bindingResult.getFieldErrors().get(i);
errMsg.append(error.getField()+":"+error.getDefaultMessage());
}
map.put("errcode", 500);
map.put("errmsg", errMsg.toString());
}
else {
map.put("errcode", 500);
map.put("errmsg", e.getMessage());
}
return map;
}
(1)這里@ControllerAdvice注解標注,@ControllerAdvice是@Controller的增強版,一般與@ExceptionHandler搭配使用。
如果標注@Controller,異常處理只會在當前controller類中的方法起作用,但是使用@ControllerAdvice,則全局有效。
(2)@ExceptionHandler注解里面填寫想要捕獲的異常類class對象
使用@Valid注解,對其參數(shù)錯誤異常的統(tǒng)一處理
在我們使用springboot作為微服務(wù)框架進行敏捷開發(fā)的時候,為了保證傳遞數(shù)據(jù)的安全性,需要對傳遞的數(shù)據(jù)進行校驗,但是在以往的開發(fā)中,開發(fā)人員花費大量的時間在繁瑣的if else 等判斷語句來對參數(shù)進行校驗,這種方式不但降低了我們的開發(fā)速度,而且寫出來的代碼中帶有很多冗余代碼,使得編寫的代碼不夠優(yōu)雅,為了將參數(shù)的驗證邏輯和代碼的業(yè)務(wù)邏輯進行解耦,Java給我們提供了@Valid注解,用來幫助我們進行參數(shù)的校驗,實現(xiàn)了將業(yè)務(wù)邏輯和參數(shù)校驗邏輯在一定程度上的解耦,增加了代碼的簡潔性和可讀性。
springboot中自帶了spring validation參數(shù)校驗框架,其使用上和@valid差不多,在這里就不累述了,本文主要講解@valid的使用對其參數(shù)校驗失敗后的錯誤一樣的統(tǒng)一處理。
首先,簡介對微服務(wù)開發(fā)中異常的統(tǒng)一處理,spring中的@RestControllerAdvice注解可以獲取帶有@controller注解類的異常,通過@ExceptionHandler(MyException.class)注解來共同完成對異常進行處理。示例如下:
/**
* 通用異常攔截處理類(通過切面的方式默認攔截所有的controller異常)
*/
@Slf4j
@RestControllerAdvice
public class CommonExceptionHandler {
/**
* 對運行時異常進行統(tǒng)一異常管理方法
* @param e
* @return
*/
@ExceptionHandler(FlyException.class) // FlyException類繼承于RuntimeException
public ResponseEntity<Map<String, Object>> handlerException(FlyException e) {
Map<String, Object> result = new HashMap<>(1);
result.put("message", e.getMessage());
return ResponseEntity.status(e.getCode()).body(result);
}
通過注解@RestControllerAdvice和注解@ExceptionHandler的聯(lián)合使用來實現(xiàn)對異常的統(tǒng)一處理,然后在前端以友好的方式顯示。
使用@Valid注解的示例如下:
@PostMapping
public ResponseEntity save(@Valid BrandCreateRequestDto dto, BindingResult bindingResult) {
// 判斷是否含有校驗不匹配的參數(shù)錯誤
if (bindingResult.hasErrors()) {
// 獲取所有字段參數(shù)不匹配的參數(shù)集合
List<FieldError> fieldErrorList = bindingResult.getFieldErrors();
Map<String, Object> result = new HashMap<>(fieldErrorList.size());
fieldErrorList.forEach(error -> {
// 將錯誤參數(shù)名稱和參數(shù)錯誤原因存于map集合中
result.put(error.getField(), error.getDefaultMessage());
});
return ResponseEntity.status(HttpStatus.BAD_REQUEST.value()).body(result);
}
brandService.save(dto);
return ResponseEntity.status(HttpStatus.CREATED.value()).build();
}
@Valid注解確實將我們原來的參數(shù)校驗的問題進行了簡化,但是,如果我們有多個handler需要處理,那我們豈不是每次都要寫這樣的冗余代碼。通過查看@valid的實現(xiàn)機制(這里就不描述了),當參數(shù)校驗失敗的時候,會拋出MethodArgumentNotValidException異常(當用{@code @Valid}注釋的參數(shù)在驗證失敗時,將引發(fā)該異常):
/**
* Exception to be thrown when validation on an argument annotated with {@code @Valid} fails.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
@SuppressWarnings("serial")
public class MethodArgumentNotValidException extends Exception {
private final MethodParameter parameter;
private final BindingResult bindingResult;
/**
* Constructor for {@link MethodArgumentNotValidException}.
* @param parameter the parameter that failed validation
* @param bindingResult the results of the validation
*/
public MethodArgumentNotValidException(MethodParameter parameter, BindingResult bindingResult) {
this.parameter = parameter;
this.bindingResult = bindingResult;
}
按照我們的預(yù)想,我們只需要在原來定義的統(tǒng)一異常處理類中,捕獲MethodArgumentNotValidException異常,然后對其錯誤信息進行分析和處理即可實現(xiàn)通用,代碼如下:
/**
* 對方法參數(shù)校驗異常處理方法
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handlerNotValidException(MethodArgumentNotValidException exception) {
log.debug("begin resolve argument exception");
BindingResult result = exception.getBindingResult();
Map<String, Object> maps;
if (result.hasErrors()) {
List<FieldError> fieldErrors = result.getFieldErrors();
maps = new HashMap<>(fieldErrors.size());
fieldErrors.forEach(error -> {
maps.put(error.getField(), error.getDefaultMessage());
});
} else {
maps = Collections.EMPTY_MAP;
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(maps);
}
但是經(jīng)過測試,會發(fā)現(xiàn)對該異常進行捕獲然后處理是沒有效果的,這可能是我,也是大家遇到的問題之一,經(jīng)過對@Valid的執(zhí)行過程的源碼進行分析,數(shù)據(jù)傳遞到spring中的執(zhí)行過程大致為:前端通過http協(xié)議將數(shù)據(jù)傳遞到spring,spring通過HttpMessageConverter類將流數(shù)據(jù)轉(zhuǎn)換成Map類型,然后通過ModelAttributeMethodProcessor類對參數(shù)進行綁定到方法對象中,并對帶有@Valid或@Validated注解的參數(shù)進行參數(shù)校驗,對參數(shù)進行處理和校驗的方法為ModelAttributeMethodProcessor.resolveArgument(...),部分源代碼如下所示:
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
...
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
...
Object attribute = null;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
else {
// Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
bindingResult = ex.getBindingResult();
}
}
//進行參數(shù)綁定和校驗
if (bindingResult == null) {
// 對屬性對象的綁定和數(shù)據(jù)校驗;
// 使用構(gòu)造器綁定屬性失敗時跳過.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
// 對綁定參數(shù)進行校驗,校驗失敗,將其結(jié)果信息賦予bindingResult對象
validateIfApplicable(binder, parameter);
// 如果獲取參數(shù)綁定的結(jié)果中包含錯誤的信息則拋出異常
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
}
bindingResult = binder.getBindingResult();
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return attribute;
}
通過查看源碼,當BindingResult中存在錯誤信息時,會拋出BindException異常,查看BindException源代碼如下:
/**
* Thrown when binding errors are considered fatal. Implements the
* {@link BindingResult} interface (and its super-interface {@link Errors})
* to allow for the direct analysis of binding errors.
*
* <p>As of Spring 2.0, this is a special-purpose class. Normally,
* application code will work with the {@link BindingResult} interface,
* or with a {@link DataBinder} that in turn exposes a BindingResult via
* {@link org.springframework.validation.DataBinder#getBindingResult()}.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
* @see BindingResult
* @see DataBinder#getBindingResult()
* @see DataBinder#close()
*/
@SuppressWarnings("serial")
public class BindException extends Exception implements BindingResult {
private final BindingResult bindingResult;
/**
* Create a new BindException instance for a BindingResult.
* @param bindingResult the BindingResult instance to wrap
*/
public BindException(BindingResult bindingResult) {
Assert.notNull(bindingResult, "BindingResult must not be null");
this.bindingResult = bindingResult;
}
我們發(fā)現(xiàn)BindException實現(xiàn)了BindingResult接口(BindResult是綁定結(jié)果的通用接口, BindResult繼承于Errors接口),所以該異常類擁有BindingResult所有的相關(guān)信息,因此我們可以通過捕獲該異常類,對其錯誤結(jié)果進行分析和處理。代碼如下:
/**
* 對方法參數(shù)校驗異常處理方法
*/
@ExceptionHandler(BindException.class)
public ResponseEntity<Map<String, Object>> handlerNotValidException(BindException exception) {
log.debug("begin resolve argument exception");
BindingResult result = exception.getBindingResult();
Map<String, Object> maps;
if (result.hasErrors()) {
List<FieldError> fieldErrors = result.getFieldErrors();
maps = new HashMap<>(fieldErrors.size());
fieldErrors.forEach(error -> {
maps.put(error.getField(), error.getDefaultMessage());
});
} else {
maps = Collections.EMPTY_MAP;
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(maps);
}
這樣,我們對是content-type類型為form(表單)類型的請求的參數(shù)校驗的異常處理就解決了,對于MethodArgumentNotValidException異常不起作用的原因主要是因為跟請求發(fā)起的數(shù)據(jù)格式(content-type)有關(guān)系,對于不同的傳輸數(shù)據(jù)的格式spring采用不同的HttpMessageConverter(http參數(shù)轉(zhuǎn)換器)來進行處理.以下是對HttpMessageConverter進行簡介:
HTTP 請求和響應(yīng)的傳輸是字節(jié)流,意味著瀏覽器和服務(wù)器通過字節(jié)流進行通信。但是,使用 Spring,controller 類中的方法返回純 String 類型或其他 Java 內(nèi)建對象。如何將對象轉(zhuǎn)換成字節(jié)流進行傳輸?
在報文到達SpringMVC和從SpringMVC出去,都存在一個字節(jié)流到j(luò)ava對象的轉(zhuǎn)換問題。在SpringMVC中,它是由HttpMessageConverter來處理的。
當請求報文來到j(luò)ava中,它會被封裝成為一個ServletInputStream的輸入流,供我們讀取報文。響應(yīng)報文則是通過一個ServletOutputStream的輸出流,來輸出響應(yīng)報文。http請求與相應(yīng)的處理過程如下:
針對不同的數(shù)據(jù)格式,springmvc會采用不同的消息轉(zhuǎn)換器進行處理,以下是springmvc的內(nèi)置消息轉(zhuǎn)換器:
由此我們可以看出,當使用json作為傳輸格式時,springmvc會采用MappingJacksonHttpMessageConverter消息轉(zhuǎn)換器, 而且底層在對參數(shù)進行校驗錯誤時,拋出的是MethodArgumentNotValidException異常,因此我們需要對BindException和MethodArgumentNotValidException進行統(tǒng)一異常管理,最終代碼演示如下所示:
/**
* 對方法參數(shù)校驗異常處理方法(僅對于表單提交有效,對于以json格式提交將會失效)
* 如果是表單類型的提交,則spring會采用表單數(shù)據(jù)的處理類進行處理(進行參數(shù)校驗錯誤時會拋出BindException異常)
*/
@ExceptionHandler(BindException.class)
public ResponseEntity<Map<String, Object>> handlerBindException(BindException exception) {
return handlerNotValidException(exception);
}
/**
* 對方法參數(shù)校驗異常處理方法(前端提交的方式為json格式出現(xiàn)異常時會被該異常類處理)
* json格式提交時,spring會采用json數(shù)據(jù)的數(shù)據(jù)轉(zhuǎn)換器進行處理(進行參數(shù)校驗時錯誤是拋出MethodArgumentNotValidException異常)
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handlerArgumentNotValidException(MethodArgumentNotValidException exception) {
return handlerNotValidException(exception);
}
public ResponseEntity<Map<String, Object>> handlerNotValidException(Exception e) {
log.debug("begin resolve argument exception");
BindingResult result;
if (e instanceof BindException) {
BindException exception = (BindException) e;
result = exception.getBindingResult();
} else {
MethodArgumentNotValidException exception = (MethodArgumentNotValidException) e;
result = exception.getBindingResult();
}
Map<String, Object> maps;
if (result.hasErrors()) {
List<FieldError> fieldErrors = result.getFieldErrors();
maps = new HashMap<>(fieldErrors.size());
fieldErrors.forEach(error -> {
maps.put(error.getField(), error.getDefaultMessage());
});
} else {
maps = Collections.EMPTY_MAP;
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(maps);
}
這樣就完美解決了我們對參數(shù)校驗異常的統(tǒng)一處理。
在這里我僅僅是針對參數(shù)校驗的異常進行了統(tǒng)一處理,也就是返回給前端的響應(yīng)碼是400(參數(shù)格式錯誤),對于自定義異?;蛘咂渌漠惓6伎梢圆捎眠@種方式來對異常進行統(tǒng)一處理。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
淺析Java中Map與HashMap,Hashtable,HashSet的區(qū)別
HashMap和Hashtable兩個類都實現(xiàn)了Map接口,二者保存K-V對(key-value對);HashSet則實現(xiàn)了Set接口,性質(zhì)類似于集合2013-09-09
利用Java實現(xiàn)復(fù)制Excel工作表功能
這篇文章主要給大家介紹了關(guān)于如何利用Java實現(xiàn)復(fù)制Excel工作表功能的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
解決jhipster修改jdl生成的實體類報錯:liquibase.exception.ValidationFailed
這篇文章主要介紹了解決jhipster修改jdl生成的實體類報錯:liquibase.exception.ValidationFailedException: Validation Failed問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11
解決springboot與springcloud版本兼容問題(附版本兼容表)
在基于spring boot搭建spring cloud時,創(chuàng)建eureka后啟動服務(wù)發(fā)生報錯,本文給大家介紹了解決springboot與springcloud版本兼容問題的幾種方案,需要的朋友可以參考下2024-02-02
ActiveMQ消息隊列技術(shù)融合Spring過程解析
這篇文章主要介紹了ActiveMQ消息隊列技術(shù)融合Spring過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-11-11
關(guān)于@ApiImplicitParams、ApiImplicitParam的使用說明
這篇文章主要介紹了關(guān)于@ApiImplicitParams、ApiImplicitParam的使用說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
使用filter實現(xiàn)url級別內(nèi)存緩存示例
這篇文章主要介紹了使用filter實現(xiàn)url級別內(nèi)存緩存示例,只需要一個靜態(tài)類,在filter中調(diào)用,也可以全部寫到filt里面。可以根據(jù)查詢參數(shù)分別緩存,需要的朋友可以參考下2014-03-03

