異常

@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

在不直接使用 Servlet API 的情況下通用地訪問請求引數以及請求和會話屬性。

jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse

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

jakarta.servlet.http.HttpSession

強制會話存在。因此,這樣的引數永遠不會是 null
請注意,會話訪問不是執行緒安全的。如果允許多個請求併發訪問會話,請考慮將 RequestMappingHandlerAdapter 例項的 synchronizeOnSession 標誌設定為 true

java.security.Principal

當前已認證的使用者——如果已知,可能是特定的 Principal 實現類。

HttpMethod

請求的 HTTP 方法。

java.util.Locale

當前請求的 Locale,由最具體的 LocaleResolver 決定——實際上是配置的 LocaleResolverLocaleContextResolver

java.util.TimeZone, java.time.ZoneId

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

java.io.OutputStream, java.io.Writer

用於訪問 Servlet API 暴露的原始響應體。

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

用於訪問錯誤響應的模型。始終為空。

RedirectAttributes

指定在重定向時使用的屬性——(即附加到查詢字串的屬性)以及臨時儲存到重定向後的請求的 Flash 屬性。詳見Redirect AttributesFlash Attributes

@SessionAttribute

用於訪問任何會話屬性,與由於類級別的 @SessionAttributes 宣告而儲存在會話中的模型屬性不同。詳見 @SessionAttribute 以獲取更多詳細資訊。

@RequestAttribute

用於訪問請求屬性。詳見 @RequestAttribute 以獲取更多詳細資訊。

返回值

@ExceptionHandler 方法支援以下返回值

返回值 描述

@ResponseBody

返回值透過 HttpMessageConverter 例項轉換並寫入響應。詳見 @ResponseBody

HttpEntity<B>, ResponseEntity<B>

返回值指定將整個響應(包括 HTTP 頭和正文)透過 HttpMessageConverter 例項轉換並寫入響應。詳見 ResponseEntity

ErrorResponse

要渲染帶有正文詳細資訊的 RFC 9457 錯誤響應,詳見 Error Responses

ProblemDetail

要渲染帶有正文詳細資訊的 RFC 9457 錯誤響應,詳見 Error Responses

String

檢視名稱,將透過 ViewResolver 實現進行解析,並與隱式模型(透過命令物件和 @ModelAttribute 方法確定)一起使用。處理程式方法還可以透過宣告一個 Model 引數(前面已描述)來程式設計式地豐富模型。

View

一個 View 例項,用於與隱式模型(透過命令物件和 @ModelAttribute 方法確定)一起渲染。處理程式方法還可以透過宣告一個 Model 引數(前面已描述)來程式設計式地豐富模型。

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

要新增到隱式模型中的屬性,檢視名稱透過 RequestToViewNameTranslator 隱式確定。

@ModelAttribute

要新增到模型中的屬性,檢視名稱透過 RequestToViewNameTranslator 隱式確定。

請注意,@ModelAttribute 是可選的。詳見本表末尾的“任何其他返回值”。

ModelAndView 物件

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

void

如果一個方法的返回型別為 void(或返回值為 null),並且它具有 ServletResponseOutputStream 引數,或者帶有 @ResponseStatus 註解,則認為它已完全處理了響應。如果控制器進行了肯定的 ETaglastModified 時間戳檢查(詳見 Controllers),也是如此。

如果以上情況均不成立,對於 REST 控制器,void 返回型別也可以表示“沒有響應體”;對於 HTML 控制器,則表示預設檢視名稱選擇。

任何其他返回值

如果返回值與上述任何型別都不匹配,且不是簡單型別(由 BeanUtils#isSimpleProperty 確定),預設情況下,它會被視為要新增到模型的模型屬性。如果它是簡單型別,則保持未解析狀態。