例外處理

@Controller@ControllerAdvice 類別可以有 @ExceptionHandler 方法來處理控制器方法中的例外,如下列範例所示

  • Java

  • Kotlin

import java.io.IOException;

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;

@Controller
public class SimpleController {

	@ExceptionHandler(IOException.class)
	public ResponseEntity<String> handle() {
		return ResponseEntity.internalServerError().body("Could not read file storage");
	}

}
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.ExceptionHandler
import java.io.IOException

@Controller
class SimpleController {

	@ExceptionHandler(IOException::class)
	fun handle() : ResponseEntity<String> {
		return ResponseEntity.internalServerError().body("Could not read file storage")
	}
	
}

例外映射

例外可以比對正在傳播的頂層例外(例如,直接拋出的 IOException)或封裝例外內的巢狀原因(例如,封裝在 IllegalStateException 內的 IOException)。從 5.3 開始,這可以在任意原因層級進行比對,而先前僅考慮直接原因。

對於比對例外類型,建議將目標例外宣告為方法引數,如先前的範例所示。當多個例外方法比對時,根例外比對通常優先於原因例外比對。更具體地說,ExceptionDepthComparator 用於根據例外與拋出例外類型的深度對例外進行排序。

或者,註解宣告可以縮小要比對的例外類型,如下列範例所示

  • Java

  • Kotlin

@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handleIoException(IOException ex) {
	return ResponseEntity.internalServerError().body(ex.getMessage());
}
@ExceptionHandler(FileSystemException::class, RemoteException::class)
fun handleIoException(ex: IOException): ResponseEntity<String> {
	return ResponseEntity.internalServerError().body(ex.message)
}

您甚至可以使用特定例外類型的列表以及非常通用的引數簽名,如下列範例所示

  • Java

  • Kotlin

@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handleExceptions(Exception ex) {
	return ResponseEntity.internalServerError().body(ex.getMessage());
}
@ExceptionHandler(FileSystemException::class, RemoteException::class)
fun handleExceptions(ex: Exception): ResponseEntity<String> {
	return ResponseEntity.internalServerError().body(ex.message)
}

根例外比對與原因例外比對之間的區別可能令人驚訝。

在先前顯示的 IOException 變體中,該方法通常以實際的 FileSystemExceptionRemoteException 實例作為引數來呼叫,因為它們都從 IOException 擴展而來。但是,如果任何此類比對例外在本身為 IOException 的封裝例外內傳播,則傳入的例外實例是該封裝例外。

handle(Exception) 變體中,行為甚至更簡單。在封裝情境中,這始終以封裝例外調用,在這種情況下,實際比對的例外可以在 ex.getCause() 中找到。僅當這些例外作為頂層例外拋出時,傳入的例外才是實際的 FileSystemExceptionRemoteException 實例。

我們通常建議您在引數簽名中盡可能具體,以減少根例外類型與原因例外類型之間不匹配的可能性。考慮將多個比對方法分解為個別的 @ExceptionHandler 方法,每個方法都透過其簽名比對單一特定例外類型。

在多個 @ControllerAdvice 配置中,我們建議在具有對應順序優先級的 @ControllerAdvice 上宣告主要根例外映射。雖然根例外比對優先於原因例外比對,但這是在給定控制器或 @ControllerAdvice 類別的方法之間定義的。這表示,與較低優先級 @ControllerAdvice Bean 上的任何比對(例如,根)相比,較高優先級 @ControllerAdvice Bean 上的原因比對更受偏好。

最後但並非最不重要的一點,@ExceptionHandler 方法實作可以選擇透過以原始形式重新拋出給定的例外實例,來退出處理。這在您僅對根層級比對或無法靜態判定的特定情境中的比對感興趣的情境中很有用。重新拋出的例外會透過剩餘的解析鏈傳播,就好像給定的 @ExceptionHandler 方法一開始就沒有比對到一樣。

Spring MVC 中對 @ExceptionHandler 方法的支援建立在 DispatcherServlet 層級,HandlerExceptionResolver 機制之上。

媒體類型映射

除了例外類型之外,@ExceptionHandler 方法還可以宣告可產生的媒體類型。這允許根據 HTTP 用戶端請求的媒體類型(通常在 "Accept" HTTP 請求標頭中)來細化錯誤回應。

應用程式可以直接在註解上宣告可產生的媒體類型,以用於相同的例外類型

  • Java

  • Kotlin

@ExceptionHandler(produces = "application/json")
public ResponseEntity<ErrorMessage> handleJson(IllegalArgumentException exc) {
	return ResponseEntity.badRequest().body(new ErrorMessage(exc.getMessage(), 42));
}

@ExceptionHandler(produces = "text/html")
public String handle(IllegalArgumentException exc, Model model) {
	model.addAttribute("error", new ErrorMessage(exc.getMessage(), 42));
	return "errorView";
}
@ExceptionHandler(produces = ["application/json"])
fun handleJson(exc: IllegalArgumentException): ResponseEntity<ErrorMessage> {
	return ResponseEntity.badRequest().body(ErrorMessage(exc.message, 42))
}

