异常

@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 确定),默认情况下,它会被视为要添加到模型的模型属性。如果它是简单类型,则保持未解析状态。