SpringBoot-异常处理
1.介绍
- 默认情况下,Spring Boot 提供/error 处理所有错误的映射
- 对于机器客户端,它将生成 JSON 响应,其中包含错误,HTTP 状态和异常消息的详细信 息。对于浏览器客户端,响应一个"whitelabel"错误视图,以 HTML 格式呈现相同的数据
3.当我们请求localhost:8080/xxx,这样一个不存的请求地址时,springboot底层会对请求进行请求转发到 /error,在DefaultErrorViewResolver处理错误的视图解析器中进行视图渲染
首先会在/public 、/static 、/resource 这些可以直接访问静态资源的目录下查找对于错误码的页面:/error/404.html,再去查找错误码首字母大头的错误页面: /error/4xx.html页面,如果没有找到会创建一个新的error视图进行渲染,也就是我们前面所说的“whitelabel”错误视图。
private ModelAndView resolve(String viewName, Map<String, Object> model) {
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
this.applicationContext);
if (provider != null) {
return new ModelAndView(errorViewName, model);
}
return resolveResource(errorViewName, model);
}
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
for (String location : this.resources.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;
}
@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);
}
2.自定义异常页面
1.文档
2.自定义异常页面说明
1、如何找到这个文档位置, 看下面一步步的指引
https://docs.spring.io/spring-boot/docs/current/reference/html/index.html => a single page html => 8.web => servlet web application => The “Spring Web MVC Framework” => Error Handling => Custom Error Pages
3.自定义异常页面-应用实例
1.需求: 自定义 404.html 500.html 4xx.html 5xx.html 当发生相应错误时,显示自定义的页面信息
这里由于配置了thymeleaf模板引擎,因此将错误页面放到templates目录下(thymeleaf内置的视图解析器,默认配置的前缀时在类路径下的templates目录下,且后缀名为.html)
2.代码实现
@Controller
public class MyErrorController {
//模拟一个服务器内部错误500
@GetMapping("/err")
public String err() {
int i = 10 / 0; //算术异常
return "manage";
}
//这里我们配置的是Post方式请求 /err2
//使用 get方式来请求 /err2 , 这样就会出现一个405开头的客户端错误
@PostMapping("/err2")
public String err2() {
//..
return "manage";
}
}
3.全局异常
1.说明
- @ControllerAdvice+@ExceptionHandler 处理全局异常
- 底层是 ExceptionHandlerExceptionResolver 支持的
2.全局异常-应用实例
1.需求: 演示全局异常使用, 当发生 ArithmeticException、NullPointerException 时,不使 用默认异常机制匹配的 xxx.html , 而是显示全局异常机制指定的错误页面
定义一个全局异常处理页面,global.html
2.创建src\main\java\com\llp\springboot\exception\GlobalExceptionHandler.java
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
//1、编写方法,处理指定异常, 比如我们处理算术异常和空指针异常, 可以指定多个异常
//2. 这里要处理的异常,由程序员来指定
//3. Exception e : 表示异常发生后,传递的异常对象
//4. Model model: 可以将我们的异常信息,放入model,并传递给显示页面
//提出一个问题,如何获取到异常发生的方法
@ExceptionHandler({
ArithmeticException.class, NullPointerException.class})
public String handleAritException(Exception e, Model model, HandlerMethod handlerMethod) {
log.info("异常信息={}", e.getMessage());
//这里将发生的异常信息放入到model,可以再错误页面取出显示
model.addAttribute("msg", e.getMessage());
//得到异常发生的方法是哪个
log.info("异常发生的方法是={}", handlerMethod.getMethod());
return "/error/global"; //视图地址
}
}
3.创建src\main\resources\templates\error\global.html
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>全局异常-显示页面</title>
</head>
<body bgcolor="#CED3FE">
<hr/>
<img src="../../static/images/1.GIF" height="60" width="150"/>
<div style="text-align: center">
<h1>全局异常/错误 发生了:)</h1><br/>
异常/错误信息: <h1 th:text="${msg}"></h1><br/>
<a href='#' th:href="@{/}">返回主页面</a>
</div>
<hr/>
</body>
</html>
4.完成测试
ExceptionHandlerExceptionResolver类的doResolveHandlerMethodException方法
@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
//先根据发生异常的方法和异常信息获取处理异常的方法对象,如果找不到则走默认的异常处理机制
ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}
if (this.argumentResolvers != null) {
exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
ArrayList<Throwable> exceptions = new ArrayList<>();
try {
if (logger.isDebugEnabled()) {
logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
}
// Expose causes as provided arguments as well
Throwable exToExpose = exception;
while (exToExpose != null) {
exceptions.add(exToExpose);
Throwable cause = exToExpose.getCause();
exToExpose = (cause != exToExpose ? cause : null);
}
//将异常数集合转换为数组,并给最后一个元素赋值 handlerMethod对象
Object[] arguments = new Object[exceptions.size() + 1];
exceptions.toArray(arguments); // efficient arraycopy call in ArrayList
arguments[arguments.length - 1] = handlerMethod;
//执行异常处理的目标方法
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
}
catch (Throwable invocationEx) {
// Any other than the original exception (or a cause) is unintended here,
// probably an accident (e.g. failed assertion or the like).
if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {
logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
}
// Continue with default processing of the original exception...
return null;
}
if (mavContainer.isRequestHandled()) {
return new ModelAndView();
}
else {
ModelMap model = mavContainer.getModel();
HttpStatus status = mavContainer.getStatus();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
mav.setViewName(mavContainer.getViewName());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
}
2022-08-13 11:00:13.791 INFO 3936 — [nio-8080-exec-8] c.l.s.interceptor.LoginInterceptor : preHandle拦截到的请求的URI=/err
2022-08-13 11:00:13.791 INFO 3936 — [nio-8080-exec-8] c.l.s.interceptor.LoginInterceptor : preHandle拦截到的请求的URL=http://localhost:8080/err
2022-08-13 11:00:17.770 INFO 3936 — [nio-8080-exec-8] c.l.s.exception.GlobalExceptionHandler : 异常信息=/ by zero
2022-08-13 11:00:17.771 INFO 3936 — [nio-8080-exec-8] c.l.s.exception.GlobalExceptionHandler : 异常发生的方法是=public java.lang.String com.llp.springboot.controller.MyErrorController.err()
2022-08-13 11:00:17.780 INFO 3936 — [nio-8080-exec-8] c.l.s.interceptor.LoginInterceptor : afterCompletion 执行了。。。
4.自定义异常
1.说明
- 如果 Spring Boot 提供的异常不能满足开发需求,程序员也可以自定义异常.
- @ResponseStatus+自定义异常
- 底层是 ResponseStatusExceptionResolver ,底层调用 response.sendError(statusCode, resolvedReason);
- 当抛出自定义异常后,仍然会根据状态码,去匹配使用 x.html 显示
2.自定义异常-应用实例
1.需求:自定义一个异常 AccessException, 当用户访问某个无权访问的路径时,抛出该异常,显示 自定义异常状态码
2.应用实例-实现
1.创建src\main\java\com\llp\springboot\exception\AccessException.java
/**
* AccessException : 我们自定义的一个异常
* value默认值: INTERNAL_SERVER_ERROR(500, Series.SERVER_ERROR, "Internal Server Error")
* value = HttpStatus.FORBIDDEN: 表示发生AccessException异常,我们通过http协议返回的状态码 403
* 这个状态码和自定义异常的对应关系是由程序员来决定[尽量合理来设置]
*/
@ResponseStatus(value = HttpStatus.FORBIDDEN)
public class AccessException extends RuntimeException {
//提供一个构造器,可以指定信息
public AccessException(String message) {
super(message);
}
//显示的定义一下无参构造器
public AccessException() {
}
}
2.修改src\main\java\com\llp\springboot\controller\MyErrorController.java
//编写方法模拟自定义异常AccessException
@GetMapping("/err3")
public String err3(String name) {
//如果用户不是tom则认为无权访问
if (!"tom".equals(name)) {
throw new AccessException();
}
return "manage";
}
- 完成测试
3.Debug一波执行流程
默认异常处理机制
没有将自定义异常配置到全局异常处理,走的时默认的异常处理机制;先根据对应的状态码去查找静态页面,没有找到则以状态码首个数字打头的xx.html进行查找,如果配置了模板引擎则到模板引擎配置的视图解析器对应的目录去查找。
全局异常处理机制
1.首先在全局异常处理的方法中添加自定义异常
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
//1、编写方法,处理指定异常, 比如我们处理算术异常和空指针异常, 可以指定多个异常
//2. 这里要处理的异常,由程序员来指定
//3. Exception e : 表示异常发生后,传递的异常对象
//4. Model model: 可以将我们的异常信息,放入model,并传递给显示页面
//提出一个问题,如何获取到异常发生的方法
@ExceptionHandler({
ArithmeticException.class, NullPointerException.class,AccessException.class})
public String handleAritException(Exception e, Model model, HandlerMethod handlerMethod) {
log.info("异常信息={}", e.getMessage());
//这里将发生的异常信息放入到model,可以再错误页面取出显示
model.addAttribute("msg", e.getMessage());
//得到异常发生的方法是哪个
log.info("异常发生的方法是={}", handlerMethod.getMethod());
return "/error/global"; //视图地址
}
}
如果自定义异常配置了全局异常处理,则会走java的异常处理,而最终返回的操作也由程序员自己来定义