spring boot默认异常处理机制原理 实现自适应、自定义的异常处理

xiaoxiao2025-04-08  16

文章目录

原理部分spring boot默认处理源码分析 实战部分定制浏览器错误页面定制json错误消息如何达到自适应效果如何传递状态码信息给/error请求最终版:自适应、自定义的异常处理 总结:如何实现自适应、自定义的异常处理

不想听原理可以直接看总结

原理部分

spring boot默认处理

浏览器,返回一个默认的错误页面 如果是其他客户端,默认响应一个json数据

源码分析

在spring boot中,关于错误的自动配置类是 ErrorMvcAutoConfiguration 这个自动配置类, 给容器中加入了这几个关键组件:

ErrorPageCustomizer: 这个类的作用是定制错误的响应规则。在它的的registerErrorPages方法里的getPath方法指定了处理错误默认发送的请求为/error请求BasicErrorController: 这个类就是来处理/error请求的,它有如下两个方法: @RequestMapping(produces = "text/html") // 指定响应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) ? modelAndView : new ModelAndView("error", model); } @RequestMapping @ResponseBody // 指定响应json数据 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<Map<String, Object>>(body, status); }

spring boot 默认处理/error请求时,会根据响应头来判断发送请求的是浏览器还是app,然后响应HTML或者json数据。 浏览器这部分代码处理完请求之后返回了一个ModelAndView 指定响应的页面,这个页面会去resolveErrorView这个方法里面找。

protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { for (ErrorViewResolver resolver : this.errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { return modelAndView; } } return null; }

这个方法遍历所有的ErrorViewResolver,从这里面找返回的页面。在没有指定的情况下,默认来到了第三个注入的组件

DefaultErrorViewResolver 这个组件的作用是指定浏览器响应的错误页面。 我们来看他是如何指定的: @Override 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; }

可以看出,默认是将响应的状态码指定为视图名称,也就是说我们如果想自定义浏览器的错误响应页面,只需要在静态资源文件夹下创建4xx, 5xx等页面,当发生对应状态码异常时就会来到对应页面。

实战部分

定制浏览器错误页面

没有模板引擎的情况下 将错误页面命名为 错误状态码.html 放在静态资源文件夹里面的 error文件夹下,发生此状态码的错误就会来到对应的页面。 ps:如果建立名为4xx.html的文件,当发生以4开头的状态码错误时,在没有被精确匹配的情况下,就会来到这个页面。有模板引擎的情况下 当然要把错误状态码.html文件放在模板引擎文件夹下。 有模板引擎的优势在于:你可在错误页面获取一些讯息: status:状态码timestamp:时间戳error:错误提示exception:异常对象message:异常消息errors:JSR303数据校验的错误都在这里 如果以上两个地方都没找到,就会来到默认一开始的页面。

定制json错误消息

假设我们现在有一个UserNotExistException

public class UserNotExistException extends RuntimeException { private String id; public String getId() { return id; } public UserNotExistException(String id) { super("用户不存在"); this.id = id; } }

我们可以利用springMvc的ControllerAdvice来定制这个异常的处理

@ControllerAdvice // 这是一个专门处理异常的Controller public class ControllerExceptionHandler { @ExceptionHandler(UserNotExistException.class) // 这个方法处理UserNotExistException异常 @ResponseBody // 已json的形式返回 @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 指定响应的错误状态码为404,不指定默认是200 public Map<String, Object> handleUserNotExistException(UserNotExistException ex) { Map<String, Object> result = new HashMap<>(); result.put("id", ex.getId()); result.put("message", ex.getMessage()); return result; } }

这么写,我们app端访问该异常时能够正确返回我们定制的json数据。 但是存在一个问题:浏览器出现该异常时返回的也是json数据,而不是我们定制的404.html页面,失去了spring boot默认的自适应效果。

如何达到自适应效果

我们知道spring boot在处理/error请求时,默认就是自适应的。 所以我们想要得到自适应效果,只需要将请求转发到/error就好了。

@ControllerAdvice // 这是一个专门处理异常的Controller public class ControllerExceptionHandler { @ExceptionHandler(UserNotExistException.class) // 这个方法处理UserNotExistException异常 @ResponseStatus(HttpStatus.NOT_FOUND) // 指定响应的错误状态码,不指定默认是200 public String handleUserNotExistException(UserNotExistException ex) { Map<String, Object> result = new HashMap<>(); result.put("id", ex.getId()); result.put("message", ex.getMessage()); return "forward:/error"; } }

