知识杂货铺 知识杂货铺
首页
后端(1本书)
  • 主题初衷与诞生
  • 介绍
  • 快速上手
  • 目录结构
  • 核心配置和约定
  • 自动生成front matter
  • Markdown 容器
  • Markdown 中使用组件
  • 相关文章

    • 使目录栏支持h2~h6标题
    • 如何让你的笔记更有表现力
    • 批量操作front matter工具
    • 部署
    • 关于写文章和H1标题
    • 关于博客搭建与管理
    • 在线编辑和新增文章的方法
  • 主题配置
  • 首页配置
  • front matter配置
  • 目录页配置
  • 添加摘要
  • 修改主题颜色和样式
  • 评论栏
  • 快速开始
  • 代码集成_TODO
  • 框架初探
  • 在GitHub上贡献代码
  • 使用K8s部署系统
  • Seata分布式事务
GitHub (opens new window)

Kevin Zhang

爱凑热闹的高龄程序猿
首页
后端(1本书)
  • 主题初衷与诞生
  • 介绍
  • 快速上手
  • 目录结构
  • 核心配置和约定
  • 自动生成front matter
  • Markdown 容器
  • Markdown 中使用组件
  • 相关文章

    • 使目录栏支持h2~h6标题
    • 如何让你的笔记更有表现力
    • 批量操作front matter工具
    • 部署
    • 关于写文章和H1标题
    • 关于博客搭建与管理
    • 在线编辑和新增文章的方法
  • 主题配置
  • 首页配置
  • front matter配置
  • 目录页配置
  • 添加摘要
  • 修改主题颜色和样式
  • 评论栏
  • 快速开始
  • 代码集成_TODO
  • 框架初探
  • 在GitHub上贡献代码
  • 使用K8s部署系统
  • Seata分布式事务
GitHub (opens new window)
  • Spring Boot 培训教程
  • Spring Boot介绍

  • 开发环境配置

  • 原理剖析

  • Web开发

  • 数据访问

  • 事务

  • 集成Redis

  • 集成MongoDB

  • 异步消息

  • 异常处理

    • 异常处理
    • 异常介绍
    • 使用全局异常
      • 10.2 Spring Boot 中全局异常的使用
        • 10.2.1 Spring Boot 默认全局异常
        • 10.2.1.1 DefaultErrorAttributes
        • 10.2.1.2 BasicErrorController
        • 10.2.1.3 ErrorPageCustomizer
        • 10.2.1.4 DefaultErrorViewResolver
        • 10.2.1.5 全局异常处理过程
        • 10.2.1.6 跟踪代码,加深理解
        • 10.2.2 自定义全局异常
        • 10.2.2.1 自定义错误页面
        • 10.2.2.2 扩展 DefaultErrorAttributes 类
        • 10.2.2.3 自定义异常处理类
        • 102.2.4 测试
    • 异常的重试
    • 课后作业
  • 单元测试与热部署

  • 安全控制

  • 应用监控

  • 企业级开发

  • 多环境配置与部署

  • 综合示例

  • 前后端分离的vue急速入门

  • Spring Boot配置大全

  • 在Docker中部署Spring Boot应用

  • 开发前后端分离应用

  • 前进到Spring Cloud

  • 规则引擎

  • 流程引擎

  • 后记
  • 后端
  • 异常处理
Kevin Zhang
2024-10-30
目录

使用全局异常

# 10.2 Spring Boot 中全局异常的使用

在实际工作中,我们大部分时间在使用 Spring Boot 开发 Web 应用(当然,最经常使用到的是框架是 Spring MVC)。由于和用户多有交互,所以在请求的过程中,发生错误异常是很常见的。Spring Boot 为我们提供了一套默认的异常/错误处理机制,帮助我们来处理交互过程中的异常/错误。

官方文档 (opens new window),对错误处理的描述如下:

By default, Spring Boot provides an /error mapping that handles all errors in a sensible way, and it is registered as a “global” error page in the servlet container. For machine clients, it produces a JSON response with details of the error, the HTTP status, and the exception message. For browser clients, there is a “whitelabel” error view that renders the same data in HTML format (to customize it, add a View that resolves to error). To replace the default behavior completely, you can implement ErrorController and register a bean definition of that type or add a bean of type ErrorAttributes to use the existing mechanism but replace the contents.

