2.spring系列之404异常的捕获

回顾 我在之前发布了一篇spring统一返回的文章,最后提到是无法捕获404异常的,这里我们先来测试一下

@RestController public class TestController {@GetMapping("/test") public String insert22() { return "hello"; } }

浏览器请求试一下 http://localhost:8080/xxx 报错
# Whitelabel Error PageThis application has no explicit mapping for /error, so you are seeing this as a fallback.Wed Dec 29 10:14:36 CST 2021There was an unexpected error (type=Not Found, status=404).

springboot的处理方式 【2.spring系列之404异常的捕获】springboot处理这个404的异常是在 BasicErrorController中处理的
@Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController {...........@Override public String getErrorPath() { return null; }@RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); }// 包含请求头 "Accept": "application/json" 会往这里走 @RequestMapping public ResponseEntity error(HttpServletRequest request) { HttpStatus status = getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity<>(status); } Map body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity<>(body, status); }............. }

只要请求路径/error就可以进去到errorHtml这个方法,在浏览器请求http://localhost:8080/error就可以进入这个方法
解决方案 我这使用的springboot的版本为2.3.7.RELEASE
方案1:重写/error的请求 这种方案会直接舍弃掉HTML响应方式,但是前后端分离模式下,后端已经很少使用ModelAndView了
@Controller public class NoFoundController extends AbstractErrorController {public NoFoundController(ErrorAttributes errorAttributes) { super(errorAttributes); }/** * 默认路径/error,可以通过server.error.path配置 */ @RequestMapping(("${server.error.path:/error}")) public ResponseEntity notFoundError(HttpServletRequest request, HttpServletResponse response) { Map map = new HashMap<>(3); HttpStatus status = getStatus(request); map.put("code", status.value()); map.put("data", null); map.put("message", status.toString()); return new ResponseEntity<>(map, status); }/** * 在springboot2.3.0新增了server.error.path进行配置,这个废弃使用了,之前版本可以直接通过设置这个返回值修改默认/error的路径 */ @Override public String getErrorPath() { return null; } }

方案2:重写BasicErrorController中的错误处理 这种方式无法将HTML响应的也改成了json返回,请求中要有"Accept": "application/json"才能走json响应
@Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class MyBasicErrorController extends BasicErrorController {public MyBasicErrorController(ServerProperties serverProperties) { // import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; super(new DefaultErrorAttributes(), serverProperties.getError()); }/** * JSON响应 */ @Override public ResponseEntity error(HttpServletRequest request) { Map map = new HashMap<>(); HttpStatus status = getStatus(request); map.put("code", status.value()); map.put("data", null); map.put("message", status.toString()); return new ResponseEntity<>(map, status); }/** * HTML响应,根据需求处理自己处理 */ @Override public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { return super.errorHtml(request, response); } }

其中MyBasicErrorController的构造函数可以参考spring自动装配ErrorMvcAutoConfiguration中的传值
//源码: public class ErrorMvcAutoConfiguration {private final ServerProperties serverProperties; public ErrorMvcAutoConfiguration(ServerProperties serverProperties) { this.serverProperties = serverProperties; }@Bean @ConditionalOnMissingBean(value = https://www.it610.com/article/ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { // ErrorAttributes return new DefaultErrorAttributes(); }@Bean @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider errorViewResolvers) { // serverProperties.getError return new BasicErrorController(errorAttributes, this.serverProperties.getError(), errorViewResolvers.orderedStream().collect(Collectors.toList())); } ........ }

最后附上完整代码:
@Getter public class BusinessException extends RuntimeException { private Integer code; public BusinessException(Integer code, String message) { super(message); this.code = code; }public BusinessException(String message) { super(message); } } --------------------------------------------------------------------------------------------@ControllerAdvice @ResponseBody @Slf4j public class GlobalException {@ExceptionHandler(value = https://www.it610.com/article/BusinessException.class) public ResponseModel businessExceptionError(BusinessException e) { log.error("业务异常", e); if (e.getCode() != null) { return ResponseModel.error(e.getCode(), e.getMessage()); } return ResponseModel.error(e.getMessage()); }@ExceptionHandler(value = https://www.it610.com/article/Exception.class) public ResponseModel exceptionError(Exception e) { log.error("系统异常", e); return ResponseModel.error(); } } -------------------------------------------------------------------------------------------- @Getter public enum ResponseEnum { SUCCESS(0, "OK"), PARAMETER_ERROR(1,"参数异常"),NO_FOUND(404,"not found"), SYSTEM_ERROR(500, "服务器异常,请联系管理员"); ResponseEnum(Integer code, String message) { this.code = code; this.message = message; }private final Integer code; private final String message; } --------------------------------------------------------------------------------------------public class ResponseModel { private Integer code; private String message; private T data; public ResponseModel(Integer code, String message, T data) { this.code = code; this.message = message; this.data = https://www.it610.com/article/data; }public static ResponseModel ok() { return ok(null); }public static ResponseModel ok(T data) { return new ResponseModel<>(ResponseEnum.SYSTEM_ERROR.getCode(), ResponseEnum.SYSTEM_ERROR.getMessage(), data); }public static ResponseModel ok(T data, String message) { return new ResponseModel<>(ResponseEnum.SYSTEM_ERROR.getCode(), message, data); }public static ResponseModel error(Integer statusCode, String message) { return new ResponseModel<>(statusCode, message, null); }public static ResponseModel error(String message) { return error(ResponseEnum.SYSTEM_ERROR.getCode(), message); }public static ResponseModel error() { return error(ResponseEnum.SYSTEM_ERROR.getCode(), ResponseEnum.SYSTEM_ERROR.getMessage()); } } -------------------------------------------------------------------------------------------- @Controller public class NoFoundController extends AbstractErrorController {public NoFoundController(ErrorAttributes errorAttributes) { super(errorAttributes); }/** * 默认路径/error,可以通过server.error.path配置 */ @RequestMapping(("${server.error.path:/error}")) public ResponseEntity notFoundError(HttpServletRequest request, HttpServletResponse response) { Map map = new HashMap<>(3); HttpStatus status = getStatus(request); map.put("code", status.value()); map.put("data", null); map.put("message", status.toString()); return new ResponseEntity<>(map, status); }/** * 在springboot2.3.0新增了server.error.path进行配置,这个废弃使用了,之前版本可以直接通过设置这个返回值修改默认/error的路径 */ @Override public String getErrorPath() { return null; } }

感谢各位小伙伴阅读到最后,如有错误,敬请指正。

    推荐阅读