实际项目开发中,程序往往会发生各式各样的异常情况,特别是身为服务端开发人员的我们,总是不停的编写接口提供给前端调用,分工协作的情况下,避免不了异常的发生,如果直接将错误的信息直接暴露给用户,这样的体验可想而知,且对黑客而言,详细异常信息往往会提供非常大的帮助…
一个简单的异常请求的接口
@GetMapping("/test")
public String test() {
// TODO 这里只是模拟异常,假设业务处理的时候出现错误了,或者空指针了等等...
int i = 10/0;
return "test1";
}
通过swagger调用:
{
"timestamp": "2019-01-28T06:38:42.517+0000",
"status": 500,
"error": "Internal Server Error",
"message": "/ by zero",
"path": "/order/test"
}
如果这接口是给第三方调用或者是自己公司的系统,看到这种错误估计得暴走吧….
在应用开发过程中,除系统自身的异常外,不同业务场景中用到的异常也不一样,为了与标题 轻松搞定全局异常 更加的贴切,定义个自己的异常,看看如何捕获…
/**
* 自定义异常
*/
public class BusinessErrorException extends RuntimeException {
private int code;
public BusinessErrorException() {
super();
}
public BusinessErrorException(ErrorCodeEnum errorCodeEnum) {
super(errorCodeEnum.getMsg());
this.setCode(errorCodeEnum.getCode());
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
定义返回的异常信息的格式,这样异常信息风格更为统一。
public class ErrorResponseEntity {
private int code;
private String message;
public ErrorResponseEntity(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
仔细一看是不是和平时正常写的代码没啥区别,不要急,接着看….
@GetMapping("/test")
public String test(@RequestParam int num) {
if(num < 0) {
throw new BusinessErrorException(ErrorCodeEnum.MY_TEST_EXCEPTION);
}
int i = 10/num;
return "result" + i;
}
注解概述
创建一个 GlobalExceptionHandler 类,并添加上 @RestControllerAdvice 注解就可以定义出异常通知类了,然后在定义的方法中添加上 @ExceptionHandler 即可实现异常的捕捉…
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
/**
* 定义要捕获的异常 可以多个 @ExceptionHandler({})
*
* @param request request
* @param e exception
* @param response response
* @return 响应结果
*/
@ExceptionHandler(BusinessErrorException.class)
public ErrorResponseEntity customExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
response.setStatus(HttpStatus.BAD_REQUEST.value());
BusinessErrorException exception = (BusinessErrorException) e;
return new ErrorResponseEntity(exception.getCode(), exception.getMessage());
}
/**
* 捕获 RuntimeException 异常
* TODO 如果你觉得在一个 exceptionHandler 通过 if (e instanceof xxxException) 太麻烦
* TODO 那么你还可以自己写多个不同的 exceptionHandler 处理不同异常
*
* @param request request
* @param e exception
* @param response response
* @return 响应结果
*/
@ExceptionHandler(RuntimeException.class)
public ErrorResponseEntity runtimeExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {
response.setStatus(HttpStatus.BAD_REQUEST.value());
RuntimeException exception = (RuntimeException) e;
return new ErrorResponseEntity(400, exception.getMessage());
}
/**
* 通用的接口映射异常处理方
* 当上面的注解都不能够命中异常时 会进入当前处理类
*/
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers,
HttpStatus status, WebRequest request) {
if (ex instanceof MethodArgumentNotValidException) {
MethodArgumentNotValidException exception = (MethodArgumentNotValidException) ex;
return new ResponseEntity<>(new ErrorResponseEntity(status.value(), exception.getBindingResult().getAllErrors().get(0).getDefaultMessage()), status);
}
if (ex instanceof MethodArgumentTypeMismatchException) {
MethodArgumentTypeMismatchException exception = (MethodArgumentTypeMismatchException) ex;
logger.error("参数转换失败,方法:" + exception.getParameter().getMethod().getName() + ",参数:" + exception.getName()
+ ",信息:" + exception.getLocalizedMessage());
return new ResponseEntity<>(new ErrorResponseEntity(status.value(), "参数转换失败"), status);
}
return new ResponseEntity<>(new ErrorResponseEntity(status.value(), "系统内部异常"), status);
}
}
除了上面那种方式我们还可以引入第三方的jar包。为我们封装好了返回的形式。也提供了特定的方法帮助我们进行组装。
在 pom.xml 中添加上 spring-boot-starter-web 的依赖即可。
<dependency>
<groupId>org.zalandogroupId>
<artifactId>problem-spring-webartifactId>
<version>0.22.1version>
dependency>
这里要继承AbstractThrowableProblem,这是第三方jar提供的异常模板类。
/**
* 自定义异常
*/
public class BusinessErrorException extends AbstractThrowableProblem {
private final String errorCode;
private final String errorMessage;
public BusinessErrorException(ErrorCodeEnum errorCodeEnum) {
this(ErrorConstants.DEFAULT_TYPE, errorCodeEnum.getCode(), errorCodeEnum.getMsg());
}
public BusinessErrorException(String msg) {
this(ErrorConstants.DEFAULT_TYPE, "99999", msg);
}
public BusinessErrorException(URI type, String errorCode, String errorMessage) {
super(type, errorMessage, Status.BAD_REQUEST, null, null, null, getErrorParameters(errorCode, errorMessage));
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
public String getErrorCode() {
return errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
private static Map<String, Object> getErrorParameters(String errorCode, String errorMessage) {
Map<String, Object> parameters = new HashMap<>();
parameters.put("errorCode", errorCode);
parameters.put("errorMessage", errorMessage);
return parameters;
}
}
异常转换的类中,我们处理到异常之后返回的都是Problem类,这个也是第三方jar提供的返回前端的异常模板类。具体我们定义的异常信息放到了响应的header中。前端可以判断status并从header中返回异常信息。
@ControllerAdvice
public class ExceptionTranslator implements ProblemHandling {
private final Logger log = LoggerFactory.getLogger(ExceptionTranslator.class);
/**
* Post-process Problem payload to add the message key for front-end if needed
*/
@Override
public ResponseEntity<Problem> process(@Nullable ResponseEntity<Problem> entity, NativeWebRequest request) {
if (entity == null || entity.getBody() == null) {
return entity;
}
Problem problem = entity.getBody();
if (!(problem instanceof ConstraintViolationProblem || problem instanceof DefaultProblem)) {
return entity;
}
ProblemBuilder builder = Problem.builder()
.withType(Problem.DEFAULT_TYPE.equals(problem.getType()) ? ErrorConstants.DEFAULT_TYPE : problem.getType())
.withStatus(problem.getStatus())
.withTitle(problem.getTitle())
.with("path", request.getNativeRequest(HttpServletRequest.class).getRequestURI());
if (problem instanceof ConstraintViolationProblem) {
builder
.with("violations", ((ConstraintViolationProblem) problem).getViolations())
.with("message", ErrorConstants.ERR_VALIDATION);
return new ResponseEntity<>(builder.build(), entity.getHeaders(), entity.getStatusCode());
} else {
builder
.withCause(((DefaultProblem) problem).getCause())
.withDetail(problem.getDetail())
.withInstance(problem.getInstance());
problem.getParameters().forEach(builder::with);
if (!problem.getParameters().containsKey("message") && problem.getStatus() != null) {
builder.with("message", "error.http." + problem.getStatus().getStatusCode());
}
return new ResponseEntity<>(builder.build(), entity.getHeaders(), entity.getStatusCode());
}
}
@Override
public ResponseEntity<Problem> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, @Nonnull NativeWebRequest request) {
BindingResult result = ex.getBindingResult();
List<FieldErrorVM> fieldErrors = result.getFieldErrors().stream()
.map(f -> new FieldErrorVM(f.getObjectName(), f.getField(), f.getCode()))
.collect(Collectors.toList());
Problem problem = Problem.builder()
.withType(ErrorConstants.CONSTRAINT_VIOLATION_TYPE)
.withTitle("Method argument not valid")
.withStatus(defaultConstraintViolationStatus())
.with("message", ErrorConstants.ERR_VALIDATION)
.with("fieldErrors", fieldErrors)
.build();
return create(ex, problem, request);
}
@ExceptionHandler(BadRequestAlertException.class)
public ResponseEntity<Problem> handleBadRequestAlertException(BadRequestAlertException ex, NativeWebRequest request) {
return create(ex, request, HeaderUtil.createFailureAlert(ex.getEntityName(), ex.getErrorKey(), ex.getMessage()));
}
@ExceptionHandler(BusinessErrorException.class)
public ResponseEntity<Problem> handleBusinessErrorException(BusinessErrorException ex, NativeWebRequest request) {
return create(ex, request, HeaderUtil.createBusinessFailureAlert(ex.getErrorCode(), ex.getErrorMessage()));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Problem> handleException(Exception ex, NativeWebRequest request) {
log.warn("resource 层抛出异常,Exception=",ex);
if (ex instanceof BusinessErrorException) {
BusinessErrorException businessErrorException = (BusinessErrorException) ex;
return create(businessErrorException, request, HeaderUtil.createBusinessFailureAlert(businessErrorException.getErrorCode(), businessErrorException.getErrorMessage()));
}else if(ex instanceof MethodArgumentNotValidException){
return handleMethodArgumentNotValid((MethodArgumentNotValidException)ex,request);
} else {
BusinessErrorException businessErrorExceptionOther = new BusinessErrorException(ErrorCodeEnum.SYSTEM_RPC_EXCEPTION);
return create(businessErrorExceptionOther, request, HeaderUtil.createBusinessFailureAlert(businessErrorExceptionOther.getErrorCode(), businessErrorExceptionOther.getErrorMessage()));
}
}
@ExceptionHandler(ConcurrencyFailureException.class)
public ResponseEntity<Problem> handleConcurrencyFailure(ConcurrencyFailureException ex, NativeWebRequest request) {
Problem problem = Problem.builder()
.withStatus(Status.CONFLICT)
.with("message", ErrorConstants.ERR_CONCURRENCY_FAILURE)
.build();
return create(ex, problem, request);
}
}
两种处理异常方式大同小异,以这种方式统处理异常能方便我们的开发,工作当中也是使用这种方式。