1、spring提供了一套控制全局异常的注解配置,通过使用注解,可以给异常一个全局出口,使用@RestControllerAdvice或者@ControllerAdvice(@RestControllerAdvice相当于@ControllerAdvice和@ResponseBody的组合)
2、@ControllerAdvice:是controller的一个辅助类,最常用的就是作为全局异常处理的切面类,可以指定扫描范围
3、@ExceptionHandler:指定异常类的处理方式
5、示例:
@RestController
@RequestMapping("/api/hello")
public class HelloWorldController {
@PostMapping("/test1")
public BaseResult exceptionTest1() throws Exception {
throw new Exception("Exception。。。。。");
}
@PostMapping("/test2")
public BaseResult exceptionTest2() throws MyException {
throw new MyException("MyExcepton....");
}
}
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public BaseResult exceptionHandler(Exception ex){
log.info("1111111111111");
return BaseResult.failed("1000",ex.getMessage());
}
@ExceptionHandler(MyException.class)
public BaseResult myExceptionHandler(MyException ex){
log.info("2222222222222");
return BaseResult.failed("1000",ex.getMessage());
}
}
当访问/api/hello/test1接口时,因为接口抛出的是Exception,所以被exceptionHandler处理,返回"1111111111111"。
当访问/api/hello/test2接口时,因为接口抛出的是MyException,所以被myExceptionHandler处理,返回"2222222222222"。
6、@ControllerAdvice只是处理Controller层的异常,但是如果请求还没到controller层就抛异常了(比如404),这种情况下,使用@ControllerAdvice是没法处理的,下面看看spring全局异常处理的逻辑:
- 在spring中全局异常处理的核心是ErrorController接口,该接口中只提供了一个getErrorPath()方法
- AbstractErrorController实现了ErrorController接口
- 全局异常具体的处理映射逻辑都在BasicErrorController中,它继承了AbstractErrorController
- 在BasicErrorController中提供了errorHtml()和error()方法,前者响应的是一个错误页面,后者响应的是json数据
/**BasicErrorController核心代码*/
@RequestMapping(produces = "text/html")
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 ? new ModelAndView("error", model) : modelAndView);
}
如果返回的是一个错误页面,执行了resolveErrorView方法,该方法中调用了DefaultErrorViewResolver类的resolveErrorView方法
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
String errorViewName = "error/" + viewName;
//如果有可用的模板(在error文件夹下),则使用模板展示,
//如果没有可用的模板,则查找静态资源文件(static文件夹下)
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
for (String location : this.resourceProperties.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
经过上面的分析,如果我们想处理类似404场景的异常,就需要自定义自己的异常处理类。实现ErrorController接口,然后提供error()或者errorHtml()方法
@RestController
@Slf4j
public class MyBasicErrorController implements ErrorController {
@Autowired
private ErrorAttributes errorAttributes;
@Value("${server.error.path:${error.path:/error}}")
private static final String path_default = "/error";
@Override
public String getErrorPath() {
return path_default;
}
@RequestMapping(value = path_default ,produces = {MediaType.APPLICATION_JSON_VALUE})
public BaseResult error(HttpServletRequest request) {
RequestAttributes requestAttributes = new ServletRequestAttributes(request);
Map<String, Object> body = this.errorAttributes.getErrorAttributes(requestAttributes, true);
log.info("错误信息:{}",JSON.toJSON(body));
return BaseResult.failed(body.get("status").toString(),body.get("message").toString());
}
}
这样双管齐下,所有异常返回的数据都有一个统一的数据格式
ps:其实也可以直接继承抽象类AbstractErrorController,但是启动服务的时候总是报一个很奇怪的错误,有时间继续研究一下。