spring 中有三种方式能够处理异常 @ResponseStatus、 ResponseEntity、 HttpServletResponse,下面比较他们的优缺点:
HttpServletResponse
HttpServletResponse 是 javax.servlet 下的一个接口,如下使用:
1 2 3 4 5
| @RequestMapping(value = "/user", method = RequestMethod.GET) public void getUser(HttpServletResponse response) throws IOException{ response.setStatus(500); response.getWriter().append("server error"); }
|
这种方式可以很好的实现同时满足自定义反馈码+消息内容,一般的实现方式也都是这样。但是也有不太好的地方:
- 如果需要返回 json 格式数据还需要设置 response.setContentType(“application/json”);和 response.setCharacterEncoding(“UTF-8”);这样做有些多余,重复的工作太多,虽然可以进行封装。
- 最严重的问题是返回值必须是 void 类型,否则就会和 @ResponseBody 出现冲突,其次就是不能利用 @ResponseBody 自动封装 json 的特性,在 spring mvc 框架中如果在方法上加上@ResponseBody 是可以对返回值自动进行 json 封装的。
@ResponseStatus
@ResponseStatus 是一个注解,可以作用在方法和类上面,如下使用:
1 2 3 4 5
| @RequestMapping(value = "/user", method = RequestMethod.GET) @ResponseStatus(code=HttpStatus.INTERNAL_SERVER_ERROR,reason="server error") public String getUser(){ return "hello user"; }
|
启动程序后,返回值如下:
1 2 3 4 5 6
| Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sun Sep 08 23:36:04 CST 2019 There was an unexpected error (type=Internal Server Error, status=500). server error
|
getUser 方法中没有错误,结果还是出现了 500 错误,也就是说只要加了该注解,就一定会报错,可以用在自定义异常或全局捕获的异常处:
1 2 3 4
| @ResponseStatus(code=HttpStatus.INTERNAL_SERVER_ERROR,reason="111") public class ServerException extends Exception {
}
|
缺点如下:
- 无法返回一个 json 格式的自定义错误信息
- code 值和 reason 只能是写死的,不能动态变化,于是每种异常都要定义一个异常类,基本无法使用。
ResponseEntity
ResponseEntity 是 spring 中的一个类,直接上代码
1 2 3 4 5 6
| @RequestMapping(value = "/user", method = RequestMethod.GET) public ResponseEntity getUser() { Map<String,Object> map = new HashMap<String,Object>(); map.put("name", "qwer"); return new ResponseEntity(map, HttpStatus.OK); }
|
返回结果
可以看到自动将 map 转成了 json,于是可以在异常类中动态修改 http 状态码和自定义返回 json 了。比上方两种都要强大。
最佳实践
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
|
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class Result<E> implements Serializable {
private static final long serialVersionUID = 801303944859566772L;
private Integer code;
private String msg;
private E data;
public static <E> Result<E> success() { return Result.<E>builder().code(200).msg("调用成功").build(); }
public static <E> Result<E> successMsg(String msg) { return Result.<E>builder().code(200).msg(msg).build(); }
public static <E> Result<E> success(E data) { return Result.<E>builder().code(200).msg("调用成功").data(data).build(); }
public static <E> Result<E> success(String msg, E data) { return Result.<E>builder().code(200).msg(msg).data(data).build(); }
public static <E> Result<?> success(int code, E data) { return Result.<E>builder().data(data).code(code).msg("调用成功").build(); }
public static <E> Result<?> success(int code, String msg, E data) { return Result.<E>builder().data(data).code(code).msg(msg).build(); }
public static <E> Result<E> fail() { return Result.<E>builder().code(0).msg("调用失败").build(); }
public static <E> Result<E> failMsg(String msg) { return Result.<E>builder().code(0).msg(msg).build(); }
public static <E> Result<E> fail(E data) { return Result.<E>builder().code(0).msg("调用失败!").data(data).build(); }
public static <E> Result<?> fail(int code, String msg) { return Result.<E>builder().code(code).msg(msg).build(); }
public static <E> Result<?> fail(int code, String msg, E data) { return Result.<E>builder().data(data).code(code).msg(msg).build(); }
@Override public String toString() { return "Result{" + "code=" + code + ", msg='" + msg + '\'' + ", data=" + data + '}'; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public interface ResultCode { int BAD_REQUEST = 400;
int UNAUTHORIZED = 4001;
int TOLOGIN = 50014;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
@Data public class ServiceException extends RuntimeException {
private static final long serialVersionUID = 1L;
private Integer code;
private HttpStatus httpStatus;
public ServiceException() { super(); }
public ServiceException(String msg) { super(msg); }
public ServiceException(int resultCode, String msg) { super(msg); this.code = resultCode; }
public ServiceException(HttpStatus httpStatus, String msg) { super(msg); this.httpStatus = httpStatus; }
public ServiceException(HttpStatus httpStatus, int resultCode, String msg) { super(msg); this.httpStatus = httpStatus; this.code = resultCode; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @Slf4j @RestControllerAdvice public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class) public ResponseEntity<Result> handleException(Exception e){ log.error("", e); return new ResponseEntity<>(Result.fail(ResultCode.BAD_REQUEST,"服务器异常,请稍后重试"), HttpStatus.BAD_REQUEST); }
@ExceptionHandler(AccessDeniedException.class) public ResponseEntity<Result> handleAccessDeniedException(AccessDeniedException e){ log.error("无权访问:{}", e.getMessage()); return new ResponseEntity<>(Result.fail(ResultCode.UNAUTHORIZED, "无权访问"), HttpStatus.UNAUTHORIZED); }
@ExceptionHandler(value = ServiceException.class) public ResponseEntity<Result> serviceException(ServiceException e) { log.error(e.getMessage()); Result result = Result.fail(); if(Objects.nonNull(e.getCode())) { result.setCode(e.getCode()); } if(Objects.nonNull(e.getMessage())) { result.setMsg(e.getMessage()); } if(Objects.nonNull(e.getHttpStatus())) { return new ResponseEntity<>(result, e.getHttpStatus()); } return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); } }
|
这样就既能自定义 http 状态码,也能灵活改变业务状态码,并实现了返回值都是 json,这样前端处理更加友好。使用实例:
1 2 3 4
| @RequestMapping(value = "/user", method = RequestMethod.GET) public ResponseEntity getUser() { throw new ServiceException(HttpStatus.UNAUTHORIZED, ResultCode.TOLOGIN, "未授权,请联系管理员"); }
|
filter 中的异常无法被 @ControllerAdvice 捕获的问题
filter 不属于 spring 的一部分,并且也不在 controller 中,所以无法被@ControllerAdvice 捕获,一个小技巧是在 filter 中 try catch,捕获到异常后,通过 HttpServletRequest 将请求转发到一个专门的异常处理 controller 中,在这个 controller 中抛出异常。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Component public class MyFilter extends OncePerRequestFilter {
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { try { throw new RuntimeException(); chain.doFilter(request, response); } catch (Exception e) { request.setAttribute(JwtAuthFilter.class.getSimpleName(), e); request.getRequestDispatcher("/filterExceptionHandler").forward(request, response); } }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Controller public class ExceptionController {
@RequestMapping("/filterExceptionHandler") public void filterExceptionHandler(HttpServletRequest request) throws Throwable { Exception e = (Exception) request.getAttribute(JwtAuthFilter.class.getSimpleName()); if (e != null) { throw e; } } }
|