SpringBoot中的异常捕获
1. 返回一个默认的错误页面
2. 使用其他工具访问(PostMan)
3. 浏览器请求头
4. 原理:
可以参照ErrorMvcAutoConfiguration;SpringBoot的默认自动配置;
给容器添加了以下重要组件
1. DefaultErrorAttributes
// 帮我们共享页面信息
@Override
@Deprecated
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
errorAttributes.put("timestamp", new Date());
addStatus(errorAttributes, webRequest);
addErrorDetails(errorAttributes, webRequest, includeStackTrace);
addPath(errorAttributes, webRequest);
return errorAttributes;
}
2. BasicErrorController ;处理默认的/erroe请求
@Controller
// 默认取server.error中的值,没有的话就取error.path中的值没有的话就默认是/error
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
// TEXT_HTML_VALUE = text/html,产生html的数据
// 就是通过浏览器发送的请求头来区分
@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);
}
@RequestMapping // 产生json数据
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);
}
}
3. ErrorPageCustomizer
@Override
public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
ErrorPage errorPage = new ErrorPage(
this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
errorPageRegistry.addErrorPages(errorPage);
}
/**
* Path of the error controller.
*/
@Value("${error.path:/error}")
private String path = "/error"; // 系统出现错误发后来到error请求进行处理;(web.xml中注册的错误页面的规则)
4. DefaultErrorViewResolver
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
private static final Map<Series, String> SERIES_VIEWS;
static {
Map<Series, String> views = new EnumMap<>(Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
// 获取具体返回到页面的ModelAndView
ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
// 如果没有找到具体的ModelAndView,并且在SERIES_VIEWS中包含所返回的4开头或5开头的状态码就去取默认的4xx或5xx的页面
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
private ModelAndView resolve(String viewName, Map<String, Object> model) {
// 默认SpringBoot会去找一个页面;errer/404.html
String errorViewName = "error/" + viewName;
// 模板引擎可以解析到这个页面就使用模板引擎进行解析
TemplateAvailabilityProvider provider =
this.templateAvailabilityProviders.getProvider(errorViewName,this.applicationContext);
if (provider != null) {
// 模板引擎可用的情况下直接返回ModelAndView指定的视图地址
return new ModelAndView(errorViewName, model);
}
// 模板引擎不可用时。就到静态资源文件夹下去找ErrorViewName对应的页面errer/404.html
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
// 循环静态资源下的文件,如果存在这个页岩就进行返回,没有则返回null
// classpath:/META-INF/resources/
for (String location : this.resourceProperties.getStaticLocations()) {
try {
Resource resource = this.applicationContext.getResource(location);
resource = resource.createRelative(viewName + ".html");
if (resource.exists()) {
return new ModelAndView(new HtmlResourceView(resource), model);
}
}
catch (Exception ex) {
}
}
return null;
}
}
5. 步骤:
1. 一但系统出现4xx或5xx之类的错误;ErrorPageCustomizer就会生效(定制错误页面的响应规则);就会来到/error请求;就会被BasicErrorController进行处理
2. 响应页面;去哪个页面是由DefaultErrorViewResolver解析得到的
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,Map<String, Object> model) {
// 通过所有的ErrorViewResolver得到所有的ModelAndView
for (ErrorViewResolver resolver : this.errorViewResolvers) {
ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
if (modelAndView != null) {
return modelAndView;
}
}
return null;
}
-
如何定制错误页面
- **在有模板引擎的情况下;**error/状态码;【将错误页面命名为 错误状态码.html 放在模板引擎文件夹下的error文件夹】,发生此状态码的错误就会来到对应的页面;
如果没有精确匹配的错误码页面,我们还可以使用4xx和5xx来定义以4开头的和以5开头的状态码来匹配;精确匹配优先;(优先寻找精确状态码的html)
页面可以获取的信息:
-
timestamp:当前时间
-
status:状态码
-
exception:异常类类名
-
message:异常消息
-
errors:JSR3.3数据校验的错误都在这
-
path:访问的路径
-
没有模板引擎的情况下(就是模板引擎下找不到这个error页面),去静态资源下找;
-
以上都没有error页面那就来到SprignBoot的默认错误页面;
-
可体定制错误json数据
- 自定义异常处理类&返回json数据,没有自适应效果,只能返回json,对前后端分离友好;
// 声明是异常处理的控制类,@RestControllerAdvice1包含了@ControllerAdvice和@ResponseBody
@ControllerAdvice
public class CustomExceptionResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomExceptionResolver.class);
// 声明要捕捉的异常类
@ExceptionHandler(value = {Exception.class})
// 返回json数据
@ResponseBody
public Object handle(HttpServletRequest request, HttpServletResponse response, Exception ex) {
BaseException customException = null;
if (ex instanceof NoHandlerFoundException) {
customException = new BaseException(BaseCodeEnum.NOT_EXIST.getKey(), BaseCodeEnum.NOT_EXIST.getValue());
} else if (ex instanceof HttpRequestMethodNotSupportedException) {
customException = new BaseException(BaseCodeEnum.PARAMETER_RRROR.getKey(), "不支持此HttpMethod");
} else if (ex instanceof TypeMismatchException || ex instanceof MissingServletRequestParameterException) {
customException = new BaseException(BaseCodeEnum.PARAMETER_RRROR.getKey(), BaseCodeEnum.PARAMETER_RRROR.getValue());
} else if(ex instanceof MethodArgumentNotValidException){
customException = new BaseException(BaseCodeEnum.PARAMETER_RRROR.getKey(), "参数格式验证失败");
} else if (ex instanceof BaseException) {
customException = (BaseException) ex;
} else {
//未定义异常返回特定异常
customException = new BaseException(BaseCodeEnum.FAIL.getKey(), "服务器异常,请稍后再试");
}
LOGGER.error("CustomExceptionResolver:{}", StringUtils.getEx(ex));
return Response.error(customException.getCode(), customException.getMessage());
}
}
/**
* AbstractErrorController类中获取错误状态码
*/
String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
使用Spring的方式去捕捉异常
- 创建全局异常处理类
// 此注解是实现全局异常处理
@ControllerAdvice
//网页code码比如404会在tomcat跳出,如果想在程序捕获提高程序权限
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomExceptionResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomExceptionResolver.class);
// 指定你要捕捉的异常类型
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Object handle(HttpServletRequest request, HttpServletResponse response, Exception ex) {
BaseException customException = null;
if (ex instanceof NoHandlerFoundException) {
customException = new BaseException(BaseCodeEnum.NOT_EXIST.getKey(), BaseCodeEnum.NOT_EXIST.getValue());
} else if (ex instanceof HttpRequestMethodNotSupportedException) {
customException = new BaseException(BaseCodeEnum.PARAMETER_RRROR.getKey(), "不支持此HttpMethod");
} else if (ex instanceof TypeMismatchException || ex instanceof MissingServletRequestParameterException) {
customException = new BaseException(BaseCodeEnum.PARAMETER_RRROR.getKey(), BaseCodeEnum.PARAMETER_RRROR.getValue());
} else if (ex instanceof BaseException) {
customException = (BaseException) ex;
} else {
//未定义异常返回特定异常
customException = new BaseException(BaseCodeEnum.FAIL.getKey(), "服务器异常,请稍后再试");
}
// 打印异常信息
LOGGER.error("CustomExceptionResolver:{}", StringUtils.getEx(ex));
// 返回页面JSON串
return Response.error(customException.getCode(), customException.getMessage(), RequestIDUtils.getRequestID());
}
}