前言
在项目开发的过程中, 不管是对底层数据库的操作过程, 还是业务层的处理过程, 还是控制层的处理过程, 都不可避免会遇到各种可预知的、 不可预知的异常需要处理。 如果对每个过程都单独作异常处理, 代码耦合度会比较高,开发工作量也会加大而且不好统一, 这也增加了代码的维护成本。
针对这种实际情况, 我们需要将所有类型的异常处理从各处理过程中解耦出来, 这样既保证了相关处理过程的功能单一, 也实现了异常信息的统一处理和维护。
同时, 我们也不希望直接把异常抛给用户, 应该对异常进行处理, 对错误信息进行封装, 然后返回一个友好的信息给用户。
自定义异常类相关知识回顾

异常的分类
- Throwable 类是 Java 异常类型的顶层父类,一个对象只有是 Throwable类的实例,才是一个异常对象,才能被异常处理机制识别。
- 按照错误严重性,从 Throwale 类中衍生出 Error 和 Exception 两大派系
Error(错误):程序在执行过程中所遇到的硬件或系统的错误。错误对程序而言是致命的,将导致程序无法运行。不允许捕获。当发生 Error 时,只能依靠外界干预。比如:内存溢出。
Exception(异常):是程序运行过程中,可以预料的意外情况。比如空指针,数组下标越界。异常出现可以被捕获处理掉,使程序继续运行。
- Exception:又分为编译时异常和运行时异常。
- 运行时异常都是 RuntimeException 类及其子类, 这些异常是不检查的异常, 是在程序运行的时候可能会发生的, 所以程序可以捕捉, 也可以不捕捉。程序应该从逻辑角度去尽量避免。如:空指针、数组下标越界等。
- 编译时异常也叫检查异常,是运行时异常以外的异常, 也是 Exception 及其子类, 这些异常从程序的角度来说是必须经过捕捉检查处理的, 否则不能通过编译. 如 IOException、SQLException 等。

上面的异常体系结构图都是系统自带的,系统自己处理,但是很多时候项目会出现特有问题,而这些问题并未被 Java 所描述并封装成对象,所以对于这些特有的问题可以按照封装的思想,将特有的问题进行自定义异常封装。要想创建自定义异常,需要继承 Throwable 或者他的子类 Exception。
使用自定义异常类的步骤

错误处理机制的默认机制
默认机制流程
Spring Boot 对异常的处理有一套默认的机制
- 当程序产生异常时,根据请求头中的 Content-Type 包含的内容来返回不同的响应信息。如果 Content-Type 是”text/html”,则以 HTML 格式返回,如果 Content-Type 是”application/json”,则以 JSON 格式返回。
错误处理的⾃动配置都在 ErrorMvcAutoConfiguration
中,两⼤核⼼机制:
SpringBoot 会自适应处理错误,响应页面或JSON数据
SpringMVC的错误处理机制依然保留,MVC处理不了,才会交给boot进行处理
如下,这张图将是整个默认处理机制的核心步骤总结

如果你使用了以前 Spring MVC 错误处理的方式,那么就按照你的来,没有就按照默认的来
这里就是使用的
@ExceptionHandler
,用于标记一个方法,该方法将处理特定类型的异常。默认情况下,它只处理 当前控制器类 中发生的异常1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class UserController {
public User getUser( { Long id)
// 可能抛出 UserNotFoundException
return userService.getUserById(id);
}
// 处理当前控制器中的 UserNotFoundException
public ResponseEntity<String> handleUserNotFound(UserNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body("User not found: " + ex.getMessage());
}
}而
@ControllerAdvice
统一处理所有错误,通常用在全局错误处理器,一般是集中处理所有 Controller 发生的错误1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class GlobalExceptionHandler {
// 处理所有控制器中的 NullPointerException
public ResponseEntity<String> handleNullPointerException(NullPointerException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Internal server error: " + ex.getMessage());
}
// 处理所有控制器中的 MethodArgumentNotValidException(验证失败)
public ResponseEntity<Map<String, String>> handleValidationErrors(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors()
.forEach(error -> errors.put(error.getField(), error.getDefaultMessage()));
return ResponseEntity.badRequest().body(errors);
}
}
而在这里,可以实现可以通过自定义异常类和 @ResponseStatus
注解来简化异常处理
如果你使用自定义 Spring MVC 的错误,注意,Spring MVC 的异常处理遵循 就近原则
- 局部处理:优先使用控制器内部的
@ExceptionHandler
。 - 全局处理:如果控制器内部没有匹配的处理方法,则使用
@ControllerAdvice
中的全局处理方法。
这部分就是在这里提一下,下面在说Spring Boot 中的异常处理方案时候还是会继续说的。
所以Spring boot 在什么都不写的时候,会默认就有一个
/error
目录。因为 Spring MVC
如果没有错误请求处理,就处理不了,就得转发到 Spring boot
处理错误的请求路径。

