web异常可以分为2种
一是在控制器处理中产生的异常;二是在请求过滤器中等等。
在控制器处理中产生的异常:
在这类异常中,我们可以在Controller中定义异常处理器,@ExceptionHandler来处理当类中产生的异常。
static class Controller1 {
public void foo() {
}
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(ArithmeticException e) {
return Map.of("error", e.getMessage());
}
}
最后被转化中异常处理器,来处理本类中的异常。
1.同时也采用了责任链模式,它会组合所有的异常处理器,以此判断是否可以处理。(摘自源码)
@Nullable
private HandlerMethodArgumentResolverComposite argumentResolvers;
2.同时也采用了缓存技术,加载后的异常处理器会存在于缓存中(摘自源码)
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap(64);
private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap();
3.其也对嵌套异常进行了处理(摘自源码)
for(Object exToExpose = exception; exToExpose != null; exToExpose = cause != exToExpose ? cause : null) {
exceptions.add(exToExpose);
cause = ((Throwable)exToExpose).getCause();
}
Object[] arguments = new Object[exceptions.size() + 1];
exceptions.toArray(arguments);
arguments[arguments.length - 1] = handlerMethod;
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
他会取出所有异常,包括当前异常的产生异常,就可以判断嵌套异常。
如果是在@ControllerAdvice中定义,它就是全局异常处理器,可以处理所有控制器异常。
@ControllerAdvice
static class MyControllerAdvice {
@ExceptionHandler
@ResponseBody
public Map<String, Object> handle(Exception e) {
return Map.of("error", e.getMessage());
}
}
对于全局处理器和单类异常处理器的优先级:
肯定是优先使用当前异常产生类中的异常处理器咯
tomcat异常处理:
如果是springmvc没有进行异常处理的或者是过滤器或者拦击器中的异常就会交由tomcat来处理。
首先tomcat遇到异常默认会产生一个异常页面,我们也可以对异常页面进行配置
@Bean // 修改了 Tomcat 服务器默认错误地址
public ErrorPageRegistrar errorPageRegistrar() { // 出现错误,会使用请求转发 forward 跳转到 error 地址
return webServerFactory -> webServerFactory.addErrorPages(new ErrorPage("/error"));
}
@Bean
public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor() {
return new ErrorPageRegistrarBeanPostProcessor();
}
@Controller
public static class MyController {
@RequestMapping("test")
public ModelAndView test() {
int i = 1 / 0;
return null;
}
@RequestMapping("/error")
@ResponseBody
public Map<String, Object> error(HttpServletRequest request) {
Throwable e = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
return Map.of("error", e.getMessage());
}
}
产生异常后,springMVC会将异常记录在request域中可以通过RequestDispatcher.ERROR_EXCEPTION来获取。
这里可以用springboot提供的一个BasicErrorController类来代替,其功能更完善,可以根据请求同来返回不同信息,浏览器返回html,postman返回json(附上源码,供大家一赏)
其实也是springboot默认使用的
@Bean
public BasicErrorController basicErrorController() {
ErrorProperties errorProperties = new ErrorProperties();
errorProperties.setIncludeException(true);
return new BasicErrorController(new DefaultErrorAttributes(), errorProperties);
}
@Bean
public View error() {
return new View() {
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println(model);
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("""
<h3>服务器内部错误</h3>
""");
}
};
}
@RequestMapping(
produces = {"text/html"}
)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity(status);
} else {
Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}