SpringBoot中全局異常處理的5種實(shí)現(xiàn)方式小結(jié)
前言
在實(shí)際開發(fā)中,異常處理是一個(gè)非常重要的環(huán)節(jié)。合理的異常處理機(jī)制不僅能提高系統(tǒng)的健壯性,還能大大提升用戶體驗(yàn)。本文將詳細(xì)介紹SpringBoot中全局異常處理的幾種實(shí)現(xiàn)方式。
為什么需要全局異常處理?
如果沒(méi)有統(tǒng)一的異常處理機(jī)制,當(dāng)系統(tǒng)發(fā)生異常時(shí),可能會(huì)導(dǎo)致以下問(wèn)題
- 用戶體驗(yàn)差:用戶可能看到一些技術(shù)性的錯(cuò)誤信息,如堆棧跟蹤
- 安全隱患:暴露系統(tǒng)內(nèi)部錯(cuò)誤詳情可能會(huì)被攻擊者利用
- 維護(hù)困難:分散在各處的異常處理代碼增加了維護(hù)難度
- 響應(yīng)格式不一致:不同接口返回的錯(cuò)誤格式不統(tǒng)一,增加前端處理難度
下面,來(lái)看幾種在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)
- 代碼簡(jiǎn)潔清晰,易于維護(hù)
- 可以針對(duì)不同的異常類型定義不同的處理方法
- 與Spring MVC結(jié)合緊密,可以獲取請(qǐng)求和響應(yīng)上下文
方式二:實(shí)現(xiàn)HandlerExceptionResolver接口
HandlerExceptionResolver是Spring MVC中用于解析異常的接口,我們可以通過(guò)實(shí)現(xiàn)此接口來(lái)自定義異常處理邏輯。
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)
- 可以完全控制異常處理的過(guò)程
- 可以直接操作HttpServletRequest和HttpServletResponse
- 適合需要特殊處理的場(chǎng)景
缺點(diǎn)
- 代碼相對(duì)復(fù)雜
- 無(wú)法利用Spring MVC的注解優(yōu)勢(shì)
方式三:使用SimpleMappingExceptionResolver
SimpleMappingExceptionResolver是HandlerExceptionResolver的一個(gè)簡(jiǎn)單實(shí)現(xiàn),適用于返回錯(cuò)誤視圖的場(chǎng)景。
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ò)誤頁(yè)面 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ò)誤頁(yè)面模板(使用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)
- 配置簡(jiǎn)單
- 適合返回錯(cuò)誤頁(yè)面的場(chǎng)景
缺點(diǎn)
- 主要適用于返回視圖,不適合RESTful API
- 靈活性有限
方式四:自定義ErrorController
Spring Boot提供了BasicErrorController來(lái)處理應(yīng)用中的錯(cuò)誤,我們可以通過(guò)繼承或替換它來(lái)自定義錯(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 = "沒(méi)有權(quán)限訪問(wè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ò)誤頁(yè)面 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ò)誤,無(wú)法攔截自定義異常
- 一般作為兜底方案使用
方式五:使用錯(cuò)誤頁(yè)面模板
Spring Boot支持通過(guò)靜態(tài)HTML頁(yè)面或模板來(lái)展示特定狀態(tài)碼的錯(cuò)誤。只需要在templates/error/目錄下創(chuàng)建對(duì)應(yīng)狀態(tài)碼的頁(yè)面即可。
目錄結(jié)構(gòu):
src/
main/
resources/
templates/
error/
404.html
500.html
error.html # 默認(rèn)錯(cuò)誤頁(yè)面
例如,404.html的內(nèi)容:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>頁(yè)面不存在</title> </head> <body> <h1>404 - 頁(yè)面不存在</h1> <p>您請(qǐng)求的頁(yè)面不存在,請(qǐng)檢查URL是否正確。</p> <a href="/" rel="external nofollow" >返回首頁(yè)</a> </body> </html>
優(yōu)點(diǎn)
- 配置極其簡(jiǎn)單,只需創(chuàng)建對(duì)應(yīng)的頁(yè)面即可
- 適合簡(jiǎn)單的Web應(yīng)用
缺點(diǎn)
- 靈活性有限
- 不適合RESTful API
- 無(wú)法處理自定義異常
實(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("沒(méi)有權(quán)限查看此用戶"); } // 模擬查詢用戶 User user = new User(id, "用戶" + id, "user" + id + "@example.com"); return Result.success(user); } }
各方式對(duì)比與使用建議
實(shí)現(xiàn)方式 | 適用場(chǎng)景 | 靈活性 | 復(fù)雜度 |
---|---|---|---|
@ControllerAdvice + @ExceptionHandler | RESTful API、前后端分離項(xiàng)目 | 高 | 低 |
HandlerExceptionResolver | 需要精細(xì)控制異常處理過(guò)程的場(chǎng)景 | 高 | 中 |
SimpleMappingExceptionResolver | 傳統(tǒng)Web應(yīng)用,需要返回錯(cuò)誤頁(yè)面 | 中 | 低 |
自定義ErrorController | 需要自定義錯(cuò)誤頁(yè)面和錯(cuò)誤響應(yīng)的場(chǎng)景 | 中 | 中 |
錯(cuò)誤頁(yè)面模板 | 簡(jiǎn)單的Web應(yīng)用,只需自定義錯(cuò)誤頁(yè)面 | 低 | 低 |
建議
對(duì)于RESTful API或前后端分離項(xiàng)目:優(yōu)先選擇@ControllerAdvice/@RestControllerAdvice + @ExceptionHandler方式,它提供了良好的靈活性和簡(jiǎn)潔的代碼結(jié)構(gòu)。
對(duì)于需要返回錯(cuò)誤頁(yè)面的傳統(tǒng)Web應(yīng)用:可以使用SimpleMappingExceptionResolver或錯(cuò)誤頁(yè)面模板方式。
對(duì)于復(fù)雜系統(tǒng):可以組合使用多種方式,例如
- 使用@ControllerAdvice處理業(yè)務(wù)異常
- 使用自定義ErrorController處理未捕獲的HTTP錯(cuò)誤
- 使用錯(cuò)誤頁(yè)面模板提供友好的錯(cuò)誤頁(yè)面
最佳實(shí)踐總結(jié)
分層異常處理:根據(jù)業(yè)務(wù)需求,設(shè)計(jì)合理的異常繼承體系,便于分類處理。
統(tǒng)一返回格式:無(wú)論成功還是失敗,都使用統(tǒng)一的返回格式,便于前端處理。
合理記錄日志:在異常處理中記錄日志,可以幫助排查問(wèn)題。不同級(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é)語(yǔ)
在Spring Boot應(yīng)用中,全局異常處理是提高系統(tǒng)健壯性和用戶體驗(yàn)的重要環(huán)節(jié)。通過(guò)本文介紹的幾種實(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í)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12Springboot處理CORS跨域請(qǐng)求的三種方法
這篇文章主要介紹了Springboot處理CORS跨域請(qǐng)求的三種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06Java Socket編程(四) 重復(fù)和并發(fā)服務(wù)器
Java Socket編程(四) 重復(fù)和并發(fā)服務(wù)器...2006-12-12Mybatis自定義TypeHandler解決特殊類型轉(zhuǎn)換問(wèn)題詳解
這篇文章主要介紹了Mybatis自定義TypeHandler解決特殊類型轉(zhuǎn)換問(wèn)題詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11java內(nèi)存模型jvm虛擬機(jī)簡(jiǎn)要分析
Java 內(nèi)存模型的主要目的是定義程序中各種變量的訪問(wèn)規(guī)則, 關(guān)注在虛擬機(jī)中把變量值存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量值這樣的底層細(xì)節(jié)2021-09-09教你一步到位部署運(yùn)行MyBatis3源碼(保姆級(jí))
一個(gè)框架的運(yùn)行流程從最簡(jiǎn)單的一個(gè)helloworld來(lái)看其源碼就能了解到框架的原理是什么,這篇文章主要給大家介紹了關(guān)于如何一步到位部署運(yùn)行MyBatis3源碼的相關(guān)資料,需要的朋友可以參考下2022-06-06