Spring Boot 全局异常处理
在开发任何应用程序时,异常处理都是至关重要的。一个良好的异常处理机制不仅能提高用户体验,还能帮助开发者更好地定位和修复问题。Spring Boot 提供了强大的异常处理能力,使我们能够集中处理应用程序中抛出的各种异常,从而构建更健壮和可靠的系统。
本文将深入探讨 Spring Boot 中全局异常处理的最佳实践,包括如何创建全局异常处理器,如何自定义异常响应,以及如何处理不同类型的异常。
为什么需要全局异常处理?
- 避免用户看到丑陋的错误页面:未处理的异常会导致用户看到默认的错误页面,这不仅不友好,还可能暴露敏感信息。
- 统一异常响应格式:通过全局异常处理,可以统一应用程序的异常响应格式,方便客户端解析和处理错误。
- 简化代码逻辑:将异常处理逻辑集中到一个地方,可以减少在各个业务逻辑代码中重复编写异常处理代码。
- 提高系统健壮性:全局异常处理可以捕获应用程序中未知的异常,避免程序崩溃。
- 方便日志记录和监控:将异常集中处理,方便记录日志和进行监控,帮助开发者更好地了解系统运行情况。
Spring Boot 中的全局异常处理:
Spring Boot 提供了两个注解来实现全局异常处理:@ControllerAdvice
和 @ExceptionHandler
。
@ControllerAdvice
: 这个注解声明一个类为全局异常处理类,该类可以捕获所有 @Controller 中抛出的异常。
@RestControllerAdvice
:作用于所有使用了 @RestController 或者 @Controller + 方法级别 @ResponseBody 注解的类。
@ExceptionHandler
: 这个注解用于指定一个方法来处理特定的异常。
实践:创建全局异常处理器
1、定义自定义异常类: 首先,我们可以定义一些自定义异常类,用于表示业务上的异常,比如用户不存在,参数错误等
public class ServiceException extends RuntimeException{
/**
* 错误码
*/
private Integer code;
/**
* 错误提示
*/
private String message;
public ServiceException(String message){
this.message = message;
}
public ServiceException(Integer code, String message) {
this.message = message;
this.code = code;
}
public ServiceException(Status status, Object... statusParams){
this.code = status.getCode();
if(statusParams != null && statusParams.length > 0){
this.message = MessageFormat.format(status.getMsg(), statusParams);
} else {
this.message = status.getMsg();
}
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
2、创建全局异常处理类: 创建一个类,并使用 @ControllerAdvice 注解声明该类为全局异常处理类
import cn.hutool.core.util.ObjectUtil;
import com.extend.chk.domain.Result;
import com.extend.chk.domain.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 拦截业务异常
*/
@ExceptionHandler(ServiceException.class)
public Result handleServiceException(ServiceException e, HttpServletRequest request){
logger.error("请求地址:{}, 发生业务异常: {}", request.getRequestURI(), e.getMessage(), e);
Integer code = e.getCode();
String msg = e.getMessage();
return ObjectUtil.isNotNull(code) ? Result.error(code, msg) : Result.error(msg);
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public Result handleRuntimeException(RuntimeException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
logger.error("请求地址'{}',发生未知异常.", requestURI, e);
return Result.error(e.getMessage());
}
/**
* 拦截系统异常
*/
@ExceptionHandler(Exception.class)
public Result handleException(Exception e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
logger.error("请求地址'{}',发生系统异常.", requestURI, e);
return Result.error(e.getMessage());
}
}
@ExceptionHandler(ServiceException.class)
:指定handleServiceException方法处理 ServiceException类型的异常。@ExceptionHandler(RuntimeException.class)
:指定handleRuntimeException方法处理 RuntimeException类型的异常。@ExceptionHandler(Exception.class)
:指定 handleException方法处理所有未被特定处理的异常, 并记录日志。
3、Controller 中抛出异常: 在 Controller 中抛出自定义的异常,测试全局异常处理是否生效。
@RestController
@RequestMapping("/yes")
public class ExtendController extends BaseController {
@GetMapping("/users/{id}")
public Integer getUser(@PathVariable int id) {
if (id == 1) {
throw new ServiceException("业务异常");
} else if (id == 2) {
throw new RuntimeException("非检查型异常");
} else {
int a = 1 / 0;
return a;
}
}
}
- 当 id 为 1 时,抛出 ServiceException异常。 {“code”: 10000,“msg”: “业务异常”,“data”: null}
- 当 id 为 2 时,抛出 RuntimeException异常。 {“code”: 10000,“msg”: “非检查型异常”,“data”: null}
- 当 id 为其他值时,抛出 Exception 异常。 {“code”: 10000,“msg”: “/ by zero”,“data”: null}
最佳实践
- 针对不同异常类型制定不同的处理策略: 不同的异常和前端约定不同的返回方式。
- 不要暴露敏感信息:在异常响应中,不要暴露系统内部的敏感信息,比如数据库连接字符串等。
- 记录日志: 将异常信息记录到日志中,方便问题追踪。
- 统一响应格式:保持一致的响应格式,方便客户端处理。
- 提供友好的错误信息: 提供给用户的错误信息应该易于理解,而不是技术术语。