例如,当我们通过浏览器访问一个不存在的 url 地址时,服务器就产生一个“404 Not Found”错误,Spring Boot 提供的默认映射“/error”就起作用了,在这个请求中处理“404 Not Found”然后通过为该请求配置的默认全局错误页面来展示错误信息。

Spring Boot 默认在没有找到对应的 Controller 处理器方法(handler not found)的情况下,不会直接报错,而是通过向 Servlet 容器(如 Tomcat)设置错误报告属性,然后 Servlet 容器发现错误(例如,http status=404),就查找默认“/error” 这个路径,Spring Boot 就接管后续全局统一异常处理。

image-20191217222722605

如果是通过 Postman 之类的工具以 Post 方式访问,Spring Boot 的默认全局异常处理机制则会返回一段json信息。

image-20191217223245717

# 10.2.1 Spring Boot 默认全局异常

在 Spring Boot 提供的org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration类通过自动配置错误处理 Bean,为 Web 容器提供了默认的全局异常处理机制。

...
@Bean//1.DefaultErrorAttributes
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
    return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}

@Bean//2.BasicErrorController
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
                                                 ObjectProvider<ErrorViewResolver> errorViewResolvers) {
    return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
                                    errorViewResolvers.orderedStream().collect(Collectors.toList()));
}

@Bean//3.ErrorPageCustomizer
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {
    return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}
...
    
@Configuration(proxyBeanMethods = false)
static class DefaultErrorViewResolverConfiguration {

    private final ApplicationContext applicationContext;

    private final ResourceProperties resourceProperties;

    DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext,
                                          ResourceProperties resourceProperties) {
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
    }

    @Bean//4.DefaultErrorViewResolver
    @ConditionalOnBean(DispatcherServlet.class)
    @ConditionalOnMissingBean(ErrorViewResolver.class)
    DefaultErrorViewResolver conventionErrorViewResolver() {
        return new DefaultErrorViewResolver(this.applicationContext, this.resourceProperties);
    }

}

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
@Conditional(ErrorTemplateMissingCondition.class)
protected static class WhitelabelErrorViewConfiguration {

    private final StaticView defaultErrorView = new StaticView();

    @Bean(name = "error")//将StaticView(就是Whitelabel错误页面)配置为error
    @ConditionalOnMissingBean(name = "error")
    public View defaultErrorView() {
        return this.defaultErrorView;
    }

...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 10.2.1.1 DefaultErrorAttributes

其中配置的org.springframework.boot.web.servlet.error.DefaultErrorAttributes类,为我们提供了在页面共享信息的“getErrorAttributes”方法。

@Override
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;
}
1
2
3
4
5
6
7
8
9

# 10.2.1.2 BasicErrorController

其中配置的org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController类,根据请求头的不同,提供了不同的方法处理默认 /error 请求。

  • 普通 html 请求使用 errorHtml 方法,返回 error 错误页面并携带错误数据。
  • 其他的请求(例如 Postman 发起的请求)使用 error 方法,返回 json 数据。

@RequestMapping("${server.error.path:${error.path:/error}}"):这是一个三元写法,如果你在配置文件配置了 server.error.path 的话,就会使用你配置的异常处理地址,如果没有就会使用你配置的 error.path 路径地址,如果还是没有,默认使用 /error 来作为发生异常的处理地址。

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")//拦截/error请求
public class BasicErrorController extends AbstractErrorController {
...
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)//处理浏览器请求,返回错误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//处理“machine clients”请求,例如Postman发起的请求,返回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, isIncludeStackTrace(request, MediaType.ALL));
    return new ResponseEntity<>(body, status);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

例如,我们在浏览器中访问了一个不存在的 url,Spring MVC 将其错误信息封装到 DefaultErrorAttributes(由 getErrorAttributes 方法提供数据),然后将其转发到“/error”请求,由 BasicErrorController.errorHtml 方法接管处理。

image-20191218222849399

其中的org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.StaticView类的“render”方法提供的就是我们经常能够看到的“Whitelabel Error Page”页面。

private static class StaticView implements View {