如果用 Postman 访问,则以 JSON 的格式返回异常信息

发⽣错误以后,转发给/error
路径,SpringBoot在底层写好⼀个
BasicErrorController
的组件,专⻔ 处理这个请求
这个路径是可以设置的,配置文件中通过server.error.path=/error
,表示错误发生后,错误请求将转发到这个路径进行处理
默认错误处理机制的实现:关键点在 BasicErrorController 这个类中
BasicErrorController 类
- 这是 Spring Boot
默认的错误处理控制器,负责处理所有未被应用程序其他控制器处理的异常请求(默认映射到
/error
路径) - 它提供了两种错误响应方式:HTML 格式(
errorHtml
方法)和 JSON 格式(error
方法) - 在处理 HTML 错误视图时,它依赖于
ErrorViewResolver
接口的实现来确定具体使用哪个视图模板
1 | /** |
1 | /** |
所以说,错误页面和错误信息是这么解析到的
1 | ModelAndView modelAndView = this.resolveErrorView(request, response, status, model); |
容器中专门的有⼀个错误视图解析器,在public class ErrorMvcAutoConfiguration
,就是去哪个错误页面是这个方法解析器来决定的,其中涉及到的关键类
DefaultErrorViewResolver
,会在下面说
1 |
|
看一下
BasicErrorController
类的关系,BasicErrorController
类本身就是一个控制器,这个类是默认处理/error
请求的。那么响应页面的时候是怎么找到页面的呢?这里有一个关键类
DefaultErrorViewResolver
。

DefaultErrorViewResolver 类:
- 这是
ErrorViewResolver
接口的默认实现类,负责根据 HTTP 状态码解析对应的错误视图 - 它尝试从模板引擎(如 Thymeleaf、FreeMarker)或静态资源目录中查找匹配的错误页面
- 如果找不到精确匹配的错误页面(如
error/404.html
),则尝试使用系列错误页面(如error/4xx.html
或error/5xx.html
)
响应页面时如何找到默认处理 /error 请求的呢,可以发现,页面当应用程序抛出异常时,Spring Boot 的错误处理流程大致如下:
异常捕获:DispatcherServlet 捕获到未处理的异常,将请求转发到
/error
路径请求处理:
BasicErrorController
接收到/error
请求,并根据请求的 Accept 头决定返回 HTML 还是 JSON 格式的响应视图解析:在处理 HTML 响应时,
BasicErrorController
调用DefaultErrorViewResolver
的resolveErrorView
方法来确定使用哪个视图模板1
2
3
4
5
6
7
8
9public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
// 尝试解析精确匹配的状态码视图(如 error/404)
ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
// 如果找不到精确匹配的视图,则尝试解析系列视图(如 error/4xx)
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
}
return modelAndView;
}
视图解析的流程大概如下:
resolveErrorView
中,调用了reslove
去解析精确匹配的状态码,然后尝试构建视图,然后resolve
方法中会调用resolveResource
获取静态资源
1 | private ModelAndView resolve(String viewName, Map<String, Object> model) { |
1 | private ModelAndView resolveResource(String viewName, Map<String, Object> model) { |
所以,我们可以得到详细的规则,如下:
解析⼀个错误⻚
如果发⽣了500、404、503、403 这些错误
如果有模板引擎,默认在
classpath:/templates/error/ 精确码 .html
如果没有模板引擎,在静态资源⽂件夹下找
精确码 .html
如果匹配不到
精确码 .html
这些精确的错误⻚,就去找5xx.html
,4xx.html
模糊匹配如果有模板引擎,默认在
classpath:/templates/error/5xx.html
如果没有模板引擎,在静态资源⽂件夹下找
5xx.html
如果模板引擎路径 templates 下有
error.html
⻚⾯,就直接渲染
如果你需要定制错误页面,这个类的步骤很重要
因为,这种机制使得开发者可以通过在
templates/error/
或static/error/
目录下放置相应的 HTML 文件来自定义错误页面,同时保持了 Spring Boot 错误处理的默认行为。
而容器中有⼀个默认的名为 error 的 view; 提供了默认⽩⻚功能
1 |
|
封装了JSON格式的错误信息
1 |
|
所以如何自定义一个异常的界面,这很有说法了
Spring Boot 中的异常处理方案
Spring Boot中自定义异常
全局异常处理:通过实现HandlerExceptionResolver
接口或扩展ResponseEntityExceptionHandler
类,实现全局的异常处理。
实现HandlerExceptionResolver
Spring提供了HandlerExceptionResolver
接口,用于定义全局的异常处理逻辑。可以通过实现该接口,来自定义异常处理策略。
1 | import org.springframework.web.servlet.HandlerExceptionResolver; |
扩展ResponseEntityExceptionHandler
ResponseEntityExceptionHandler
是Spring提供的一个基类,包含了一些常见异常的处理逻辑。可以通过扩展该类,来实现自定义的异常处理逻辑。
1 | import org.springframework.http.ResponseEntity; |
常见异常的处理方法
处理资源未找到异常
资源未找到异常(ResourceNotFoundException)是指请求的资源不存在,通常在RESTful API中较为常见。可以通过自定义异常类和异常处理方法来处理这种异常。
1 | public class ResourceNotFoundException extends RuntimeException { |
在全局异常处理类中处理该异常:
1 |
|
处理数据验证异常
数据验证异常(ValidationException)通常在对输入数据进行验证时出现。可以通过在Controller中使用@Valid
注解,对输入数据进行验证,并在全局异常处理类中处理验证异常。
1 | import javax.validation.constraints.NotNull; |
在Controller中使用@Valid
注解:
1 | import org.springframework.web.bind.annotation.*; |
在全局异常处理类中处理验证异常
1 | import org.springframework.validation.FieldError; |
处理数据库异常
数据库异常(DatabaseException)通常在与数据库交互时出现。例如,违反唯一性约束、外键约束等。可以通过自定义异常类和异常处理方法来处理这种异常。
1 | public class DatabaseException extends RuntimeException { |
在全局异常处理类中处理数据库异常:
1 |
|
自定义json响应
简介
在现代前后端分离的架构中,RESTful API 通常需要返回结构化的 JSON 错误响应,而不是 HTML 页面。Spring Boot 提供了多种方式来实现自定义 JSON 错误响应,使前端能够以统一的方式处理错误。
Spring 提供了一个非常方便的异常处理方案:控制器通知
(@ControllerAdvice
或@RestControllerAdvice
),它将所有控制器作为一个
切面,利用切面技术来实现异常的通知。处理Controller层的异常处理就是通过@ControllerAdvice
和@ExceptionHandler
注解,集中处理Controller层的异常。这两个注解的使用我在上面已经说过了,在这里就是扩展讲一下了
相关注解
@ExceptionHandler
@ExceptionHandler
,用于标记一个方法,该方法将处理特定类型的异常。默认情况下,它只处理
当前控制器类 中发生的异常
作用范围:仅处理当前控制器类中抛出的异常。
异常类型匹配:通过
value
属性指定要处理的异常类型。返回值:可以返回视图名称(如
String
)、ModelAndView
或直接返回响应体(如@ResponseBody
)。
@ControllerAdvice
@ControllerAdvice
是一个特殊的
@Component
,用于定义
@ExceptionHandler
、@InitBinder
和
@ModelAttribute
方法,这些方法将应用于
所有控制器。
核心特性
全局作用域:处理所有控制器中抛出的异常。
选择性应用:可通过
annotations
、basePackages
等属性指定要应用的控制器范围。与
@ExceptionHandler
结合:在@ControllerAdvice
类中定义@ExceptionHandler
方法,实现全局异常处理。如果组合使用
@ExceptionHandler
和@ControllerAdvice
,注意Spring MVC 的异常处理遵循 就近原则:- 局部处理:优先使用控制器内部的
@ExceptionHandler
。 - 全局处理:如果控制器内部没有匹配的处理方法,则使用
@ControllerAdvice
中的全局处理方法。
例如,处理流程示例
控制器抛出
UserNotFoundException
- 首先查找当前控制器中是否有
@ExceptionHandler(UserNotFoundException.class)
。 - 如果没有,则查找
@ControllerAdvice
类中是否有匹配的处理方法。 - 如果仍没有,则由 Spring MVC 的默认异常处理器处理。
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// 全局异常处理器
public class GlobalExceptionHandler {
// 全局处理 RuntimeException
public ResponseEntity<String> handleRuntimeException(RuntimeException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Runtime error: " + ex.getMessage());
}
}
// 特定控制器
public class OrderController {
public Order createOrder( { Order order)
// 可能抛出 BusinessException
return orderService.createOrder(order);
}
// 仅处理当前控制器中的 BusinessException
public ResponseEntity<String> handleBusinessException(BusinessException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("Business error: " + ex.getMessage());
}
}- 首先查找当前控制器中是否有
- 局部处理:优先使用控制器内部的
@RestControllerAdvice
@RestControllerAdvice
是 @ControllerAdvice
的特殊版本,是专门为 RESTful
控制器设计的全局异常处理器,它的方法返回值将自动转换为JSON响应体。它是
@ControllerAdvice
和 @ResponseBody
的结合体。
当 Spring Boot 应用启动时,Spring 容器会自动扫描并加载带有
@RestControllerAdvice
注解的类,将其实例化并纳入管理。一旦控制器层在处理请求时抛出异常,Spring
MVC 的异常处理机制就会被触发。
在标注了 @RestControllerAdvice
的类中,我们可以定义若干个 @ExceptionHandler
方法,这些方法会根据其参数类型与抛出的异常类型进行匹配
1 |
|
由于 @RestControllerAdvice 类型的处理器返回值带有 @ResponseBody 效果,因此,这些 @ExceptionHandler 方法的返回值会被自动转换为 HTTP 响应体的内容。常见的做法是返回一个自定义的错误响应实体,如 ErrorResponse,包含错误代码、消息等信息,方便客户端理解和处理,自定义的错误响应实体示例如下
1 |
|
@ResponseStatus
@ResponseStatus
注解是spring-web
包中提供的一个注解,其主要作用就是为了改变HTTP响应的状态码
有两种用法
一种是加载自定义异常类上
一种是加在目标方法中,当修饰一个类的时候,通常修饰的是一个异常类。
1
2
3
4
5
public Response unauthorized() {
return new Response(401, "Unauthorized", null);
}HttpStatus.CREATED 状态码为201,将原来请求状态码200改为201。
@ResponseStatus注释可指定下表所示属性:
属性 | 类型 | 是否必要 | 说明 |
---|---|---|---|
code | HttpStatus | 否 | http状态码,如HttpStatus.CREATED,HttpStatus.OK |
value | String | 否 | 同code属性 |
reason | HttpStatus | 否 | 错误信息 |
如果@ResponseStatus
有reason
属性,@RequestMapping
方法返回值都不处理了,直接返回服务器自带的
ERROR
页面,交互体验比较差,建议不要使用。而且当@ResponseStatus
用在方法上,如果添加了reason
属性,且reason
不为”“,且code
> 0(哪怕状态码是200),也会对当前请求走错误处理。
响应状态的覆盖顺序
- 方法级
@ResponseStatus
- 异常类级
@ResponseStatus
ResponseEntity
设置的状态码- Spring默认状态码
可以通过自定义异常类和 @ResponseStatus
注解来简化异常处理:
使用时,先声明一个自定义异常类,在自定义异常类上面加上@ResponseStatus
注释表示系统运行期间,当抛出自定义异常的时候,使用@ResponseStatus
注解中声明的属性和reason属性将异常信息返回给客户端,提高可读性。
1 | import org.springframework.web.bind.annotation.ResponseStatus; |
1 |
|
响应结果可以看到:

而@ResponseStatus
注解配合@ExceptionHandler
注解使用会更好
1 |
|
@ExceptionHandler(ValidationException.class)
指定该方法处理ValidationException
类型的异常。当任何控制器抛出ValidationException
时,此方法会被调用,@ResponseStatus(HttpStatus.BAD_REQUEST)
将 HTTP 状态码设置为 400(Bad Request)- 这种组合无需在每个异常处理方法中手动设置
HttpServletResponse
的状态码,代码更简洁,符合 RESTful API 设计原则。
而且还可以实现组合状态码与自定义响应
1 |
|
但是会覆盖异常类的注解
1 |
|
自定义页面响应
在Web应用中,除了常见的JSON格式错误响应外,很多时候我们还需要返回自定义的错误页面,特别是在传统的服务端渲染(SSR)应用中。Spring Boot提供了多种方式来实现自定义错误页面,让用户获得更友好的错误提示体验。
实现自定义异常页面主要通过以下两种方式:基于静态资源的简单实现和基于模板引擎的动态实现。
但是我们需要先知道定制错误处理逻辑异常页面的资源加载过程
假设我们拥有自定义的异常页面,如下图结构

如上图定义一个 404 的异常界面。查找顺序如下
- 有模板引擎的情况下:
templates/error/状态码
,将错误页面命名为错误状态码.html
放在模板引擎文件夹里面的error
文件夹下, 发生此状态码的错误就会来到对应的页面。 - 没有模板引擎或者模板引擎找不到错误页面,静态资源
static
文件 夹下找:static/error/状态
- 以上都没有,默认来到 Spring Boot 默认的错误提示页面
也就是说,根据默认错误页面机制,Spring Boot 默认会在以下路径查找错误页面:
/error/404.html
(针对404错误)/error/5xx.html
(针对5xx系列错误)/error/error.html
(通用错误页面)
如果需要修改默认的错误页面路径,可以在application.properties
中配置:
1 | server.error.path=/my-error |
对于更复杂的场景,我们可以使用模板引擎动态渲染错误页面。
以使用Thymeleaf模板引擎为例子
实现步骤:
- 在
resources/templates/error/
目录下创建模板文件 - 模板中可以访问错误信息变量
而 Spring Boot 会自动向错误页面提供以下属性:
timestamp
:错误发生的时间戳status
:HTTP状态码error
:错误原因exception
:异常类名message
:异常消息path
:请求路径
1 | <!-- resources/templates/error/error.html --> |
如果需要更灵活的错误页面解析逻辑,可以自定义
ErrorViewResolver
。
- 创建自定义 ErrorViewResolver 实现
ErrorViewResolver
接口,并重写resolveErrorView
方法。
1 | import org.springframework.boot.web.servlet.error.ErrorViewResolver; |
如果需要完全控制错误处理逻辑,可以自定义
ErrorController
。
创建自定义 ErrorController 实现
ErrorController
接口,处理/error
路径。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
public class EnhancedErrorController implements ErrorController {
public String handleError(HttpServletRequest request, Model model) {
// 收集错误信息
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
Object exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
// 设置模型数据
model.addAttribute("timestamp", new Date());
model.addAttribute("status", status);
model.addAttribute("error", HttpStatus.valueOf(Integer.valueOf(status.toString())).getReasonPhrase());
if (exception != null) {
Throwable ex = (Throwable) exception;
model.addAttribute("message", ex.getMessage());
model.addAttribute("exception", ex.getClass().getName());
}
Object path = request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
if (path != null) {
model.addAttribute("path", path);
}
return "error/custom-error";
}
}
虽然@ControllerAdvice
通常用于返回JSON响应,但也可以用来返回错误页面。算是冷知识了。
需要针对特定异常特殊处理时,可以使用@ControllerAdvice
1 |
|
Spring Boot 异常处理实战
前后分离场景
- 核心手段:利用
@ControllerAdvice + @ExceptionHandler
组合,对后台产生的所有错误,执行统一异常处理流程,将异常信息以合适格式(如 JSON)返回给前端,便于前端做错误提示等处理。
服务端页面渲染场景
不可预知的 HTTP 码错误(服务器或客户端错误 )
- 精确匹配:在
classpath:/templates/error/
路径下,放置像500.html
(处理 500 错误 )、404.html
(处理 404 错误 )这类,与具体 HTTP 错误码精准对应的错误页面。 - 模糊匹配:同样在
classpath:/templates/error/
路径下,放5xx.html
(匹配所有 5 开头的服务器错误 )、4xx.html
(匹配所有 4 开头的客户端错误 )这类通用错误页面,作为精确匹配未命中时的兜底。
业务错误
- 核心业务错误:针对核心业务里的各类错误,通过代码主动控制跳转,导向专门定制的专属错误页,保证核心业务错误提示精准、贴合业务逻辑。
- 通用业务错误:借助
classpath:/templates/error.html
页面,在通用业务出错时,展示错误信息,作为通用业务错误的统一展示入口 。
所以就是整体是在 Spring Boot 框架下,依据不同的开发场景(前后端交互形式、错误类型),规划对应的错误处理与页面 / 信息返回策略,让错误处理更规范、体验更优 。
HttpStatus
文章末尾,贴一下org.springframework
包内的
HttpStatus
状态码的枚举类
1 | // |