Spring异常处理框架
初识底层接口类HandlerExceptionResolver
- HandlerExceptionResolver 是一个处理 Web 程序发生异常时的接口,当异常发生时,需要进行捕获并返回一个友好的ModelAndView给请求用户,可以通过继承AbstractHandlerExceptionResolver来实现。这个接口返回 null 表示让其他异常处理器进行处理,这里由于异常处理链机制,如果不处理异常,就会由 Web 容器将异常返回给客户端(直接将可读性很差的异常信息返回给用户是不推荐的做法)。
接口如下
@Nullable
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
底层扩展抽象类AbstractHandlerExceptionResolver
- 当我们需要实现自定义的 HandlerExceptionResolver时,只要通过继承它的抽象类 AbstractHandlerExceptionResolver,覆写 doResolveException方法就可以了。
避免动不动扩展底层,而使用 @ExceptionHandler
- 如果觉得只是简单的处理一下指定异常类对应的ModelAndView,最好不要那么麻烦跑去实现HandlerExceptionResolver接口,或者是继承AbstractHandlerExceptionResolver重写doResolveException接口,毕竟这些算是相对比较底层的接口,动不动就重写底层类不是好习惯。那么好习惯是怎样的呢?
- 好习惯: ExceptionHandlerExceptionResolver类提供了一种 @ExceptionHandler注解,被这个注解过的返回可以处理指定异常的ModelAndView。
如下:
@RestController
public class RestApiController {
// 指定RestApiController的方法 遇到IllegalStateException,RemoteException时,返回 { data:null, message:"error msg", code: 400 } 给前端
@ExceptionHandler({ IllegalStateException.class, RemoteException.class })
public ModelAndView handleIllegalStateException(IllegalStateException ex) {
System.out.println("非法状态异常出现,需要处理 " + ex.getMessage());
ModelAndView modelAndView = new ModelAndView();
Map<String, String> maps = new HashMap<>();
maps.put("data", null);
maps.put("message", ex.getClass().getName());
maps.put("code", "400");
MappingJackson2JsonView mappingJackson2JsonView = new MappingJackson2JsonView();
mappingJackson2JsonView.setAttributesMap(maps);
modelAndView.setView(mappingJackson2JsonView);
return modelAndView;
}
}
- 【注意】:!!这样方式使用 @ExceptionHandler 存在一个缺陷,就是只会针对当前控制器下的异常处理,比如上面的方法只能处理RestApiController的异常。
@ExceptionHandler的救星 @ControllerAdvice
- @ExceptionHandler无法对全局异常有效,而加上@ControllerAdvice就可以了
示例代码:
@ControllerAdvice
public class NormalExceptionHandler {
@ExceptionHandler()
public ResponseEntity handleException(Exception e) {
System.out.println("NormalExceptionHandler handle exception");
return ResponseEntity.ok(new Result<>(400, e.getMessage(), null));
}
}
类似上面的代码,两个注解结合就可以实现全局handle了
可以通过 @ControllerAdvice(annotations = RestController.class)约定有效范围,免得动不动就全局
SpringBoot2.0挖的新坑 ErrorController
Springboot默认引入了一个异常处理控制器,他会根据前端请求得
请求时 Header 里 Accept 值是json还是html/text,来决定返回怎样的错误信息。
如下示例:
图片来源:https://juejin.im/post/5cdfe58051882525de0822a7
可以在 application.properties 设置 server.error.whitelabel.enabled 为 false 干掉这个坑。
ErrorViewResolver 与 ErrorController的关系
ErrorViewResolver是 AbstractErrorController 的成员, 用来解析异常Exception和请求HttpRequest用的,最终会返回ModelAndView给ErrorController,然后丢给前端。
HandlerExceptionResolver 与 ErrorController的关系
没关系。
因为先有Spring的HandlerExceptionResolver,后有SpirngBoot2.0的ErrorController。ErrorController没有用到任何Spring异常类,而是简单的用到了@RequestMapping({"KaTeX parse error: Expected '}', got 'EOF' at end of input: …ver.error.path:{error.path:/error}}"})注解来捕获异常。
他们是不同的两个流派,上面讲了半天都是与HandlerExceptionResolver有关的。
那么ErrorController流派如何处理异常呢?
参考示例如下:
package com.hehe.error;
@Controller@RequestMapping("${server.error.path:/error}")
public class GlobalErrorController implements ErrorController {
private final static String DEFAULT_ERROR_VIEW = "error";//错误信息页
/**
* 情况1:若预期返回类型为text/html,则返回错误信息页(View).
*/
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request) {
return new ModelAndView(DEFAULT_ERROR_VIEW, "errorInfo",null);
}
@Override
public String getErrorPath() {//获取映射路径
return "/error";
}
}
上面的代码看看就好,举个例子而已,不是很优雅。
总结:
SpirngBoot有两大 异常处理流派:
自己发明的ErrorController流
以及,Spring原生的 ExceptionHandler流