    private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);

    private static final Log logger = LogFactory.getLog(StaticView.class);

    @Override
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
        throws Exception {
        if (response.isCommitted()) {
            String message = getMessage(model);
            logger.error(message);
            return;
        }
        response.setContentType(TEXT_HTML_UTF8.toString());
        StringBuilder builder = new StringBuilder();
        Date timestamp = (Date) model.get("timestamp");
        Object message = model.get("message");
        Object trace = model.get("trace");
        if (response.getContentType() == null) {
            response.setContentType(getContentType());
        }
        builder.append("<html><body><h1>Whitelabel Error Page</h1>").append(
            "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>")
            .append("<div id='created'>").append(timestamp).append("</div>")
            .append("<div>There was an unexpected error (type=").append(htmlEscape(model.get("error")))
            .append(", status=").append(htmlEscape(model.get("status"))).append(").</div>");
        if (message != null) {
            builder.append("<div>").append(htmlEscape(message)).append("</div>");
        }
        if (trace != null) {
            builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>");
        }
        builder.append("</body></html>");
        response.getWriter().append(builder.toString());
    }
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 10.2.1.3 ErrorPageCustomizer

其中的org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.ErrorPageCustomizer类,在 Spring Boot(Spring MVC)应用启动时,读取 server.error.path 配置属性,注册 ErrorPage,在应用出现异常(比如404)时就能找到对应的页面(/error)进行处理。

private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {

    private final ServerProperties properties;

    private final DispatcherServletPath dispatcherServletPath;

    protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) {
        this.properties = properties;
        this.dispatcherServletPath = dispatcherServletPath;
    }

    @Override
    public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
        ErrorPage errorPage = new ErrorPage(
            this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()));
        //this.properties.getError().getPath()=server.error.path=error(默认值)
        errorPageRegistry.addErrorPages(errorPage);
    }

    @Override
    public int getOrder() {
        return 0;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 10.2.1.4 DefaultErrorViewResolver

其中的org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver类,完成默认的错误视图解析工作。

@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
    ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
    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) {
    //Spring Boot去找到一个处理错误的页面,例如error/404
    String errorViewName = "error/" + viewName;
    TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
                                                                                           this.applicationContext);
    if (provider != null) {
        return new ModelAndView(errorViewName, model);
    }
    return resolveResource(errorViewName, model);
}
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 10.2.1.5 全局异常处理过程

默认情况下,Web 应用一旦出现错误,Spring Boot 全局异常体系就接管该错误,并按照如下步骤进行处理:

  1. 系统启动时 ErrorPageCustomizer 注册错误处理页面,并准备为异常服务;
  2. 一但系统出现 4xx 或者 5xx 之类的错误,“/error”请求会被 BasicErrorController 处理,根据请求头的不同,返回页面或 json 数据;
  3. 在返回页面的情况下,由 DefaultErrorViewResolver 解析得到由 ErrorPageCustomizer 注册的响应页面,如果没有指定页面,BasicErrorController 就使用"error"视图;
  4. "error"视图是由 StaticView 这个类提供的“Whitelabel Error Page”页面。

# 10.2.1.6 跟踪代码,加深理解

