SpringBoot中全局異常處理的5種實(shí)現(xiàn)方式小結(jié)
前言
在實(shí)際開發(fā)中,異常處理是一個(gè)非常重要的環(huán)節(jié)。合理的異常處理機(jī)制不僅能提高系統(tǒng)的健壯性,還能大大提升用戶體驗(yàn)。本文將詳細(xì)介紹SpringBoot中全局異常處理的幾種實(shí)現(xiàn)方式。
為什么需要全局異常處理?
如果沒有統(tǒng)一的異常處理機(jī)制,當(dāng)系統(tǒng)發(fā)生異常時(shí),可能會(huì)導(dǎo)致以下問題
- 用戶體驗(yàn)差:用戶可能看到一些技術(shù)性的錯(cuò)誤信息,如堆棧跟蹤
- 安全隱患:暴露系統(tǒng)內(nèi)部錯(cuò)誤詳情可能會(huì)被攻擊者利用
- 維護(hù)困難:分散在各處的異常處理代碼增加了維護(hù)難度
- 響應(yīng)格式不一致:不同接口返回的錯(cuò)誤格式不統(tǒng)一,增加前端處理難度
下面,來看幾種在SpringBoot中實(shí)現(xiàn)全局異常處理的方式。
方式一:@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler
這是SpringBoot中最常用的全局異常處理方式。
首先,定義一個(gè)統(tǒng)一的返回結(jié)果類:
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage("操作成功");
result.setData(data);
return result;
}
public static <T> Result<T> error(Integer code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
return result;
}
// getter和setter方法省略
}
然后,定義自定義異常:
public class BusinessException extends RuntimeException {
private Integer code;
public BusinessException(String message) {
super(message);
this.code = 500;
}
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
public Integer getCode() {
return code;
}
}
創(chuàng)建全局異常處理類:
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 處理自定義業(yè)務(wù)異常
*/
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusinessException(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}
/**
* 處理參數(shù)校驗(yàn)異常
*/
@ExceptionHandler(ConstraintViolationException.class)
public Result<Void> handleValidationException(ConstraintViolationException e) {
return Result.error(400, "參數(shù)校驗(yàn)失敗:" + e.getMessage());
}
/**
* 處理資源找不到異常
*/
@ExceptionHandler(NoHandlerFoundException.class)
public Result<Void> handleNotFoundException(NoHandlerFoundException e) {
return Result.error(404, "請(qǐng)求的資源不存在");
}
/**
* 處理其他所有未捕獲的異常
*/
@ExceptionHandler(Exception.class)
public Result<Void> handleException(Exception e) {
return Result.error(500, "服務(wù)器內(nèi)部錯(cuò)誤:" + e.getMessage());
}
}
優(yōu)點(diǎn)
- 代碼簡潔清晰,易于維護(hù)
- 可以針對(duì)不同的異常類型定義不同的處理方法
- 與Spring MVC結(jié)合緊密,可以獲取請(qǐng)求和響應(yīng)上下文
方式二:實(shí)現(xiàn)HandlerExceptionResolver接口
HandlerExceptionResolver是Spring MVC中用于解析異常的接口,我們可以通過實(shí)現(xiàn)此接口來自定義異常處理邏輯。
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@Component
public class CustomExceptionResolver implements HandlerExceptionResolver {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
Result<?> result;
// 處理不同類型的異常
if (ex instanceof BusinessException) {
BusinessException businessException = (BusinessException) ex;
result = Result.error(businessException.getCode(), businessException.getMessage());
} else {
result = Result.error(500, "服務(wù)器內(nèi)部錯(cuò)誤:" + ex.getMessage());
}
// 設(shè)置響應(yīng)類型和狀態(tài)碼
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_OK);
try {
// 將錯(cuò)誤信息寫入響應(yīng)
PrintWriter writer = response.getWriter();
writer.write(objectMapper.writeValueAsString(result));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
// 返回空的ModelAndView表示異常已經(jīng)處理完成
return new ModelAndView();
}
}
優(yōu)點(diǎn)
- 可以完全控制異常處理的過程
- 可以直接操作HttpServletRequest和HttpServletResponse
- 適合需要特殊處理的場景
缺點(diǎn)
- 代碼相對(duì)復(fù)雜
- 無法利用Spring MVC的注解優(yōu)勢(shì)
方式三:使用SimpleMappingExceptionResolver
SimpleMappingExceptionResolver是HandlerExceptionResolver的一個(gè)簡單實(shí)現(xiàn),適用于返回錯(cuò)誤視圖的場景。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import java.util.Properties;
@Configuration
public class ExceptionConfig {
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
// 設(shè)置默認(rèn)錯(cuò)誤頁面
resolver.setDefaultErrorView("error/default");
// 設(shè)置異常映射
Properties mappings = new Properties();
mappings.setProperty(BusinessException.class.getName(), "error/business");
mappings.setProperty(RuntimeException.class.getName(), "error/runtime");
resolver.setExceptionMappings(mappings);
// 設(shè)置異常屬性名,默認(rèn)為"exception"
resolver.setExceptionAttribute("ex");
return resolver;
}
}
對(duì)應(yīng)的錯(cuò)誤頁面模板(使用Thymeleaf):
<!-- templates/error/business.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>業(yè)務(wù)錯(cuò)誤</title>
</head>
<body>
<h1>業(yè)務(wù)錯(cuò)誤</h1>
<p th:text="${ex.message}">錯(cuò)誤信息</p>
</body>
</html>
優(yōu)點(diǎn)
- 配置簡單
- 適合返回錯(cuò)誤頁面的場景
缺點(diǎn)
- 主要適用于返回視圖,不適合RESTful API
- 靈活性有限
方式四:自定義ErrorController
Spring Boot提供了BasicErrorController來處理應(yīng)用中的錯(cuò)誤,我們可以通過繼承或替換它來自定義錯(cuò)誤處理邏輯。
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class CustomErrorController implements ErrorController {
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<Result<Void>> handleError(HttpServletRequest request) {
HttpStatus status = getStatus(request);
Integer statusCode = status.value();
String message = "未知錯(cuò)誤";
switch (statusCode) {
case 404:
message = "請(qǐng)求的資源不存在";
break;
case 403:
message = "沒有權(quán)限訪問該資源";
break;
case 500:
message = "服務(wù)器內(nèi)部錯(cuò)誤";
break;
default:
break;
}
return new ResponseEntity<>(Result.error(statusCode, message), status);
}
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public String handleErrorHtml(HttpServletRequest request, Map<String, Object> model) {
HttpStatus status = getStatus(request);
// 添加錯(cuò)誤信息到模型
model.put("status", status.value());
model.put("message", status.getReasonPhrase());
// 返回錯(cuò)誤頁面
return "error/error";
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
try {
return HttpStatus.valueOf(statusCode);
} catch (Exception ex) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}
}
優(yōu)點(diǎn)
- 可以同時(shí)處理HTML和JSON響應(yīng)
- 對(duì)所有未處理的錯(cuò)誤提供統(tǒng)一的處理方式
- 可以獲取錯(cuò)誤的狀態(tài)碼和詳細(xì)信息
缺點(diǎn)
- 只能處理已經(jīng)發(fā)生的HTTP錯(cuò)誤,無法攔截自定義異常
- 一般作為兜底方案使用
方式五:使用錯(cuò)誤頁面模板
Spring Boot支持通過靜態(tài)HTML頁面或模板來展示特定狀態(tài)碼的錯(cuò)誤。只需要在templates/error/目錄下創(chuàng)建對(duì)應(yīng)狀態(tài)碼的頁面即可。
目錄結(jié)構(gòu):
src/
main/
resources/
templates/
error/
404.html
500.html
error.html # 默認(rèn)錯(cuò)誤頁面
例如,404.html的內(nèi)容:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>頁面不存在</title>
</head>
<body>
<h1>404 - 頁面不存在</h1>
<p>您請(qǐng)求的頁面不存在,請(qǐng)檢查URL是否正確。</p>
<a href="/" rel="external nofollow" >返回首頁</a>
</body>
</html>
優(yōu)點(diǎn)
- 配置極其簡單,只需創(chuàng)建對(duì)應(yīng)的頁面即可
- 適合簡單的Web應(yīng)用
缺點(diǎn)
- 靈活性有限
- 不適合RESTful API
- 無法處理自定義異常
實(shí)戰(zhàn)示例:完整的異常處理體系
下面提供一個(gè)完整的異常處理體系示例,組合了多種方式:
首先,創(chuàng)建異常體系:
// 基礎(chǔ)異常類
public abstract class BaseException extends RuntimeException {
private final int code;
public BaseException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}
// 業(yè)務(wù)異常
public class BusinessException extends BaseException {
public BusinessException(String message) {
super(400, message);
}
public BusinessException(int code, String message) {
super(code, message);
}
}
// 系統(tǒng)異常
public class SystemException extends BaseException {
public SystemException(String message) {
super(500, message);
}
public SystemException(int code, String message) {
super(code, message);
}
}
// 權(quán)限異常
public class PermissionException extends BaseException {
public PermissionException(String message) {
super(403, message);
}
}
創(chuàng)建全局異常處理器:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 處理自定義基礎(chǔ)異常
*/
@ExceptionHandler(BaseException.class)
public Result<?> handleBaseException(BaseException e) {
logger.error("業(yè)務(wù)異常:{}", e.getMessage());
return Result.error(e.getCode(), e.getMessage());
}
/**
* 處理參數(shù)校驗(yàn)異常(@Valid)
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
String errorMsg = fieldErrors.stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));
logger.error("參數(shù)校驗(yàn)錯(cuò)誤:{}", errorMsg);
return Result.error(400, "參數(shù)校驗(yàn)錯(cuò)誤: " + errorMsg);
}
/**
* 處理所有其他異常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<?> handleException(Exception e) {
logger.error("系統(tǒng)異常:", e);
return Result.error(500, "服務(wù)器內(nèi)部錯(cuò)誤,請(qǐng)聯(lián)系管理員");
}
}
使用示例:
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/{id}")
public Result<User> getUser(@PathVariable Long id) {
if (id <= 0) {
throw new BusinessException("用戶ID必須大于0");
}
if (id > 100) {
throw new SystemException("系統(tǒng)維護(hù)中");
}
if (id == 10) {
throw new PermissionException("沒有權(quán)限查看此用戶");
}
// 模擬查詢用戶
User user = new User(id, "用戶" + id, "user" + id + "@example.com");
return Result.success(user);
}
}
各方式對(duì)比與使用建議
| 實(shí)現(xiàn)方式 | 適用場景 | 靈活性 | 復(fù)雜度 |
|---|---|---|---|
| @ControllerAdvice + @ExceptionHandler | RESTful API、前后端分離項(xiàng)目 | 高 | 低 |
| HandlerExceptionResolver | 需要精細(xì)控制異常處理過程的場景 | 高 | 中 |
| SimpleMappingExceptionResolver | 傳統(tǒng)Web應(yīng)用,需要返回錯(cuò)誤頁面 | 中 | 低 |
| 自定義ErrorController | 需要自定義錯(cuò)誤頁面和錯(cuò)誤響應(yīng)的場景 | 中 | 中 |
| 錯(cuò)誤頁面模板 | 簡單的Web應(yīng)用,只需自定義錯(cuò)誤頁面 | 低 | 低 |
建議
對(duì)于RESTful API或前后端分離項(xiàng)目:優(yōu)先選擇@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler方式,它提供了良好的靈活性和簡潔的代碼結(jié)構(gòu)。
對(duì)于需要返回錯(cuò)誤頁面的傳統(tǒng)Web應(yīng)用:可以使用SimpleMappingExceptionResolver或錯(cuò)誤頁面模板方式。
對(duì)于復(fù)雜系統(tǒng):可以組合使用多種方式,例如
- 使用@ControllerAdvice處理業(yè)務(wù)異常
- 使用自定義ErrorController處理未捕獲的HTTP錯(cuò)誤
- 使用錯(cuò)誤頁面模板提供友好的錯(cuò)誤頁面
最佳實(shí)踐總結(jié)
分層異常處理:根據(jù)業(yè)務(wù)需求,設(shè)計(jì)合理的異常繼承體系,便于分類處理。
統(tǒng)一返回格式:無論成功還是失敗,都使用統(tǒng)一的返回格式,便于前端處理。
合理記錄日志:在異常處理中記錄日志,可以幫助排查問題。不同級(jí)別的異常使用不同級(jí)別的日志。
區(qū)分開發(fā)和生產(chǎn)環(huán)境:在開發(fā)環(huán)境可以返回詳細(xì)的錯(cuò)誤信息,而在生產(chǎn)環(huán)境則應(yīng)該隱藏敏感信息。
異常分類
- 業(yè)務(wù)異常:用戶操作引起的可預(yù)期異常
- 系統(tǒng)異常:系統(tǒng)內(nèi)部錯(cuò)誤
- 第三方服務(wù)異常:調(diào)用外部服務(wù)失敗等
不要忽略異常:即使是捕獲異常后不需要處理,也應(yīng)該至少記錄日志。
結(jié)語
在Spring Boot應(yīng)用中,全局異常處理是提高系統(tǒng)健壯性和用戶體驗(yàn)的重要環(huán)節(jié)。通過本文介紹的幾種實(shí)現(xiàn)方式,開發(fā)者可以根據(jù)實(shí)際需求選擇合適的實(shí)現(xiàn)方案。在實(shí)際項(xiàng)目中,往往需要結(jié)合多種方式,構(gòu)建一個(gè)完整的異常處理體系。
以上就是SpringBoot中全局異常處理的5種實(shí)現(xiàn)方式小結(jié)的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot全局異常處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Springcloud Nacos基本操作代碼實(shí)例
這篇文章主要介紹了Springcloud Nacos基本操作代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12
Springboot處理CORS跨域請(qǐng)求的三種方法
這篇文章主要介紹了Springboot處理CORS跨域請(qǐng)求的三種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
Java Socket編程(四) 重復(fù)和并發(fā)服務(wù)器
Java Socket編程(四) 重復(fù)和并發(fā)服務(wù)器...2006-12-12
Mybatis自定義TypeHandler解決特殊類型轉(zhuǎn)換問題詳解
這篇文章主要介紹了Mybatis自定義TypeHandler解決特殊類型轉(zhuǎn)換問題詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
教你一步到位部署運(yùn)行MyBatis3源碼(保姆級(jí))
一個(gè)框架的運(yùn)行流程從最簡單的一個(gè)helloworld來看其源碼就能了解到框架的原理是什么,這篇文章主要給大家介紹了關(guān)于如何一步到位部署運(yùn)行MyBatis3源碼的相關(guān)資料,需要的朋友可以參考下2022-06-06

