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");
}

这种方式可以很好的实现同时满足自定义反馈码+消息内容,一般的实现方式也都是这样。但是也有不太好的地方:

  1. 如果需要返回 json 格式数据还需要设置 response.setContentType(“application/json”);和 response.setCharacterEncoding(“UTF-8”);这样做有些多余,重复的工作太多,虽然可以进行封装。
  2. 最严重的问题是返回值必须是 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);
}

返回结果

1
2
3
{
"name": "qwer"
}

可以看到自动将 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
// Result.java
// 统一的返回数据封装
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Result<E> implements Serializable {

/**
* 序列化
*/
private static final long serialVersionUID = 801303944859566772L;

/**
* 操作结果的状态码,200为成功,其余失败
*/
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
// ResultCode.java
// 业务代码的code值
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
//ServiceException.java
//自定义业务异常
@Data
public class ServiceException extends RuntimeException {

private static final long serialVersionUID = 1L;

/**
* 业务代码code
*/
private Integer code;

/**
* http协议的status
*/
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
// GlobalExceptionHandler.java
@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);
}

/**
* 处理 接口无权访问异常AccessDeniedException
*/
@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
// MyFilter.java
@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的一个属性
request.setAttribute(JwtAuthFilter.class.getSimpleName(), e);
// 过滤器的异常不能被RestControllerAdvice捕获到,跳转到专门的异常controller
request.getRequestDispatcher("/filterExceptionHandler").forward(request, response);
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ExceptionController.java
@Controller
public class ExceptionController {
/**
* 直接throw Filter 传过来的异常,让 ControllerAdvice 处理
*/
@RequestMapping("/filterExceptionHandler")
public void filterExceptionHandler(HttpServletRequest request) throws Throwable {
Exception e = (Exception) request.getAttribute(JwtAuthFilter.class.getSimpleName());
if (e != null) {
throw e;
}
}
}