异常

@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

用于访问引发异常的控制器方法。

WebRequestNativeWebRequest

在不直接使用 Servlet API 的情况下,对请求参数以及请求和会话属性进行通用访问。

jakarta.servlet.ServletRequestjakarta.servlet.ServletResponse

选择任何特定的请求或响应类型(例如,ServletRequestHttpServletRequest 或 Spring 的MultipartRequestMultipartHttpServletRequest)。

jakarta.servlet.http.HttpSession

强制存在会话。因此,此类参数永远不会为null
请注意,会话访问不是线程安全的。如果允许多个请求同时访问会话,请考虑将RequestMappingHandlerAdapter 实例的synchronizeOnSession 标志设置为true

java.security.Principal

当前已认证的用户——如果已知,可能是特定Principal 实现类。

HttpMethod

请求的 HTTP 方法。

java.util.Locale

当前请求区域设置,由可用的最具体的LocaleResolver 确定——实际上,是配置的LocaleResolverLocaleContextResolver

java.util.TimeZonejava.time.ZoneId

与当前请求关联的时区,由LocaleContextResolver 确定。

java.io.OutputStreamjava.io.Writer

用于访问 Servlet API 公开的原始响应体。

java.util.Maporg.springframework.ui.Modelorg.springframework.ui.ModelMap

用于访问错误响应的模型。始终为空。

RedirectAttributes

指定在重定向情况下使用的属性——(即要附加到查询字符串)以及要临时存储直到重定向后的请求的闪存属性。参见 重定向属性闪存属性

@SessionAttribute

要访问任何会话属性,这与由于类级@SessionAttributes声明而存储在会话中的模型属性形成对比。有关更多详细信息,请参阅@SessionAttribute

@RequestAttribute

要访问请求属性。有关更多详细信息,请参阅@RequestAttribute

返回值

@ExceptionHandler方法支持以下返回值

返回值 描述

@ResponseBody

返回值通过HttpMessageConverter实例进行转换并写入响应。请参阅@ResponseBody

HttpEntity<B>, ResponseEntity<B>

返回值指定完整响应(包括HTTP标头和正文)通过HttpMessageConverter实例进行转换并写入响应。请参阅ResponseEntity

ErrorResponse

要使用正文中的详细信息呈现RFC 9457错误响应,请参阅错误响应

ProblemDetail

要使用正文中的详细信息呈现RFC 9457错误响应,请参阅错误响应

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时间戳检查,则也是如此(有关详细信息,请参阅控制器)。

如果以上都不适用,void返回类型还可以表示REST控制器的“无响应正文”或HTML控制器的默认视图名称选择。

任何其他返回值

如果返回值与上述任何一个都不匹配,并且不是简单类型(由BeanUtils#isSimpleProperty确定),则默认情况下,它被视为要添加到模型中的模型属性。如果它是简单类型,则它保持未解析状态。