Spring MVC Controller返回值及異常的統(tǒng)一處理方法
舊的設(shè)計(jì)方案
開發(fā)api的時候,需要先定義好接口的數(shù)據(jù)響應(yīng)結(jié)果.如下是一個很簡單直接的Controller實(shí)現(xiàn)方法及響應(yīng)結(jié)果定義.
@RestController
@RequestMapping("/users")
public class UserController {
@Inject
private UserService userService;
@GetRequest("/{userId:\\d+}")
public ResponseBean signin(@PathVariable long userId) {
try {
User user = userService.getUserBaseInfo(userId);
return ResponseBean.success(user);
} catch (ServiceException e) {
return new ReponseBean(e.getCode(), e.getMsg());
} catch (Exception e) {
return ResponseBean.systemError();
}
}
}
{
code: "",
data: {}, // 可以是對象或者數(shù)組
msg: ""
}
從上面的代碼,我們可以看到對于每個 Controller 方法,都會有很多重復(fù)的代碼出現(xiàn),我們應(yīng)該設(shè)法去避免重復(fù)的代碼。將重復(fù)的代碼移除之后,可以得到如下的代碼,簡單易懂。
@RestController
@RequestMapping("/users")
public class UserController {
@Inject
private UserService userService;
@GetRequest("/{userId:\\d+}")
public User signin(@PathVariable long userId) {
return userService.getUserBaseInfo(userId);
}
}
在以上的實(shí)現(xiàn)中,還做了一個必要的要求,就是 ServiceException 需要定義為 RuntimeException的子類,而不是 Exception的子類。由于 ServiceException 表示服務(wù)異常,一般發(fā)生這種異常是應(yīng)該直接提示前端,而無需進(jìn)行其他特殊處理的。在定義為 RuntimeException 的子類之后,會減少大量的異常拋出聲明,而且不再需要在事務(wù)@Transactional 中進(jìn)行特殊聲明。
統(tǒng)一 Controller 返回值格式
在開發(fā)的過程中,我發(fā)現(xiàn)上面的結(jié)構(gòu)
@ControllerAdvice
public class ControllerResponseHandler implements ResponseBodyAdvice<Object> {
private Logger logger = LogManager.getLogger(getClass());
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 支持所有的返回值類型
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
if(body instanceof ResponseBean) {
return body;
} else {
// 所有沒有返回 ResponseBean 結(jié)構(gòu)的結(jié)果均認(rèn)為是成功的
return ResponseBean.success(body);
}
}
}
統(tǒng)一異常處理
如下的代碼中,ServiceException ServiceMessageException ValidatorErrorType FieldValidatorError 均為自定義類。
@ControllerAdvice
public class ControllerExceptionHandler {
private Logger logger = LogManager.getLogger(getClass());
private static final String logExceptionFormat = "[EXIGENCE] Some thing wrong with the system: %s";
/**
* 自定義異常
*/
@ExceptionHandler(ServiceMessageException.class)
public ResponseBean handleServiceMessageException(HttpServletRequest request, ServiceMessageException ex) {
logger.debug(ex);
return new ResponseBean(ex.getMsgCode(), ex.getMessage());
}
/**
* 自定義異常
*/
@ExceptionHandler(ServiceException.class)
public ResponseBean handleServiceException(HttpServletRequest request, ServiceException ex) {
logger.debug(ex);
String message = codeToMessage(ex.getMsgCode());
return new ResponseBean(ex.getMsgCode(), message);
}
/**
* MethodArgumentNotValidException: 實(shí)體類屬性校驗(yàn)不通過
* 如: listUsersValid(@RequestBody @Valid UserFilterOption option)
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseBean handleMethodArgumentNotValid(HttpServletRequest request, MethodArgumentNotValidException ex) {
logger.debug(ex);
return validatorErrors(ex.getBindingResult());
}
private ResponseBean validatorErrors(BindingResult result) {
List<FieldValidatorError> errors = new ArrayList<FieldValidatorError>();
for (FieldError error : result.getFieldErrors()) {
errors.add(toFieldValidatorError(error));
}
return ResponseBean.validatorError(errors);
}
/**
* ConstraintViolationException: 直接對方法參數(shù)進(jìn)行校驗(yàn),校驗(yàn)不通過。
* 如: pageUsers(@RequestParam @Min(1)int pageIndex, @RequestParam @Max(100)int pageSize)
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResponseBean handleConstraintViolationException(HttpServletRequest request,
ConstraintViolationException ex) {
logger.debug(ex);
//
List<FieldValidatorError> errors = new ArrayList<FieldValidatorError>();
for (ConstraintViolation<?> violation : ex.getConstraintViolations()) {
errors.add(toFieldValidatorError(violation));
}
return ResponseBean.validatorError(errors);
}
private FieldValidatorError toFieldValidatorError(ConstraintViolation<?> violation) {
Path.Node lastNode = null;
for (Path.Node node : violation.getPropertyPath()) {
lastNode = node;
}
FieldValidatorError fieldNotValidError = new FieldValidatorError();
// fieldNotValidError.setType(ValidatorTypeMapping.toType(violation.getConstraintDescriptor().getAnnotation().annotationType()));
fieldNotValidError.setType(ValidatorErrorType.INVALID.value());
fieldNotValidError.setField(lastNode.getName());
fieldNotValidError.setMessage(violation.getMessage());
return fieldNotValidError;
}
private FieldValidatorError toFieldValidatorError(FieldError error) {
FieldValidatorError fieldNotValidError = new FieldValidatorError();
fieldNotValidError.setType(ValidatorErrorType.INVALID.value());
fieldNotValidError.setField(error.getField());
fieldNotValidError.setMessage(error.getDefaultMessage());
return fieldNotValidError;
}
/**
* BindException: 數(shù)據(jù)綁定異常,效果與MethodArgumentNotValidException類似,為MethodArgumentNotValidException的父類
*/
@ExceptionHandler(BindException.class)
public ResponseBean handleBindException(HttpServletRequest request, BindException ex) {
logger.debug(ex);
return validatorErrors(ex.getBindingResult());
}
/**
* 返回值類型轉(zhuǎn)化錯誤
*/
@ExceptionHandler(HttpMessageConversionException.class)
public ResponseBean exceptionHandle(HttpServletRequest request,
HttpMessageConversionException ex) {
return internalServiceError(ex);
}
/**
* 對應(yīng) Http 請求頭的 accept
* 客戶器端希望接受的類型和服務(wù)器端返回類型不一致。
* 這里雖然設(shè)置了攔截,但是并沒有起到作用。需要通過http請求的流程來進(jìn)一步確定原因。
*/
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public ResponseBean handleHttpMediaTypeNotAcceptableException(HttpServletRequest request,
HttpMediaTypeNotAcceptableException ex) {
logger.debug(ex);
StringBuilder messageBuilder = new StringBuilder().append("The media type is not acceptable.")
.append(" Acceptable media types are ");
ex.getSupportedMediaTypes().forEach(t -> messageBuilder.append(t + ", "));
String message = messageBuilder.substring(0, messageBuilder.length() - 2);
return new ResponseBean(HttpStatus.NOT_ACCEPTABLE.value(), message);
}
/**
* 對應(yīng)請求頭的 content-type
* 客戶端發(fā)送的數(shù)據(jù)類型和服務(wù)器端希望接收到的數(shù)據(jù)不一致
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public ResponseBean handleHttpMediaTypeNotSupportedException(HttpServletRequest request,
HttpMediaTypeNotSupportedException ex) {
logger.debug(ex);
StringBuilder messageBuilder = new StringBuilder().append(ex.getContentType())
.append(" media type is not supported.").append(" Supported media types are ");
ex.getSupportedMediaTypes().forEach(t -> messageBuilder.append(t + ", "));
String message = messageBuilder.substring(0, messageBuilder.length() - 2);
System.out.println(message);
return new ResponseBean(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), message);
}
/**
* 前端發(fā)送過來的數(shù)據(jù)無法被正常處理
* 比如后天希望收到的是一個json的數(shù)據(jù),但是前端發(fā)送過來的卻是xml格式的數(shù)據(jù)或者是一個錯誤的json格式數(shù)據(jù)
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseBean handlerHttpMessageNotReadableException(HttpServletRequest request,
HttpMessageNotReadableException ex) {
logger.debug(ex);
String message = "Problems parsing JSON";
return new ResponseBean(HttpStatus.BAD_REQUEST.value(), message);
}
/**
* 將返回的結(jié)果轉(zhuǎn)化到響應(yīng)的數(shù)據(jù)時候?qū)е碌膯栴}。
* 當(dāng)使用json作為結(jié)果格式時,可能導(dǎo)致的原因?yàn)樾蛄谢e誤。
* 目前知道,如果返回一個沒有屬性的對象作為結(jié)果時,會導(dǎo)致該異常。
*/
@ExceptionHandler(HttpMessageNotWritableException.class)
public ResponseBean handlerHttpMessageNotWritableException(HttpServletRequest request,
HttpMessageNotWritableException ex) {
return internalServiceError(ex);
}
/**
* 請求方法不支持
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseBean exceptionHandle(HttpServletRequest request, HttpRequestMethodNotSupportedException ex) {
logger.debug(ex);
StringBuilder messageBuilder = new StringBuilder().append(ex.getMethod())
.append(" method is not supported for this request.").append(" Supported methods are ");
ex.getSupportedHttpMethods().forEach(m -> messageBuilder.append(m + ","));
String message = messageBuilder.substring(0, messageBuilder.length() - 2);
return new ResponseBean(HttpStatus.METHOD_NOT_ALLOWED.value(), message);
}
/**
* 參數(shù)類型不匹配
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseBean methodArgumentTypeMismatchExceptionHandler(HttpServletRequest request,
MethodArgumentTypeMismatchException ex) {
logger.debug(ex);
String message = "The parameter '" + ex.getName() + "' should of type '"
+ ex.getRequiredType().getSimpleName().toLowerCase() + "'";
FieldValidatorError fieldNotValidError = new FieldValidatorError();
fieldNotValidError.setType(ValidatorErrorType.TYPE_MISMATCH.value());
fieldNotValidError.setField(ex.getName());
fieldNotValidError.setMessage(message);
return ResponseBean.validatorError(Arrays.asList(fieldNotValidError));
}
/**
* 缺少必填字段
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseBean exceptionHandle(HttpServletRequest request,
MissingServletRequestParameterException ex) {
logger.debug(ex);
String message = "Required parameter '" + ex.getParameterName() + "' is not present";
FieldValidatorError fieldNotValidError = new FieldValidatorError();
fieldNotValidError.setType(ValidatorErrorType.MISSING_FIELD.value());
fieldNotValidError.setField(ex.getParameterName());
fieldNotValidError.setMessage(message);
return ResponseBean.validatorError(Arrays.asList(fieldNotValidError));
}
/**
* 文件上傳時,缺少 file 字段
*/
@ExceptionHandler(MissingServletRequestPartException.class)
public ResponseBean exceptionHandle(HttpServletRequest request, MissingServletRequestPartException ex) {
logger.debug(ex);
return new ResponseBean(HttpStatus.BAD_REQUEST.value(), ex.getMessage());
}
/**
* 請求路徑不存在
*/
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseBean exceptionHandle(HttpServletRequest request, NoHandlerFoundException ex) {
logger.debug(ex);
String message = "No resource found for " + ex.getHttpMethod() + " " + ex.getRequestURL();
return new ResponseBean(HttpStatus.NOT_FOUND.value(), message);
}
/**
* 缺少路徑參數(shù)
* Controller方法中定義了 @PathVariable(required=true) 的參數(shù),但是卻沒有在url中提供
*/
@ExceptionHandler(MissingPathVariableException.class)
public ResponseBean exceptionHandle(HttpServletRequest request, MissingPathVariableException ex) {
return internalServiceError(ex);
}
/**
* 其他所有的異常
*/
@ExceptionHandler()
public ResponseBean handleAll(HttpServletRequest request, Exception ex) {
return internalServiceError(ex);
}
private String codeToMessage(int code) {
//TODO 這個需要進(jìn)行自定,每個 code 會匹配到一個相應(yīng)的 msg
return "The code is " + code;
}
private ResponseBean internalServiceError(Exception ex) {
logException(ex);
// do something else
return ResponseBean.systemError();
}
private <T extends Throwable> void logException(T e) {
logger.error(String.format(logExceptionFormat, e.getMessage()), e);
}
}
通過上面的配置,可以有效地將異常進(jìn)行統(tǒng)一的處理,同時對返回的結(jié)果進(jìn)行統(tǒng)一的封裝。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。
相關(guān)文章
關(guān)于Java集合框架Collection接口詳解
這篇文章主要介紹了關(guān)于Java集合框架Collection接口詳解,Collection接口是Java集合框架中的基礎(chǔ)接口,定義了一些基本的集合操作,包括添加元素、刪除元素、遍歷集合等,需要的朋友可以參考下2023-05-05
Spring boot啟動流程之解決循環(huán)依賴的方法
循環(huán)依賴,指的是兩個bean之間相互依賴,形成了一個循環(huán),spring解決循環(huán)依賴的方式是在bean的實(shí)例化完成之后,所以不要在構(gòu)造方法中引入循環(huán)依賴,因?yàn)檫@時對象還沒有實(shí)例化,spring也無法解決,本文給大家介紹Spring boot循環(huán)依賴的解決方法,一起看看吧2024-02-02
使用c3p0連接數(shù)據(jù)庫實(shí)現(xiàn)增刪改查
這篇文章主要為大家詳細(xì)介紹了使用c3p0連接數(shù)據(jù)庫實(shí)現(xiàn)增刪改查,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-08-08
java Callable接口和Future接口創(chuàng)建線程示例詳解
這篇文章主要為大家介紹了java Callable接口和Future接口創(chuàng)建線程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
java?ThreadPoolExecutor線程池內(nèi)部處理流程解析
這篇文章主要為大家介紹了java?ThreadPoolExecutor線程池內(nèi)部處理流程解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
Java中l(wèi)ist集合為空或?yàn)閚ull的區(qū)別說明
這篇文章主要介紹了Java中l(wèi)ist集合為空或?yàn)閚ull的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11