@ExceptionHandler(produces = ["text/html"])
fun handle(exc: IllegalArgumentException, model: Model): String {
	model.addAttribute("error", ErrorMessage(exc.message, 42))
	return "errorView"
}

在此,方法處理相同的例外類型,但不會因重複而被拒絕。相反地,請求 "application/json" 的 API 用戶端將收到 JSON 錯誤,而瀏覽器將取得 HTML 錯誤視圖。每個 @ExceptionHandler 註解都可以宣告多種可產生的媒體類型,錯誤處理階段的內容協商將決定將使用哪種內容類型。

方法引數

@ExceptionHandler 方法支援下列引數

方法引數 描述

例外類型

用於存取引發的例外。

HandlerMethod

用於存取引發例外的控制器方法。

WebRequest, NativeWebRequest

通用存取請求參數以及請求和 Session 屬性,而無需直接使用 Servlet API。

jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse

選擇任何特定的請求或回應類型(例如,ServletRequestHttpServletRequest 或 Spring 的 MultipartRequestMultipartHttpServletRequest)。

jakarta.servlet.http.HttpSession

強制 Session 的存在。因此,此類引數永遠不會是 null
請注意,Session 存取不是執行緒安全的。如果允許多個請求同時存取 Session,請考慮將 RequestMappingHandlerAdapter 實例的 synchronizeOnSession 旗標設定為 true

java.security.Principal

目前已驗證的使用者 — 如果已知,則可能是特定的 Principal 實作類別。

HttpMethod

請求的 HTTP 方法。

java.util.Locale

目前請求地區設定,由最特定的可用 LocaleResolver 決定 — 實際上是已組態的 LocaleResolverLocaleContextResolver

java.util.TimeZone, java.time.ZoneId

與目前請求關聯的時區,由 LocaleContextResolver 決定。

java.io.OutputStream, java.io.Writer

用於存取 Servlet API 公開的原始回應 Body。

java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap

用於存取錯誤回應的模型。永遠為空。

RedirectAttributes

指定在重新導向時要使用的屬性 — (即要附加到查詢字串)以及要暫時儲存直到重新導向後的請求的 Flash 屬性。請參閱 重新導向屬性Flash 屬性

@SessionAttribute

用於存取任何 Session 屬性,與作為類別層級 @SessionAttributes 宣告結果儲存在 Session 中的模型屬性形成對比。有關更多詳細資訊,請參閱 @SessionAttribute

@RequestAttribute

用於存取請求屬性。有關更多詳細資訊,請參閱 @RequestAttribute

傳回值

@ExceptionHandler 方法支援下列傳回值

傳回值 描述

@ResponseBody

傳回值透過 HttpMessageConverter 實例轉換並寫入回應。請參閱 @ResponseBody

HttpEntity<B>, ResponseEntity<B>

傳回值指定要透過 HttpMessageConverter 實例轉換並寫入回應的完整回應(包括 HTTP 標頭和 Body)。請參閱 ResponseEntity

ErrorResponse

若要呈現 RFC 9457 錯誤回應,並在 Body 中包含詳細資訊,請參閱 錯誤回應

ProblemDetail

若要呈現 RFC 9457 錯誤回應,並在 Body 中包含詳細資訊,請參閱 錯誤回應

字串

要使用 ViewResolver 實作解析的視圖名稱,並與隱含模型一起使用 — 透過命令物件和 @ModelAttribute 方法決定。處理器方法也可以透過宣告 Model 引數(先前描述)以程式化方式豐富模型。

View

要與隱含模型一起使用的 View 實例,以進行呈現 — 透過命令物件和 @ModelAttribute 方法決定。處理器方法也可以透過宣告 Model 引數(先前描述)以程式化方式豐富模型。

java.util.Map, org.springframework.ui.Model

要新增至隱含模型的屬性,視圖名稱透過 RequestToViewNameTranslator 隱含決定。

@ModelAttribute

要新增至模型的屬性,視圖名稱透過 RequestToViewNameTranslator 隱含決定。

請注意,@ModelAttribute 是選用的。請參閱此表格末尾的「任何其他傳回值」。

ModelAndView 物件

要使用的視圖和模型屬性,以及可選的回應狀態。

void

如果方法同時具有 ServletResponseOutputStream 引數或 @ResponseStatus 註解,則傳回類型為 void(或 null 傳回值)的方法會被視為已完全處理回應。如果控制器已進行正向 ETaglastModified 時間戳記檢查,情況也是如此(有關詳細資訊,請參閱 控制器)。

如果以上皆非,則對於 REST 控制器,void 傳回類型也可以表示「無回應 Body」,對於 HTML 控制器,則表示預設視圖名稱選擇。

任何其他傳回值

如果傳回值與以上任何一項都不匹配,並且不是簡單類型(由 BeanUtils#isSimpleProperty 決定),則預設情況下,它會被視為要新增至模型的模型屬性。如果是簡單類型,則保持未解析狀態。