为了理解 Spring Boot 在 Spring MVC 应用中提供的全局异常处理机制,建议读者以调试方式启动 Spring Boot 应用,通过浏览器访问一个不存在的地址(例如http://localhost:8080/good),跟踪代码,加深理解。

  • 在org.springframework.web.servlet.DispatcherServlet.doDispatch方法处设置断点;
  • 在org.apache.catalina.core.StandardHostValve.status方法处设置断点;
  • 在org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml方法处设置断点;
  • 在org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver.resolve方法处设置断点;

然后跟踪代码:

  1. 第 1 次进入 doDispatch 方法,是浏览器发起的,观察 request 的 attributes 中的值org.springframework.web.servlet.HandlerMapping.lookupPath=/good,DispatcherType=REQUEST;
  2. 然后进入 status 方法,tomcat 设置 statusCode=404,ErrorPage[errorCode=0, location=/error],然后转发错误请求“/error”;
  3. 第 2 次进入 doDispatch 方法,观察 request 的 dispatcherType=DispatcherType.ERROR,servletPath=/error;匹配的mappedHandler=org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml;
  4. 后续就由 errorHtml 方法接管错误处理了。在ModelAndView modelAndView = resolveErrorView(request, response, status, model)查找对应的视图(view),我们没有指定视图的情况下返回 null,然后在return (modelAndView != null) ? modelAndView : new ModelAndView("error", model)代码处为指定 Model 为“error”(ModelAndView("error", model));
  5. 浏览器得到“Whitelabel Error Page”错误页面。

# 10.2.2 自定义全局异常

在 SpringBoot 统一异常处理的做法主要有两种:一是使用注解ExceptionHandler处理某个特定的异常类,二是实现接口ErrorController或继承扩展AbstractErrorController类(BasicErrorController 就继承自这个类)。

本小节使用“Thymeleaf模板引擎+Spring MVC”的 Spring Boot 应用,来讲解提供自定义错误页面进行如何优雅的全局异常处理。

image-20191220211602338

# 10.2.2.1 自定义错误页面

在 resources/templates 下面创建 error.html 页面,内容如下:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>哦豁</title>
</head>
<body>
	发生错误啦!(使用Thymeleaf动态错误页面)
	<p th:text="${'错误:'+error}"></p>
	<p th:text="${'错误码:'+status}"></p>
	<p th:text="${'错误信息:'+message}"></p>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13

如果要细分错误码(如404,500)则在 resources/templates/error 下创建对应的页面,如 500.html,内容如下:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>哦豁</title>
</head>
<body>
	发生“服务器内部”错误啦!(使用Thymeleaf动态错误页面)
	<p th:text="${'错误:'+error}"></p>
	<p th:text="${'错误码:'+status}"></p>
	<p th:text="${'错误信息:'+message}"></p>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13

404.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>哦豁</title>
</head>
<body>
	你访问的页面逃跑啦!(使用Thymeleaf动态错误页面)
	<p th:text="${'错误:'+error}"></p>
	<p th:text="${'错误码:'+status}"></p>
	<p th:text="${'错误信息:'+message}"></p>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13

通过一个 Controller 方法,内部除 0 发生异常,来测试 500 错误:

package com.example.exception.foo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/exception/")
public class TestController {
	
	@RequestMapping("/serverError")
	public String serverError() {
		double d = 100/0;
		return "error" + d;
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

浏览器访问http://localhost:8080/exception/serverError (opens new window), 由 500.html 响应的效果如下:

image-20191221165912434

浏览器访问http://localhost:8080/hello (opens new window), 由 404.html 响应的效果如下:

image-20191221165804252

# 10.2.2.2 扩展 DefaultErrorAttributes 类

如果我们需要在错误发生的时候,生成(或提供)更多的信息,可以扩展 Spring Boot 提供的 DefaultErrorAttributes 类,在其中添加额外的错误信息。

package com.example.exception.component;

import java.util.Map;

import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

@Component
public class ExErrorAttributes extends DefaultErrorAttributes {

	@Override
	public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
		Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
// 从ExExceptionHandler中传递过来的额外异常信息,放入到errorAttributes中,供后续错误处理页面用。
		String msg = (String) webRequest.getAttribute("extErrorMsg", 0);
		errorAttributes.put("extErrorMsg", msg);
		return errorAttributes;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 10.2.2.3 自定义异常处理类

创建一个异常类 DemoException,用来演示抛出异常。

package com.example.exception.component;

public class DemoException extends RuntimeException {

	private static final long serialVersionUID = -9185062164416168469L;

	public DemoException() {
		super("演示用异常");
	}
}
1
2
3
4
5
6
7
8
9
10

自定义一个异常处理类,专门处理 DemoException 异常:

package com.example.exception.component;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class ExExceptionHandler {

	@ExceptionHandler(DemoException.class)
	public String handleException(Exception e, HttpServletRequest request) {
		request.setAttribute("javax.servlet.error.status_code", 500);
		request.setAttribute("extErrorMsg", "演示用异常,扩展信息。");
		return "forward:/error";
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 102.2.4 测试

在控制器 TestController 中添加一个方法,其内部抛出 DemoException,测试。

package com.example.exception.foo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.exception.component.DemoException;

@RestController
@RequestMapping("/exception/")
public class TestController {
	
	@RequestMapping("/demoException")
	public String serverError() {
		throw new DemoException();
	}

	@RequestMapping("/serverError")
	public String serverError2() {
		double d = 100/0;
		return "error" + d;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

打开浏览器访问http://localhost:8080/exception/demoException (opens new window), 验证自定义异常的处理效果。

image-20191222232426158

使用 Postman 发起请求,同样能够得到期望的异常信息。

image-20191222233240800

本小节示例项目代码:

https://github.com/gyzhang/SpringBootCourseCode/tree/master/spring-boot-exception (opens new window)

编辑 (opens new window)
上次更新: 2024/11/17, 16:29:23
异常介绍
异常的重试

← 异常介绍 异常的重试→

最近更新
01
PNG图片处理C++
02-07
02
PNG图片处理
01-24
03
离线安装Docker
12-24
更多文章>
Theme by Vdoing | Copyright © 2008-2025 Kevin Zhang | MIT License | 蜀ICP备20013663号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式