我在之前发布了一篇spring统一返回的文章,最后提到是无法捕获404异常的,这里我们先来测试一下
@RestController public class TestController { @GetMapping("/test") public String insert22() { return "hello"; } }
浏览器请求试一下 http://localhost:8080/xxx 报错
# Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback. Wed Dec 29 10:14:36 CST 2021 There was an unexpected error (type=Not Found, status=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<String, Object> 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<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity<>(status); } Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity<>(body, status); } ............. }
只要请求路径/error就可以进去到errorHtml
这个方法,在浏览器请求http://localhost:8080/error就可以进入这个方法
我这使用的springboot的版本为2.3.7.RELEASE
这种方案会直接舍弃掉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<Map<String, Object>> notFoundError(HttpServletRequest request, HttpServletResponse response) { Map<String, Object> 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; } }
这种方式无法将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<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> 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 = 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<ErrorViewResolver> 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 = BusinessException.class) public ResponseModel<Void> businessExceptionError(BusinessException e) { log.error("业务异常", e); if (e.getCode() != null) { return ResponseModel.error(e.getCode(), e.getMessage()); } return ResponseModel.error(e.getMessage()); } @ExceptionHandler(value = Exception.class) public ResponseModel<Void> 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<T> { 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 = data; } public static ResponseModel<Void> ok() { return ok(null); } public static <T> ResponseModel<T> ok(T data) { return new ResponseModel<>(ResponseEnum.SYSTEM_ERROR.getCode(), ResponseEnum.SYSTEM_ERROR.getMessage(), data); } public static <T> ResponseModel<T> ok(T data, String message) { return new ResponseModel<>(ResponseEnum.SYSTEM_ERROR.getCode(), message, data); } public static ResponseModel<Void> error(Integer statusCode, String message) { return new ResponseModel<>(statusCode, message, null); } public static ResponseModel<Void> error(String message) { return error(ResponseEnum.SYSTEM_ERROR.getCode(), message); } public static ResponseModel<Void> 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<Map<String, Object>> notFoundError(HttpServletRequest request, HttpServletResponse response) { Map<String, Object> 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; } }
感谢各位小伙伴阅读到最后,如有错误,敬请指正。