第一章:Spring MVC全局异常处理概述
1.1 全局异常处理的核心价值
全局异常处理是Spring MVC框架中一个非常重要的机制,它能够将应用程序中各个层抛出的异常集中到一个统一的处理点进行处理,从而避免在每个控制器中重复编写异常处理代码。全局异常处理的核心价值在于解耦、统一和简化异常处理流程,使得开发者可以专注于业务逻辑的实现,而不是分散精力在异常处理上。
首先,全局异常处理能够解耦异常处理逻辑。在传统的局部异常处理方式中,每个控制器都需要自己处理可能出现的异常,导致代码中大量重复的异常处理逻辑。通过全局异常处理,可以将这些处理逻辑集中到一个专门的类中,使得代码更加简洁和易于维护。
其次,全局异常处理能够统一异常响应格式。在实际开发中,不同的异常可能需要返回不同的响应信息给客户端,例如参数错误、资源未找到或服务器内部错误等。通过全局异常处理,可以定义一个统一的响应格式,使得客户端能够更容易地理解和处理这些异常信息。
最后,全局异常处理能够简化错误处理流程。在传统的异常处理方式中,每个控制器都需要捕获异常并进行处理,这不仅增加了代码量,还可能导致处理逻辑不一致。通过全局异常处理,可以将这些处理逻辑集中到一个地方,使得整个系统的错误处理更加一致和规范。
1.2 异常处理的基本分类
异常处理可以分为以下几类:
编译时异常(Checked Exception) :这类异常是Java中需要显式捕获或声明抛出的异常,如IOException
、SQLException
等。在Spring MVC中,通常通过try-catch
块捕获这些异常,并在需要时将其向上抛出。
运行时异常(RuntimeException) :这类异常是Java中不需要显式捕获或声明抛出的异常,如NullPointerException
、IllegalArgumentException
等。在Spring MVC中,通常不显式捕获这些异常,而是让它们自动传播到全局异常处理器进行处理。
自定义异常(Custom Exception) :这类异常是开发者根据业务需求自定义的异常,如BusinessException
、ServiceException
等。通过自定义异常,可以更精确地表达业务逻辑中的错误情况,并提供更详细的错误信息。
框架异常(Framework Exception) :这类异常是Spring框架本身抛出的异常,如MethodArgumentNotValidException
(参数校验异常)、MissingServletRequestParameterException
(缺少请求参数异常)等。这些异常通常需要通过全局异常处理器进行捕获和处理。
在实际开发中,全局异常处理主要关注的是运行时异常和框架异常,而编译时异常通常需要在业务层进行处理,并通过自定义异常向上抛出,以便全局异常处理器能够捕获和处理。
1.3 局部异常处理与全局异常处理的差异
局部异常处理和全局异常处理是Spring MVC中两种不同的异常处理方式,它们之间存在显著差异:
特性 | 局部异常处理 | 全局异常处理 |
---|---|---|
作用范围 | 仅作用于当前控制器 | 作用于所有控制器 |
配置方式 | 在控制器中使用@ExceptionHandler注解 | 使用@ControllerAdvice注解创建全局异常处理器 |
代码重复 | 需要在每个控制器中重复编写相同异常处理逻辑 | 异常处理逻辑集中在一个地方,避免代码重复 |
异常捕获 | 只能捕获当前控制器抛出的异常 | 可以捕获整个应用中抛出的异常 |
响应格式 | 可以返回不同格式的响应 | 可以统一返回格式化的响应,如JSON或HTML页面 |
局部异常处理 是在特定的控制器中使用@ExceptionHandler
注解来处理该控制器中可能出现的异常。这种方式的优点是可以在特定的控制器中处理特定的异常,但缺点是代码重复,难以维护。例如:
@RestController
public class UserController {
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
// 业务逻辑
return userService.getUser(id);
}
@ExceptionHandler value = {IllegalArgumentException.class})
public ResponseEntity<String> handleIllegalArgumentException IllegalArgumentException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body("Invalid user ID: " + e.getMessage());
}
}
全局异常处理 是通过创建一个带有@ControllerAdvice
注解的类,并在其中使用@ExceptionHandler
注解来处理整个应用中可能出现的异常。这种方式的优点是代码集中,易于维护,但缺点是可能需要处理更广泛的异常类型。例如:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = {IllegalArgumentException.class})
public ResponseEntity<String> handleIllegalArgumentException IllegalArgumentException e) {
return ResponseEntity.status(HttpStatus BAD_REQUEST)
.body("Invalid parameter: " + e.getMessage());
}
@ExceptionHandler(value = {NullPointerException.class})
public ResponseEntity<String> handleNullPointerException(NullPointerException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Server error: " + e.getMessage());
}
}
全局异常处理的优势在于能够集中处理所有控制器中的异常,避免代码重复,同时提供统一的错误响应格式。然而,在某些特定情况下,局部异常处理可能更适合,例如需要为某个特定控制器返回不同格式的错误响应。
1.4 为什么需要全局异常处理机制
在实际的Web应用开发中,全局异常处理机制是必不可少的,主要原因如下:
首先,提高用户体验。当应用程序出现异常时,如果直接将技术细节暴露给用户,不仅会让用户感到困惑,还可能带来安全隐患。通过全局异常处理机制,可以将技术异常转换为用户友好的错误提示,例如"系统繁忙,请稍后再试",而不是"NullPointerException: Cannot invoke method on a null object"。
其次,增强系统安全性。全局异常处理可以避免敏感信息(如数据库错误、服务器路径等)暴露给客户端,减少安全风险。通过统一的错误响应格式,可以确保所有异常信息都经过过滤和处理,只返回必要的信息给用户。
第三,简化代码结构。在没有全局异常处理机制的情况下,每个控制器都需要编写类似的异常处理代码,导致代码冗余和难以维护。通过全局异常处理机制,可以将这些代码集中到一个地方,使得整个系统的代码更加简洁和一致。
第四,便于错误追踪和监控。全局异常处理可以将异常信息记录到日志中,便于开发人员追踪和调试问题。同时,通过统一的错误响应格式,可以更容易地集成错误监控系统,及时发现和处理系统中的问题。
最后,支持国际化和多端适配。全局异常处理可以更容易地支持国际化,为不同语言的用户返回相应的错误信息。同时,可以通过不同的异常处理器为Web端和移动端返回不同格式的错误响应。
第二章:Spring MVC内置异常处理机制
2.1 SimpleMappingExceptionResolver的配置与使用
SimpleMappingExceptionResolver是Spring MVC提供的一种简单异常处理器,它通过配置将特定的异常类型映射到对应的视图名称或HTTP状态码。这种方式适合传统的MVC应用,但对于RESTful API应用可能不够灵活。
在Spring Boot中,可以通过Java配置或XML配置方式来使用SimpleMappingExceptionResolver。
Java配置方式:
@Configuration
public class ExceptionConfig {
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
// 设置异常与视图映射
Properties exceptionMappings = new Properties();
exceptionMappings.put("java.lang.RuntimeException", "error");
exceptionMappings.put("java.lang.NullPointerException", "error-internal");
exceptionMappings.put("cn.tedu.csmall.product.exServiceException", "error-service");
resolver.setExceptionMappings(exceptionMappings);
// 设置异常与HTTP状态码映射
Properties exceptionStatuses = new Properties();
exceptionStatuses.put("java.lang.RuntimeException", Integer.toString(HttpStatus.INTERNAL_SERVER_ERROR.value()));
exceptionStatuses.put("java.lang.NullPointerException", Integer.toString(HttpStatus.INTERNAL_SERVER_ERROR.value()));
exceptionStatuses.put("cn.tedu.csmall.product.exServiceException", Integer.toString(HttpStatusBAD_REQUEST.value()));
resolver.setExceptionStatuses(exceptionStatuses);
// 设置视图中异常信息的变量名
resolver.setExceptionAttribute("ex");
return resolver;
}
}
XML配置方式:
<bean class="org.springframework.web.servlet handler SimpleMappingExceptionResolver">
<!-- 默认异常处理页面 -->
<property name="defaultErrorView" value="error"/>
<!-- 异常信息在视图中的变量名 -->
<property name="exceptionAttribute" value="ex"/>
<!-- 定义需要特殊处理的异常 -->
<property name="exceptionMappings">
<props>
<prop key="java.lang.RuntimeException">error-internal</prop>
<prop key="cn.tedu.csmall.product.exServiceException">error-service</prop>
</props>
</property>
<!-- 定义异常对应的HTTP状态码 -->
<property name="exceptionStatuses">
<props>
<prop key="java.lang.RuntimeException">500</prop>
<prop key="cn.tedu.csmall.product.exServiceException">400</prop>
</props>
</property>
</bean>
在使用SimpleMappingExceptionResolver时,需要注意以下几点:
-
异常类型匹配:SimpleMappingExceptionResolver是通过全限定类名来匹配异常类型的,因此需要精确指定异常的类名。
-
优先级控制 :如果有多个异常处理器,SimpleMappingExceptionResolver的优先级通常较低,可以通过
@Order
注解或实现Ordered
接口来调整其优先级。 -
兜底处理 :SimpleMappingExceptionResolver有一个
defaultErrorView
属性,可以设置默认的异常处理视图,当没有匹配到特定异常时,会使用这个视图进行处理。 -
HTTP状态码 :通过
exceptionStatuses
属性,可以为特定的异常设置HTTP状态码,这样客户端可以根据状态码进行相应的处理。
2.2 HandlerExceptionResolver接口的实现与应用
HandlerExceptionResolver是Spring MVC中定义的异常处理器接口,它提供了更灵活的异常处理方式。通过实现这个接口,可以自定义异常处理逻辑,例如记录日志、发送邮件通知或返回特定的响应信息。
以下是实现HandlerExceptionResolver接口的一个示例:
@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 记录异常信息
ex.printStackTrace();
logger.error("Error occurred in controller: {}", ex.getMessage());
// 根据异常类型创建ModelAndView
if (ex instanceof ParamException) {
// 参数异常
ModelAndView mv = new ModelAndView();
mv.addObject("errorMsg", "Invalid parameter: " + ex.getMessage());
mv.setViewName("error/param-error");
return mv;
} else if (ex instanceof腔口异常) {
// 数据库异常
ModelAndView mv = new ModelAndView();
mv.addObject("errorMsg", "Database error: " + ex.getMessage());
mv.setViewName("error/database-error");
return mv;
} else {
// 其他异常
ModelAndView mv = new ModelAndView();
mv.addObject("errorMsg", "Server error: " + ex.getMessage());
mv.setViewName("error/500");
return mv;
}
}
}
在Spring Boot中,可以通过@Bean
方式将自定义的HandlerExceptionResolver添加到容器中:
@Configuration
public class ExceptionConfig {
@Bean
public CustomExceptionResolver customExceptionResolver() {
return new CustomExceptionResolver();
}
}
HandlerExceptionResolver接口的实现类可以处理任何类型的异常 ,包括控制器方法抛出的异常和处理器映射器、处理器适配器等组件抛出的异常。这使得它比局部的@ExceptionHandler
注解更加灵活和强大。
2.3 @ControllerAdvice与@ExceptionHandler的使用
@ControllerAdvice是Spring 3.1引入的一个注解,用于创建全局异常处理器。通过这个注解,可以定义一个类,该类中的方法可以处理所有控制器中的异常。这种方式特别适合RESTful API应用,因为它可以返回格式化的JSON响应,而不仅仅是视图名称。
以下是使用@ControllerAdvice
和@ExceptionHandler
注解的一个示例:
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理参数异常
@ExceptionHandler(ParamException.class)
public ResponseEntity<ErrorResult> handleParamException(ParamException ex) {
ErrorResult errorResult = new ErrorResult();
errorResult码 = 400;
errorResult息 = "Invalid parameter: " + ex.getMessage();
return ResponseEntity.status(HttpStatus BAD_REQUEST).body(errorResult);
}
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResult> handleBusinessException(BusinessException ex) {
ErrorResult errorResult = new ErrorResult();
errorResult码 = 400;
errorResult息 = "Business error: " + ex.getMessage();
return ResponseEntity.status(HttpStatus BAD_REQUEST).body(errorResult);
}
// 处理数据库异常
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ErrorResult> handleDataAccessException(DataAccessException ex) {
ErrorResult errorResult = new ErrorResult();
errorResult码 = 500;
errorResult息 = "Database access error: " + ex.getMessage();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResult);
}
// 处理所有未处理的异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResult> handleAllExceptions(Exception ex) {
ErrorResult errorResult = new ErrorResult();
errorResult码 = 500;
errorResult息 = "Server error: " + ex.getMessage();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResult);
}
}
在使用@ControllerAdvice
时,需要注意以下几点:
-
异常处理顺序 :多个
@ExceptionHandler
方法会按照声明顺序执行,如果某个方法能够处理当前异常,则后续的方法不会执行。 -
异常类型匹配 :
@ExceptionHandler
注解可以指定处理的异常类型,可以是具体类型、类型数组或通配符。 -
响应格式 :通过返回
ResponseEntity
,可以灵活控制HTTP状态码和响应体,这对于RESTful API应用非常有用。 -
日志记录:在异常处理方法中,可以记录详细的日志信息,便于后续的调试和问题排查。
-
国际化支持 :可以通过
MessageSource
获取国际化错误信息,为不同语言的用户提供相应的错误提示。
2.4 三种机制的对比与适用场景
Spring MVC提供了三种主要的异常处理机制,它们各有优缺点,适用于不同的场景:
特性 | SimpleMappingExceptionResolver | HandlerExceptionResolver接口 | @ControllerAdvice + @ExceptionHandler |
---|---|---|---|
配置方式 | 配置文件(XML或Java配置) | 实现接口并注册为Bean | 注解方式,无需额外配置 |
作用范围 | 全局 | 全局 | 全局 |
响应格式 | 视图名称 | 模型和视图 | 可返回任意格式(如JSON、XML) |
灵活性 | 中等,通过配置文件定义映射 | 高,可以自定义处理逻辑 | 高,通过注解定义处理逻辑 |
适用场景 | 传统MVC应用,需要返回视图 | 需要高度定制异常处理逻辑 | RESTful API应用,需要返回结构化响应 |
优先级 | 通常较低 | 可以通过@Order调整 | 可以通过@Order调整 |
SimpleMappingExceptionResolver适合传统的MVC应用,它通过配置文件将异常映射到视图名称,实现简单,但灵活性较低。它主要用于处理控制器方法抛出的异常,但无法处理处理器映射器、处理器适配器等组件抛出的异常。
HandlerExceptionResolver接口提供了更高的灵活性,允许开发者自定义异常处理逻辑,包括记录日志、发送通知等。它适用于需要高度定制异常处理逻辑的场景,但实现相对复杂。
@ControllerAdvice + @ExceptionHandler 是Spring 3.1引入的注解方式,特别适合RESTful API应用。它可以通过注解定义异常处理逻辑,返回格式化的JSON响应,同时也可以通过@Order
注解调整处理顺序。这种方式既灵活又简洁,是当前Spring Boot应用中的主流选择。
在实际开发中,通常会根据应用场景选择合适的异常处理机制:
- 对于传统MVC应用,可以使用SimpleMappingExceptionResolver或实现HandlerExceptionResolver接口。
- 对于RESTful API应用,推荐使用
@ControllerAdvice
和@ExceptionHandler
注解。 - 如果需要处理非常复杂的异常情况,可以考虑实现HandlerExceptionResolver接口并注册为Bean。
- 对于简单的异常处理,可以使用
@ControllerAdvice
和@ExceptionHandler
注解。
第三章:自定义全局异常处理器的实现
3.1 定义自定义异常类
在实际开发中,自定义异常类是全局异常处理的重要组成部分。通过自定义异常类,可以更精确地表达业务逻辑中的错误情况,并提供更详细的错误信息。
以下是定义一个自定义业务异常类的示例:
public class BusinessException extends RuntimeException {
// 错误码
private final String errorCode;
// 错误信息
private final String errorMsg;
// 错误详情(可选)
private final String errorDetail;
// 带错误码和错误信息的构造函数
public BusinessException(String errorCode, String errorMsg) {
super(errorMsg);
this.errorCode =errorCode;
this error_msg = errorMsg;
this.errorDetail = null;
}
// 带错误码、错误信息和错误详情的构造函数
public BusinessException(String errorCode, String errorMsg, String errorDetail) {
super(errorMsg);
this.errorCode =errorCode;
this error_msg = errorMsg;
this.errorDetail = errorDetail;
}
// 获取错误码
public String getErrorCode() {
return this.errorCode;
}
// 获取错误信息
public String geterrorMsg() {
return this.errorMsg;
}
// 获取错误详情
public String getErrorDetail() {
return this.errorDetail;
}
}
在自定义异常类中,可以添加以下关键信息:
-
错误码:一个唯一的错误码,用于标识不同的错误类型,便于客户端进行错误分类和处理。
-
错误信息:一个简短的错误描述,通常用于前端显示。
-
错误详情:一个详细的错误描述,通常用于日志记录和调试。
-
错误原因:可以包含导致异常的根本原因,如底层数据库异常等。
-
错误建议:可以包含解决异常的建议,如重试、联系管理员等。
自定义异常类通常继承自RuntimeException,这样它们可以被Spring MVC的异常处理器自动捕获和处理。如果需要处理编译时异常(Checked Exception),则需要继承自Exception类。
3.2 使用@RestControllerAdvice实现全局异常处理
@RestControllerAdvice是Spring 4.2引入的一个注解 ,它是@ControllerAdvice
和@RestController
的组合,适用于RESTful API应用。通过这个注解,可以定义一个全局异常处理器,返回格式化的JSON响应。
以下是使用@RestControllerAdvice
实现全局异常处理的示例:
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理参数异常
@ExceptionHandler(ParamException.class)
public ResponseEntity<ErrorResult> handleParamException(ParamException ex) {
// 记录日志
log.error("Parameter error: {}", ex.getMessage(), ex);
// 构建错误响应
ErrorResult errorResult = new ErrorResult();
errorResult码 = 400;
errorResult息 = "Invalid parameter";
errorResult息详情 = ex.getMessage();
// 返回响应
return ResponseEntity.status(HttpStatus BAD_REQUEST)
.body(errorResult);
}
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResult> handleBusinessException(BusinessException ex) {
// 记录日志
log.error("Business error: {}", ex.getMessage(), ex);
// 构建错误响应
ErrorResult errorResult = new ErrorResult();
errorResult码 = 500;
errorResult息 = "Business error";
errorResult息详情 = ex.getMessage();
// 可以从异常中获取错误码
errorResult码 = ex.getErrorCode();
// 返回响应
return ResponseEntity.status errorResult码)
.body(errorResult);
}
// 处理所有未处理的异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResult> handleAllExceptions(Exception ex) {
// 记录日志
log.error("Server error: {}", ex.getMessage(), ex);
// 构建错误响应
ErrorResult errorResult = new ErrorResult();
errorResult码 = 500;
errorResult息 = "Server error";
errorResult息详情 = ex.getMessage();
// 返回响应
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(errorResult);
}
}
在使用@RestControllerAdvice
时,需要注意以下几点:
-
异常处理顺序 :多个
@ExceptionHandler
方法会按照声明顺序执行,如果某个方法能够处理当前异常,则后续的方法不会执行。 -
异常类型匹配 :
@ExceptionHandler
注解可以指定处理的异常类型,可以是具体类型、类型数组或通配符。 -
响应格式 :通过返回
ResponseEntity
,可以灵活控制HTTP状态码和响应体。 -
日志记录:在异常处理方法中,可以记录详细的日志信息,便于后续的调试和问题排查。
-
国际化支持 :可以通过
MessageSource
获取国际化错误信息,为不同语言的用户提供相应的错误提示。
3.3 响应格式化与标准化
在实际开发中,定义一个标准化的响应格式对于前后端协作非常重要。通过统一的响应格式,可以简化前端代码,提高系统的可维护性和一致性。
以下是定义一个标准化响应格式的示例:
@Data
public class ResponseResult<T> {
// 状态码,200表示成功,非200表示失败
private int code;
// 错误码,用于标识具体的错误类型
private String errorCode;
// 错误信息,用于前端显示
private String message;
// 业务数据,成功时返回
private T data;
// 成功响应的静态方法
public static <T> ResponseResult<T> success(T data) {
ResponseResult<T> result = new ResponseResult<>();
result.code = 200;
result.message = "success";
result据 = data;
return result;
}
// 失败响应的静态方法
public static <T> ResponseResult<T> fail(int code, String message, T data) {
ResponseResult<T> result = new ResponseResult<>();
result.code = code;
result.message = message;
result.data = data;
return result;
}
// 带错误码的失败响应的静态方法
public static <T> ResponseResult<T> fail(String errorCode, int code, String message, T data) {
ResponseResult<T> result = new ResponseResult<>();
result.code = code;
result.message = message;
result.errorCode =errorCode;
result.data = data;
return result;
}
}
在全局异常处理器中,可以返回这个标准化的响应格式:
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理参数异常
@ExceptionHandler(ParamException.class)
public ResponseEntity<ResponseResult<Void>> handleParamException(ParamException ex) {
// 记录日志
log.error("Parameter error: {}", ex.getMessage(), ex);
// 构建错误响应
ResponseResult<Void> result = ResponseResult.fail(
400,
"Invalid parameter",
null
);
result.setErrorCode(" Parameter_ERROR");
// 返回响应
return ResponseEntity.status(result码).body(result);
}
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ResponseResult<Void>> handleBusinessException(BusinessException ex) {
// 记录日志
log.error("Business error: {}", ex.getMessage(), ex);
// 构建错误响应
ResponseResult<Void> result = ResponseResult.fail(
500,
"Business error",
null
);
result.setErrorCode(ex.getErrorCode());
// 返回响应
return ResponseEntity.status(result码).body(result);
}
// 处理所有未处理的异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ResponseResult<Void>> handleAllExceptions(Exception ex) {
// 记录日志
log.error("Server error: {}", ex.getMessage(), ex);
// 构建错误响应
ResponseResult<Void> result = ResponseResult.fail(
500,
"Server error",
null
);
result.setErrorCode("SERVER_ERROR");
// 返回响应
return ResponseEntity.status(result码).body(result);
}
}
标准化响应格式应该包含以下关键信息:
-
状态码:一个整数,表示请求的状态,如200表示成功,400表示参数错误,500表示服务器内部错误等。
-
错误码:一个字符串,用于标识具体的错误类型,便于客户端进行错误分类和处理。
-
错误信息:一个简短的错误描述,通常用于前端显示。
-
错误详情:一个详细的错误描述,通常用于日志记录和调试。
-
业务数据:成功时返回的数据,失败时可以返回部分数据或null。
在实际开发中,可以根据项目的具体需求调整响应格式,例如添加国际化支持、时间戳、请求ID等信息。
3.4 异常信息的封装与返回
在全局异常处理器中,封装和返回异常信息是关键步骤。通过合理的封装,可以为客户端提供清晰、一致的错误信息,同时保护服务器的敏感信息不被泄露。
以下是封装和返回异常信息的示例:
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ResponseResult<ValidationResult>> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
// 记录日志
log.error("Validation error: {}", ex.getMessage(), ex);
// 构建验证结果
Map<String, String> errors = new HashMap<>();
ex.getBindingResult(). AllError().forEach(error -> {
errors.put(error场名, error默认信息);
});
// 构建响应结果
ResponseResult<ValidationResult> result = ResponseResult.fail(
400,
"Validation failed",
new ValidationResult(errors)
);
result.setErrorCode("VALIDATION_ERROR");
// 返回响应
return ResponseEntity.status(result码).body(result);
}
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ResponseResult<Void>> handleBusinessException(BusinessException ex) {
// 记录日志
log.error("Business error: {}", ex.getMessage(), ex);
// 构建响应结果
ResponseResult<Void> result = ResponseResult.fail(
500,
"Business error",
null
);
result.setErrorCode(ex.getErrorCode());
// 返回响应
return ResponseEntity.status(result码).body(result);
}
// 处理所有未处理的异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ResponseResult<Void>> handleAllExceptions(Exception ex) {
// 记录日志
log.error("Server error: {}", ex.getMessage(), ex);
// 构建响应结果
ResponseResult<Void> result = ResponseResult.fail(
500,
"Server error",
null
);
result.setErrorCode("SERVER_ERROR");
// 返回响应
return ResponseEntity.status(result码).body(result);
}
}
在封装异常信息时,需要注意以下几点:
-
信息过滤:避免将敏感信息(如数据库错误、服务器路径等)返回给客户端。
-
错误分类:通过错误码(errorCode)区分不同的错误类型,便于客户端进行错误分类和处理。
-
错误描述:提供清晰、简洁的错误信息,帮助用户理解问题所在。
-
错误详情:对于开发人员,提供详细的错误信息,便于调试和问题排查。
-
响应格式:根据项目的具体需求,选择合适的响应格式,如JSON、XML等。
-
HTTP状态码:根据异常类型,设置合适的HTTP状态码,如400表示参数错误,500表示服务器内部错误等。
对于参数校验异常(MethodArgumentNotValidException),可以提取所有字段的错误信息,并返回给客户端,便于用户修正输入。
第四章:实际开发中的最佳实践
4.1 异常分层策略与设计
在实际开发中,设计合理的异常分层策略非常重要。通过分层处理异常,可以将不同类型的异常归类处理,提高系统的可维护性和一致性。
以下是常见的异常分层策略:
-
基础层异常 :如
IOException
、SQLException
等,这些通常是底层框架或库抛出的异常。 -
业务层异常 :如
BusinessException
、ServiceException
等,这些是业务逻辑中抛出的异常,表示业务规则违反或业务操作失败。 -
控制层异常 :如
NullPointerException
、IllegalArgumentException
等,这些是控制器中抛出的异常,表示参数校验失败或方法执行错误。 -
框架层异常 :如
MethodArgumentNotValidException
、MissingServletRequestParameterException
等,这些是Spring框架抛出的异常。
异常分层的设计原则:
-
明确异常类型:为每种异常类型定义明确的错误码和错误信息。
-
异常传播:底层异常应该向上抛出,由上层进行处理和封装。
-
异常封装:上层异常应该封装底层异常的信息,但避免直接暴露技术细节。
-
异常分类:根据异常的严重程度和影响范围,将异常分为不同类别,如参数错误、业务错误、系统错误等。
-
异常处理:全局异常处理器应该能够处理所有类型的异常,并返回统一的响应格式。
以下是一个异常分层的示例:
// 基础异常
public class BaseException extends RuntimeException {
private final String errorCode;
private final String errorMsg;
public BaseException(StringerrorCode, String errorMsg) {
super(errorMsg);
this.errorCode =errorCode;
this.errorMsg = errorMsg;
}
// 获取错误码
public String getErrorCode() {
return this.errorCode;
}
// 获取错误信息
public String geterrorMsg() {
return this.errorMsg;
}
}
// 业务异常
public class BusinessException extends BaseException {
public BusinessException(StringerrorCode, String errorMsg) {
super(errorCode, errorMsg);
}
}
// 参数异常
public class ParamException extends BaseException {
public ParamException(StringerrorCode, String errorMsg) {
super(errorCode, errorMsg);
}
}
// 系统异常
public class SystemException extends BaseException {
public SystemException(StringerrorCode, String errorMsg) {
super(errorCode, errorMsg);
}
}
在实际开发中,可以通过异常的层次结构来指导全局异常处理器的处理逻辑,例如:
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理参数异常
@ExceptionHandler(ParamException.class)
public ResponseEntity<ErrorResult> handleParamException(ParamException ex) {
// 记录日志
log.error("Parameter error: {}", ex.getMessage(), ex);
// 构建错误响应
ErrorResult errorResult = new ErrorResult();
errorResult.errorCode = 400;
errorResult.errorMsg = "Invalid parameter";
// 返回响应
return ResponseEntity.status(HttpStatus BAD_REQUEST)
.body(errorResult);
}
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResult> handleBusinessException(BusinessException ex) {
// 记录日志
log.error("Business error: {}", ex.getMessage(), ex);
// 构建错误响应
ErrorResult errorResult = new ErrorResult();
errorResult.errorCode = 500;
errorResult.errorMsg = "Business error";
// 返回响应
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(errorResult);
}
// 处理系统异常
@ExceptionHandler(SystemException.class)
public ResponseEntity<ErrorResult> handleSystemException(SystemException ex) {
// 记录日志
log.error("System error: {}", ex.getMessage(), ex);
// 构建错误响应
ErrorResult errorResult = new ErrorResult();
errorResult.errorCode = 500;
errorResult.errorMsg = "System error";
// 返回响应
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(errorResult);
}
// 处理所有未处理的异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResult> handleAllExceptions(Exception ex) {
// 记录日志
log.error("Server error: {}", ex.getMessage(), ex);
// 构建错误响应
ErrorResult errorResult = new ErrorResult();
errorResult.errorCode = 500;
errorResult.errorMsg = "Server error";
// 返回响应
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(errorResult);
}
}
异常分层策略可以提高系统的可维护性和一致性,使得开发人员能够更容易地理解和处理系统中的异常情况。
4.2 统一响应格式的设计与实现
在实际开发中,设计一个统一的响应格式对于前后端协作非常重要。通过统一的响应格式,可以简化前端代码,提高系统的可维护性和一致性。
以下是设计一个统一响应格式的考虑因素:
-
成功响应:当请求成功时,返回的数据应该包含状态码、消息和业务数据。
-
失败响应:当请求失败时,返回的数据应该包含状态码、错误码、错误信息和可能的错误详情。
-
状态码:使用HTTP状态码表示请求的状态,如200表示成功,400表示参数错误,500表示服务器内部错误等。
-
错误码:定义项目特定的错误码,用于标识具体的错误类型,便于客户端进行错误分类和处理。
-
消息:提供简短的错误描述,通常用于前端显示。
-
错误详情:提供详细的错误信息,通常用于日志记录和调试。
-
数据:成功时返回的数据,失败时可以返回部分数据或null。
-
其他信息:根据需要,可以添加时间戳、请求ID等信息。
以下是实现统一响应格式的示例:
@Data
public class ResponseResult<T> {
// 状态码,200表示成功,非200表示失败
private int code;
// 错误码,用于标识具体的错误类型
private String error_code;
// 消息,用于前端显示
private String message;
// 数据,成功时返回
private T data;
// 成功响应的静态方法
public static <T> ResponseResult<T> success(T data) {
ResponseResult<T> result = new ResponseResult<>();
result.code = 200;
result.message = "success";
result.data = data;
return result;
}
// 失败响应的静态方法
public static <T> ResponseResult<T> fail(int code, String message, T data) {
ResponseResult<T> result = new ResponseResult<>();
result.code = code;
result.message = message;
result.data = data;
return result;
}
// 带错误码的失败响应的静态方法
public static <T> ResponseResult<T> fail(String error_code, int code, String message, T data) {
ResponseResult<T> result = new ResponseResult<>();
result.code = code;
result.message = message;
result.data = data;
result.setErrorCode(error_code);
return result;
}
}
在全局异常处理器中,可以返回这个统一的响应格式:
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理参数异常
@ExceptionHandler(ParamException.class)
public ResponseEntity<ResponseResult<Void>> handleParamException(ParamException ex) {
// 记录日志
log.error("Parameter error: {}", ex.getMessage(), ex);
// 构建响应结果
ResponseResult<Void> result = ResponseResult.fail(
"PARAMETER_ERROR",
400,
"Invalid parameter",
null
);
// 返回响应
return ResponseEntity.status(result码).body(result);
}
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ResponseResult<Void>> handleBusinessException(BusinessException ex) {
// 记录日志
log.error("Business error: {}", ex.getMessage(), ex);
// 构建响应结果
ResponseResult<Void> result = ResponseResult.fail(
ex.getErrorCode(),
500,
"Business error",
null
);
// 返回响应
return ResponseEntity.status(result码).body(result);
}
// 处理所有未处理的异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ResponseResult<Void>> handleAllExceptions(Exception ex) {
// 记录日志
log.error("Server error: {}", ex.getMessage(), ex);
// 构建响应结果
ResponseResult<Void> result = ResponseResult.fail(
"SERVER_ERROR",
0,
"Server error",
null
);
// 返回响应
return ResponseEntity.status(result码).body(result);
}
}
统一响应格式的设计应该考虑项目的具体需求,例如:
- 是否需要支持多端(Web、移动端、小程序等)。
- 是否需要支持国际化。
- 是否需要记录请求ID以便追踪问题。
- 是否需要返回时间戳。
在实际开发中,可以通过枚举类管理错误码和消息,提高代码的可维护性和一致性:
public enum ErrorEnum {
// 参数错误
PARAMETER_ERROR(400, " invalid parameter"),
// 业务错误
BUSINESS_ERROR(500, "business error"),
// 系统错误
SERVER_ERROR(500, "server error");
// 错误码
private final int code;
// 错误消息
private final String message;
ErrorEnum(int code, String message) {
this.code = code;
this.message = message;
}
// 获取错误码
public int getCode() {
return this.code;
}
// 获取错误消息
public StringgetMessage() {
return this.message;
}
}
在全局异常处理器中,可以使用这个枚举类:
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理参数异常
@ExceptionHandler(ParamException.class)
public ResponseEntity<ResponseResult<Void>> handleParamException(ParamException ex) {
// 记录日志
log.error("Parameter error: {}", ex.getMessage(), ex);
// 构建响应结果
ResponseResult<Void> result = ResponseResult.fail(
ErrorEnum.PARAMETER_ERROR,
ex.getMessage()
);
// 返回响应
return ResponseEntity.status(result码).body(result);
}
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ResponseResult<Void>> handleBusinessException(BusinessException ex) {
// 记录日志
log.error("Business error: {}", ex.getMessage(), ex);
// 构建响应结果
ResponseResult<Void> result = ResponseResult.fail(
ErrorEnum.BUSINESS_ERROR,
ex.getMessage()
);
// 返回响应
return ResponseEntity.status(result码).body(result);
}
// 处理所有未处理的异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ResponseResult<Void>> handleAllExceptions(Exception ex) {
// 记录日志
log.error("Server error: {}", ex.getMessage(), ex);
// 构建响应结果
ResponseResult<Void> result = ResponseResult.fail(
ErrorEnum服务器错误,
"Server error: " + ex.getMessage()
);
// 返回响应
return ResponseEntity.status(result码).body(result);
}
}
统一响应格式的设计和实现可以提高系统的可维护性和一致性,使得开发人员能够更容易地理解和处理系统中的异常情况。
4.3 与日志框架的集成方案
在全局异常处理中,与日志框架的集成非常重要。通过记录详细的异常信息,可以便于后续的调试和问题排查。
Spring Boot默认使用SLF4J作为日志门面,Logback作为日志实现。可以通过以下方式在全局异常处理器中集成日志框架:
-
使用SLF4J的logger:在全局异常处理器中注入logger,记录异常信息。
@RestControllerAdvice
public class GlobalExceptionHandler {// 使用SLF4J的logger private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); // 处理参数异常 @ExceptionHandler(ParamException.class) public ResponseEntity<ErrorResult> handleParamException(ParamException ex) { // 记录日志 log.error("Parameter error: {}", ex.getMessage(), ex); // 构建错误响应 ErrorResult errorResult = new ErrorResult(); errorResult码 = 400; errorResult息 = "Invalid parameter"; errorResult息详情 = ex.getMessage(); // 返回响应 return ResponseEntity.status(HttpStatus BAD_REQUEST) .body(errorResult); } // 处理业务异常 @ExceptionHandler(BusinessException.class) public ResponseEntity<ErrorResult> handleBusinessException(BusinessException ex) { // 记录日志