这样确实能达到自适应效果,但又出现新的问题

设置响应状态码的注解不起作用,还是默认的200。导致无法定位我们为浏览器设置的404.html界面。我们自定义数据不起作用

如何传递状态码信息给/error请求

我们再回头看一下BasicErrorController里的方法,看它是如何获取状态码的。然后我们就发现了HttpStatus status = getStatus(request);方法。点进去一看:

protected HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request .getAttribute("javax.servlet.error.status_code"); ... }

原来它是从request域中按照这个javax.servlet.error.status_code属性获取的。 所以我们把它的参数HttpServletRequest也拿到我们的ExceptionHandler方法中。 然后设置request域中属性的值

@ExceptionHandler(UserNotExistException.class) // 这个方法处理UserNotExistException异常 public String handleUserNotExistException(UserNotExistException ex, HttpServletRequest request) { Map<String, Object> result = new HashMap<>(); result.put("id", ex.getId()); result.put("message", ex.getMessage()); request.setAttribute("javax.servlet.error.status_code", 404); return "forward:/error"; }

成功传入状态码。但是没有自定义数据。

最终版:自适应、自定义的异常处理

我们已经完成自适应了,接下来再说自定义。 再回头看一看BasicErrorController的源码。看它是怎么获取响应数据的。 然后我们发现,不管是浏览器返回的model还是app返回的map。数据都是由getErrorAttributes这个方法获取的。点进来。

protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) { RequestAttributes requestAttributes = new ServletRequestAttributes(request); return this.errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace); }

看一下返回的这个this.errorAttributes:

public abstract class AbstractErrorController implements ErrorController { private final ErrorAttributes errorAttributes; ... public AbstractErrorController(ErrorAttributes errorAttributes) { this(errorAttributes, null); } ... }

我们发现这个ErrorAttributes是从容器中获取的。 而ErrorAttributes在我们一开始说的ErrorMvcAutoConfiguration这个自动配置类中第一个注入的就是DefaultErrorAttributes!

@Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); }

我们可以看到在它的getErrorAttributes方法中,设置了我们可以在模板引擎中获取的数据。

@Override public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>(); errorAttributes.put("timestamp", new Date()); addStatus(errorAttributes, requestAttributes); addErrorDetails(errorAttributes, requestAttributes, includeStackTrace); addPath(errorAttributes, requestAttributes); return errorAttributes; }

也就是说,如果我们要自定义消息,只需要重写这个getErrorAttributes方法就好了。 先在ControllerExceptionHandler的handleUserNotExistException方法中,在转发请求之前添加一行代码:request.setAttribute("extResult", result); 将我们自定义的数据一并转发过去。

然后继承DefaultErrorAttributes重写getErrorAttributes方法。

@Component public class MyErrorAttributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { // 父类返回的map Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace); // 自定义的添加数据 map.put("author", "joker"); // 从请求域中获得我们自己添加的数据 Map<String, Object> extResult = (Map<String, Object>) requestAttributes.getAttribute("extResult", 0); map.put("extResult", extResult); return map; } }

总结:如何实现自适应、自定义的异常处理

编写一个异常类 public class UserNotExistException extends RuntimeException { private String id; public String getId() { return id; } public UserNotExistException(String id) { super("用户不存在"); this.id = id; } } 写一个ControllerAdvice指定一个ExceptionHandler方法处理该异常 @ControllerAdvice // 这是一个专门处理异常的Controller public class ControllerExceptionHandler { @ExceptionHandler(UserNotExistException.class) // 这个方法处理UserNotExistException异常 public String handleUserNotExistException(UserNotExistException ex, HttpServletRequest request) { // 自定义消息 Map<String, Object> result = new HashMap<>(); result.put("id", ex.getId()); result.put("message", ex.getMessage()); // 设置错误状态码 request.setAttribute("javax.servlet.error.status_code", 404); // 将自定义消息放入转发域 request.setAttribute("extResult", result); // 转发到/error请求,使其获得自适应效果 return "forward:/error"; } } 编写一个类继承DefaultErrorAttributes重写getErrorAttributes方法。 @Component public class MyErrorAttributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { // 父类返回的map Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace); // 自定义的添加数据 map.put("author", "joker"); Map<String, Object> extResult = (Map<String, Object>) requestAttributes.getAttribute("extResult", 0); map.put("extResult", extResult); return map; } } 在模板引擎文件夹或者静态资源文件夹中加入 错误状态码.html
转载请注明原文地址: https://www.6miu.com/read-5027800.html

最新回复(0)