异常

@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

当前请求区域设置,由最具体的 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 属性,直到重定向后的请求。请参阅重定向属性Flash 属性

@SessionAttribute

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

@RequestAttribute

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

返回值

@ExceptionHandler 方法支持以下返回值

返回值 描述

@ResponseBody

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

HttpEntity<B>, ResponseEntity<B>

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

ErrorResponse, ProblemDetail

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

字符串

要通过 ViewResolver 实现解析并与隐式模型(通过命令对象和 @ModelAttribute 方法确定)一起使用的视图名称。处理程序方法还可以通过声明 Model 参数(前面已描述)来编程地丰富模型。

View

要与隐式模型(通过命令对象和 @ModelAttribute 方法确定)一起渲染的 View 实例。处理程序方法还可以通过声明 Model 参数(前面已描述)来编程地丰富模型。

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

要添加到隐式模型的属性,视图名称通过 RequestToViewNameTranslator 隐式确定。

@ModelAttribute

要添加到模型的属性,视图名称通过 RequestToViewNameTranslator 隐式确定。

请注意,@ModelAttribute 是可选的。请参阅此表末尾的“任何其他返回值”。

ModelAndView 对象

要使用的视图和模型属性,以及可选的响应状态。

void

如果一个方法具有 void 返回类型(或 null 返回值),并且它也具有 ServletResponseOutputStream 参数或 @ResponseStatus 注解,则认为它已完全处理了响应。如果控制器已进行了积极的 ETaglastModified 时间戳检查(有关详细信息,请参阅控制器),则也适用。

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

任何其他返回值

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

© . This site is unofficial and not affiliated with VMware.