springboot异常处理以及页面返回定制数据

异常处理

@ControllerAdvice

使得被注解的类变成切面类,所有经过controller的方法的异常都会被他捕获。

@ExceptionHandler

在@ControllerAdvice注解下的类,里面的方法用@ExceptionHandler注解修饰的方法,会将对应的异常交给对应的方法处理。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ControllerAdvice
@ResponseBody
public class MyExpectionHandler {

@ResponseStatus(HttpStatus.OK)
@ExceptionHandler
public Map<String,Object> hanlderException(UserNotExist exception){
Map<String,Object> map=new HashMap<>();
map.put("RespCode","XX");
map.put("RespDesc","用户不存在");

return map;
}

}

页面定制返回数据

使用@ControllerAdvice没有自定义效果,浏览器和客户端都会返回为JSON数据,只是适用于前后端分离。

SpringBoot的ErrorMvcAutoConfiguration中定义了一个BasicErrorController用来处理自适应返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {

@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = getStatus(request);
Map<String, Object> model = Collections
.unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}

@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
return new ResponseEntity<>(body, status);
}

}

查看源码可以看到BasicErrorController如果不配置错误的路由映射的话会默认映射/error,并且针对浏览器和其他客户端进行自适应返回,所以如果我们需要自适应,只需要转发到error即可。

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
@ControllerAdvice
public class MyExpectionHandler {

@ExceptionHandler
public String hanlderException(UserNotExist exception){
Map<String,Object> map=new HashMap<>();
map.put("RespCode","XX");
map.put("RespDesc","用户不存在");

return "forward:/error";
}

}

但是这样返回的数据只有默认的,并没有自定义的效果.而且可以看到状态码为200。

1
2
3
4
5
6
7
{
"timestamp": "2019-09-23T02:07:39.938+0000",
"status": 200,
"error": "OK",
"message": "用户不存在",
"path": "/emp/1123123"
}

这是因为在BasicErrorController的解析视图逻辑被过滤掉了

从BasicErrorController的getStatus方法中可以看到,status是从请求域中得到的。

1
2
3
4
5
6
7
8
9
10
11
12
protected HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
try {
return HttpStatus.valueOf(statusCode);
}
catch (Exception ex) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}

所以我们只需要在request中加入对应属性的错误码即可。

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ControllerAdvice
public class MyExpectionHandler {

@ExceptionHandler
public String hanlderException(UserNotExist exception, HttpServletRequest request){
request.setAttribute("javax.servlet.error.status_code",400);
Map<String,Object> map=new HashMap<>();
map.put("RespCode","XX");
map.put("RespDesc","用户不存在");

return "forward:/error";
}

}

这样返回的数据就是自适应的,并且可以响应错误定制化页面即4XX 5XX页面。

1
2
3
4
5
6
7
{
"timestamp": "2019-09-23T02:15:38.047+0000",
"status": 400,
"error": "Bad Request",
"message": "用户不存在",
"path": "/emp/1123123"
}

定制自适应的返回数据

配置errorAttributes

我们可以看到上述中,springboot的自适应返回数据,都是调用getErrorAttributes来获取

1
2
3
4
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}

最后可以追溯到ErrorMvcAutoConfiguration下的DefaultErrorAttributes的getErrorAttributes方法。

1
2
3
4
5
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}
1
2
3
4
5
6
7
8
9
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}

这里就是默认返回的错误数据。

所以我们只需要替换掉容器中的DefaultErrorAttributes即可

1
2
3
4
5
6
7
8
9
10
11
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);

errorAttributes.put("company","sun");

return errorAttributes;
}
}

这样返回来的数据,出了原来的默认的数据之外,还添加了company的值。

1
2
3
4
5
6
7
8
{
"timestamp": "2019-09-23T06:29:09.074+0000",
"status": 400,
"error": "Bad Request",
"message": "用户不存在",
"path": "/emp/1123123",
"company": "sun"
}

将异常处理的数据也整合到自定义数据中

因为自定义的数据,最终是从request中得到的,所以我们只需要将异常数据存入请求域中即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@ControllerAdvice
public class MyExpectionHandler {

@ExceptionHandler
public String hanlderException(UserNotExist exception, HttpServletRequest request){
request.setAttribute("javax.servlet.error.status_code",400);
Map<String,Object> map=new HashMap<>();
map.put("RespCode","XX");
map.put("RespDesc","用户不存在");

request.setAttribute("expection",map);

return "forward:/error";
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);

errorAttributes.put("company","sun");
Map<String, Object> expection = (Map<String, Object>) webRequest.getAttribute("expection", 0);
errorAttributes.put("expection",expection);

return errorAttributes;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
{
"timestamp": "2019-09-23T06:37:15.044+0000",
"status": 400,
"error": "Bad Request",
"message": "用户不存在",
"path": "/emp/1123123",
"company": "sun",
"expection": {
"RespCode": "XX",
"RespDesc": "用户不存在"
}
}