高级话题—Exception Handling in Spring MVC
程序员文章站
2022-06-08 22:06:14
...
高级话题之—Exception Handling in Spring MVC
Spring MVC provides several complimentary approaches to exception handling but, when teaching Spring MVC, I often find that my students are confused or not comfortable with them.
【Spring MVC 提供了几种免费的异常处理方式,但在我教授Spring MVC时,我发现我的学生往往对这几种处理方式感到迷惑并且不能够很舒服的使用它们。】
Today I’m going to show you the various options available. Our goal is to not handle exceptions explicitly in Controller methods where possible. They are a cross-cutting concern better handled separately in dedicated code.
【今天我将为你展示这几种不同的异常处理选项。我的目标是尽可能的不在Controller 方法中显示处理异常。将异常处理作为一个横切关注的问题,在专用的代码中单独处理会更好。】
There are three options: per exception, per controller or globally.
【有三个选项:每个异常处理、每个控制器处理或全局处理。】
A demonstration application that shows the points discussed here can be found at
http://github.com/paulc4/mvc-exceptions. See Sample Application below for details.
【在这里我为大家提供了一个演示示例用于展示上述讨论的关注点,你要以在http://github.com/paulc4/mvc-exceptions这里找到示例。参见下述的Sample Application以了解更多的细节。】
NOTE: The demo applications has been revamped and updated (April 2018) to use Spring Boot 2.0.1 and is (hopefully) easier to use and understand. I also fixed some broken links (thanks for the feedback, sorry it took a while).
【注意:该示例在2018年4月经过了使用Spring Boot 2.0.1重构以更方便使用和理解。我还修复了一些断开的链接(感谢大家的反馈,很抱歉花了一段时间)。】
Spring Boot
Spring Boot allows a Spring project to be setup with minimal configuration and it is likely that you are using it if your application is less than a few years old.
【SpringBoot允许使用最少的配置来设置Spring项目,并且如果你的应用程序还不到几年,那么你很可能正在使用它。】
Spring MVC offers no default (fall-back) error page out-of-the-box. The most common way to set a default error page has always been the SimpleMappingExceptionResolver (since Spring V1 in fact). We will discuss this later.
【Spring MVC不提供默认(回退)的错误页面。设置默认错误页面的最常见方法是使用SimpleMappingExceptionResolver(实际上从Spring v1开始)。我们稍后再讨论它。】
However Spring Boot does provide for a fallback error-handling page.
【然而Spring Boot提供一个回退错误处理页面。】
At start-up, Spring Boot tries to find a mapping for /error. By convention, a URL ending in /error maps to a logical view of the same name: error. In the demo application this view maps in turn to the error.html Thymeleaf template. (If using JSP, it would map to error.jsp according to the setup of your InternalResourceViewResolver). The actual mapping will depend on what ViewResolver (if any) that you or Spring Boot has setup.
【在Spring Boot启动时,它会尝试查找/error的映射。按照惯例,以/error结尾的URL映射到具有相同名称的逻辑视图:error。在演示应用程序中,此视图依次映射到了thymeleaf模板error.html。(如果使用JSP的话,Spring Boot将根据InternalResourceViewResolver的设置映射到error.jsp)。如果有的话,实际的映射将取决于你或Spring Boot设置的ViewResolver】
If no view-resolver mapping for /error can be found, Spring Boot defines its own fall-back error page - the so-called “Whitelabel Error Page” (a minimal page with just the HTTP status information and any error details, such as the message from an uncaught exception). In the sample applicaiton, if you rename the error.html template to, say, error2.html then restart, you will see it being used.
【如果Spring Boot找不到/error的视图解析器映射,那么Spring Boot定义了自己的回退错误页面,即所谓的“WhiteLabel错误页”(一个最小的页,只包含HTTP状态信息和任何错误详细信息,例如来自未捕获异常的消息)。在示例应用程序中,如果将error.html模板重命名为,比如,error2.html,然后重新启动,您将看到WhiteLabel错误页面被使用。】
If you are making a RESTful request (the HTTP request has specified a desired response type other than HTML) Spring Boot returns a JSON representation of the same error information that it puts in the “Whitelabel” error page.
【如果你正在进行一个RESTful请求(HTTP请求指定了一个所需的响应类型,而不是HTML),那么Spring Boot将返回一个JSON表示,该表示与它在“WhiteLabel”错误页中放置的错误信息相同。】
Spring Boot also sets up a default error-page for the container, equivalent to the
<error-page> directive in web.xml (although implemented very differently). Exceptions thrown outside the Spring MVC framework, such as from a servlet Filter, are still reported by the Spring Boot fallback error page. The sample application also shows an example of this.
【SpringBoot同样为容器设置了默认的错误页面,相当于web.xml中的<error-page>指令(尽管实现方式非常不同)。在Spring MVC框架之外抛出的异常,例如来自servlet过滤器的异常,仍然由Spring Boot回退错误页面报告。示例应用程序还显示了一个这样的示例。】
A more in-depth discussion of Spring Boot error-handling can be found at the end of this article.
【在本文的最后,可以找到关于Spring Boot error-handling的更深入的讨论。】
The rest of this article applies regardless of whether you are using Spring with or without Spring Boot.
【本文的其余部分适用于无论您使用的是Spring 还是不使用 Spring Boot。】
Impatient REST developers may choose to skip directly to the section on custom REST error responses. However they should then read the full article as most of it applies equally to all web applications, REST or otherwise.
【坐不住的REST开发人员很可能会选择直接跳到自定义REST错误响应部分。然而,他们应该阅读完整的文章,因为大部分文章同样适用于所有Web应用程序,无论是REST还是其他应用程序。】
Using HTTP Status Codes
【使用HTTP响应状态码】
Normally any unhandled exception thrown when processing a web-request causes the server to return an HTTP 500 response. However, any exception that you write yourself can be annotated with the @ResponseStatus annotation (which supports all the HTTP status codes defined by the HTTP specification). When an annotated exception is thrown from a controller method, and not handled elsewhere, it will automatically cause the appropriate HTTP response to be returned with the specified status-code.
【通常,处理Web请求时引发的任何未处理异常都会导致服务器返回HTTP 500响应。但是,您自己编写的任何异常都可以使用@ResponseStatus注解进行注释(它支持由HTTP规范定义的所有HTTP状态代码)。当从控制器方法抛出带注释的异常,而不是在其他地方处理时,它将自动使用指定的状态代码返回适当的HTTP响应。】
For example, here is an exception for a missing order.
【例如,这里有一个缺少订单的例外。】
And here is a controller method using it:
【这里是一个使用它的控制器方法:】
A familiar HTTP 404 response will be returned if the URL handled by this method includes an unknown order id.
【如果此方法处理的URL包含未知的订单ID,则返回熟悉的HTTP404响应。】
Controller Based Exception Handling
【基于控制器的异常处理】
Using @ExceptionHandler
【使用@ExceptionHandler】
You can add extra (@ExceptionHandler) methods to any controller to specifically handle exceptions thrown by request handling (@RequestMapping) methods in the same controller. Such methods can:
【您可以向任何控制器添加额外的(由@ExceptionHandler注释的)方法,以专门处理同一控制器中请求处理(@RequestMapping)方法引发的异常。这种方法可以:】
.Handle exceptions without the @ResponseStatus annotation (typically predefined exceptions that you didn’t write)
.Redirect the user to a dedicated error view
.Build a totally custom error response
【
.处理不使用@ResponseStatus注释的异常(通常是你没有写入的预定义异常
.将用户重定向到专用的错误视图
.生成完全自定义的错误响应
】
The following controller demonstrates these three options:
【以下控制器演示了这三个选项:】
In any of these methods you might choose to do additional processing - the most common example is to log the exception.
【在这些方法中,你可以选择执行其他处理方式——例如最常见的是记录异常。】
Handler methods have flexible signatures so you can pass in obvious servlet-related objects such as HttpServletRequest, HttpServletResponse, HttpSession and/or Principle.
【异常处理方法具有灵活的方法签名,因此可以传递明显的servlet相关对象,如HttpServletRequest、HttpServletResponse、HttpSession 和/或Principle。】
Important Note: The Model may not be a parameter of any @ExceptionHandler method. Instead, setup a model inside the method using a ModelAndView as shown by handleError() above.
【重要提示:模型不应该是任何@ExceptionHandler方法的参数。相反,应该使用上面handleError()方法所演示的,在ModelAndView 内部设置模型。】
Exceptions and Views
【异常与视图】
Be careful when adding exceptions to the model. Your users do not want to see web-pages containing Java exception details and stack-traces. You may have security policies that expressly forbid putting any exception information in the error page. Another reason to make sure you override the Spring Boot white-label error page.
【在向模型添加异常时要小心。用户可能不希望看到包含Java异常细节和堆栈跟踪的网页。您可能有明确禁止将任何异常信息放入错误页面的安全策略。另一个原因是确保覆盖Spring Boot White Label错误页。】
Make sure exceptions are logged usefully so they can be analyzed after the event by your support and development teams.
【确保异常被有效地记录下来,这样你的支持和开发团队就可以在事件发生后对它们进行分析。】
Please remember the following may be convenient but it is not best practice in production.
【请记住,以下内容可能很方便,但这不是生产中的最佳实践。】
It can be useful to hide exception details in the page source as a comment, to assist testing. If using JSP, you could do something like this to output the exception and the corresponding stack-trace (using a hidden <div> is another option).
【将异常详细信息作为注释隐藏在页面源中,有助于测试。如果使用JSP,可以这样做:输出异常和相应的堆栈跟踪(使用一个隐藏的<div>是另一个选项)。】
For the Thymeleaf equivalent see support.html in the demo application. The result looks like this.
【关于等同Thymeleaf 的参见示例应用中的support.html。结果看起来像这样。】
Global Exception Handling
【全局异常处理】
使用 @ControllerAdvice 注解的相关类
A controller advice allows you to use exactly the same exception handling techniques but apply them across the whole application, not just to an individual controller. You can think of them as an annotation driven interceptor.
【controller advice 允许在应用中使用完全相同的异常处理技术,但在整个应用程序中应用它们,而不仅仅是对单个控制器。您可以将它们视为注解驱动的拦截器。】
Any class annotated with @ControllerAdvice becomes a controller-advice and three types of method are supported:
【任何使用 @ControllerAdvice注释的类都将成为controller-advice,并且支持三种类型的方法:】
.Exception handling methods annotated with @ExceptionHandler.
【用@ExceptionHandler 注释的异常处理方法。】
.Model enhancement methods (for adding additional data to the model) annotated with @ModelAttribute. Note that these attributes are not available to the exception handling views.
【使用@ModelAttribute 注解进行模型增强的方法(用于向模型添加附加数据)。注意,这些属性对异常处理视图不可用。】
。Binder initialization methods (used for configuring form-handling) annotated with @InitBinder.
【使用@InitBinder注解注释的Binder 初始化方法(用于配置表单处理)】
We are only going to look at exception handling - search the online manual for more on @ControllerAdvice methods.
【我们将只关注于异常处理——参见在线手册中搜索@ControllerAdvic方法的更多信息。】
Any of the exception handlers you saw above can be defined on a controller-advice class - but now they apply to exceptions thrown from any controller. Here is a simple example:
【在上面看到的任何异常处理程序都可以在controller-advice类上定义,但现在它们应用于从任何控制器抛出的异常。下面是一个简单的例子:】
If you want to have a default handler for any exception, there is a slight wrinkle. You need to ensure annotated exceptions are handled by the framework. The code looks like this:
【如果你想拥有一个默认处理程序来应对任何异常,则会出现轻微的褶皱。因为你需要确保框架处理带注释的异常。代码如下:】
Going Deeper
【深入】
HandlerExceptionResolver
Any Spring bean declared in the DispatcherServlet’s application context that implements HandlerExceptionResolver will be used to intercept and process any exception raised in the MVC system and not handled by a Controller. The interface looks like this:
【在DispatcherServlet的应用程序上下文中声明的、实现HandlerExceptionResolver 接口的任何Spring Bean都将用于截获和处理MVC系统中引发的、不由控制器处理的任何异常。该接口如下所示:】
The handler refers to the controller that generated the exception (remember that @Controller instances are only one type of handler supported by Spring MVC. For example: HttpInvokerExporter and the WebFlow Executor are also types of handler).
【handler 引用生成异常的控制器(请记住,@controller实例只是Spring MVC支持的一种类型的handler 。例如:HttpInvokerExporter 和WebFlow Executor 也是handler 的类型)。】
Behind the scenes, MVC creates three such resolvers by default. It is these resolvers that implement the behaviours discussed above:
【在幕后,MVC默认创建三个这样的解析器。正是这些解析器实现了上述行为:】
.ExceptionHandlerExceptionResolver matches uncaught exceptions against suitable @ExceptionHandler methods on both the handler (controller) and on any controller-advices.
【ExceptionHandlerExceptionResolver 匹配未捕获的异常,待翻译确认】
.ResponseStatusExceptionResolver looks for uncaught exceptions annotated by @ResponseStatus (as described in Section 1)
【ResponseStatusExceptionResolver 查找由@ResponseStatus注释的未捕获异常(如第1节所述)】
.DefaultHandlerExceptionResolver converts standard Spring exceptions and converts them to HTTP Status Codes (I have not mentioned this above as it is internal to Spring MVC).
【DefaultHandlerExceptionResolver 转换标准的Spring异常并将其转换为HTTP状态代码(在上述中,我没有提到这一点,因为它是Spring MVC内部的)】
These are chained and processed in the order listed - internally Spring creates a dedicated bean (the HandlerExceptionResolverComposite) to do this.
【在内部它们以一定的顺序组织成链,然后处理异常。Spring创建了一个专用的bean ——HandlerExceptionResolverComposite来干这件事情】
Notice that the method signature of resolveException does not include the Model. This is why @ExceptionHandler methods cannot be injected with the model.
【注意,resolveException 的方法签名不包括模型。这就是为什么@ExceptionHandler 方法不能与注入模型的原因。】
You can, if you wish, implement your own HandlerExceptionResolver to setup your own custom exception handling system. Handlers typically implement Spring’s Ordered interface so you can define the order that the handlers run in.
【如果愿意,你可以实现自己的HandlerExceptionResolver 来设置自己的自定义异常处理系统。处理程序通常实现Spring的Ordered接口,这样您就可以定义处理程序的运行顺序。】
SimpleMappingExceptionResolver
Spring has long provided a simple but convenient implementation of HandlerExceptionResolver that you may well find being used in your appication already - the SimpleMappingExceptionResolver. It provides options to:
【Spring长期以来提供了一个简单但方便的HandlerExceptionResolver 实现,您可能会发现它已经在应用程序中使用了——SimpleMappingExceptionResolver。它提供了以下选项:】
Here is a typical configuration using Java Configuration:
【以下是一段使用Java配置的典型配置:】
Or using XML Configuration:
【或使用XML配置:】
The defaultErrorView property is especially useful as it ensures any uncaught exception generates a suitable application defined error page. (The default for most application servers is to display a Java stack-trace - something your users should never see). Spring Boot provides another way to do the same thing with its “white-label” error page.
【属性defaultErrorView 非常有用,因为它可以确保任何未补获的异常能够有一个合适的应用级定义的错误页面。(大多数应用服务器的默认值是显示Java堆栈跟踪-用户不应该看到的东西)。SpringBoot提供了另一种方法“white-label”错误页面来达到同样的效果。】
Extending SimpleMappingExceptionResolver
【扩展 SimpleMappingExceptionResolver】
It is quite common to extend SimpleMappingExceptionResolver for several reasons:
【扩展SimpleMappingExceptionResolver非常常见,原因有以下几种:】
.You can use the constructor to set properties directly - for example to enable exception logging and set the logger to use
【可以使用构造函数直接设置属性-例如启用异常日志记录并设置日志记录器】
.Override the default log message by overriding buildLogMessage. The default implementation always returns this fixed text:
【通过重写buildLogMessage来重写默认日志消息。默认实现始终返回此固定文本:】
.Handler execution resulted in exception
To make additional information available to the error view by overriding doResolveException
【执行Handler 引发的异常 通过重写doResolveException可使错误视图可以使用其他信息】
For example:
这部分代码参见 ExampleSimpleMappingExceptionResolver
Extending ExceptionHandlerExceptionResolver
【扩展ExceptionHandlerExceptionResolver】
It is also possible to extend ExceptionHandlerExceptionResolver and override its
doResolveHandlerMethodException method in the same way. It has almost the same signature (it just takes the new HandlerMethod instead of a Handler).
【也可以扩展exceptionhandlerexceptionresolver并重写其
doResolveHandlerMethodException 方法的方式相同。它具有几乎相同的签名(它只采用新的HandlerMethod 而不Handler)。】
To make sure it gets used, also set the inherited order property (for example in the constructor of your new class) to a value less than MAX_INT so it runs before the default ExceptionHandlerExceptionResolver instance (it is easier to create your own handler instance than try to modify/replace the one created by Spring). See ExampleExceptionHandlerExceptionResolver in the demo app for more.
【为了确保使用它,还将继承order属性(例如在新类的构造函数中)设置为小于MAX_INT的值,以便它在默认的ExceptionHandlerExceptionResolver 实例之前运行(创建自己的处理程序实例比尝试修改/替换一个Spring创建的)。有关详细信息,请参阅演示应用程序中的ExampleExceptionHandlerExceptionResolver。】
Errors and REST
RESTful GET requests may also generate exceptions and we have already seen how we can return standard HTTP Error response codes. However, what if you want to return information about the error? This is very easy to do. Firstly define an error class:
【RESTful GET请求也可能发生异常,我们已经了解了如何返回标准HTTP错误响应代码。但是,如果您想返回有关错误的信息,该怎么办?这很容易做到。首先定义一个错误类:】
public class ErrorInfo {
public final String url;
public final String ex;
public ErrorInfo(String url, Exception ex) {
this.url = url;
this.ex = ex.getLocalizedMessage();
}
}
Now we can return an instance from a handler as the @ResponseBody like this:
什么时候使用?
As usual, Spring likes to offer you choice, so what should you do? Here are some rules of thumb. However if you have a preference for XML configuration or Annotations, that’s fine too.
【像往常一样,Spring 提供了选择,你应该怎么做?以下是一些经验法则。但是,如果您对XML配置或注释有偏好,那也没关系。】
Spring MVC provides several complimentary approaches to exception handling but, when teaching Spring MVC, I often find that my students are confused or not comfortable with them.
【Spring MVC 提供了几种免费的异常处理方式,但在我教授Spring MVC时,我发现我的学生往往对这几种处理方式感到迷惑并且不能够很舒服的使用它们。】
Today I’m going to show you the various options available. Our goal is to not handle exceptions explicitly in Controller methods where possible. They are a cross-cutting concern better handled separately in dedicated code.
【今天我将为你展示这几种不同的异常处理选项。我的目标是尽可能的不在Controller 方法中显示处理异常。将异常处理作为一个横切关注的问题,在专用的代码中单独处理会更好。】
There are three options: per exception, per controller or globally.
【有三个选项:每个异常处理、每个控制器处理或全局处理。】
A demonstration application that shows the points discussed here can be found at
http://github.com/paulc4/mvc-exceptions. See Sample Application below for details.
【在这里我为大家提供了一个演示示例用于展示上述讨论的关注点,你要以在http://github.com/paulc4/mvc-exceptions这里找到示例。参见下述的Sample Application以了解更多的细节。】
NOTE: The demo applications has been revamped and updated (April 2018) to use Spring Boot 2.0.1 and is (hopefully) easier to use and understand. I also fixed some broken links (thanks for the feedback, sorry it took a while).
【注意:该示例在2018年4月经过了使用Spring Boot 2.0.1重构以更方便使用和理解。我还修复了一些断开的链接(感谢大家的反馈,很抱歉花了一段时间)。】
Spring Boot
Spring Boot allows a Spring project to be setup with minimal configuration and it is likely that you are using it if your application is less than a few years old.
【SpringBoot允许使用最少的配置来设置Spring项目,并且如果你的应用程序还不到几年,那么你很可能正在使用它。】
Spring MVC offers no default (fall-back) error page out-of-the-box. The most common way to set a default error page has always been the SimpleMappingExceptionResolver (since Spring V1 in fact). We will discuss this later.
【Spring MVC不提供默认(回退)的错误页面。设置默认错误页面的最常见方法是使用SimpleMappingExceptionResolver(实际上从Spring v1开始)。我们稍后再讨论它。】
However Spring Boot does provide for a fallback error-handling page.
【然而Spring Boot提供一个回退错误处理页面。】
At start-up, Spring Boot tries to find a mapping for /error. By convention, a URL ending in /error maps to a logical view of the same name: error. In the demo application this view maps in turn to the error.html Thymeleaf template. (If using JSP, it would map to error.jsp according to the setup of your InternalResourceViewResolver). The actual mapping will depend on what ViewResolver (if any) that you or Spring Boot has setup.
【在Spring Boot启动时,它会尝试查找/error的映射。按照惯例,以/error结尾的URL映射到具有相同名称的逻辑视图:error。在演示应用程序中,此视图依次映射到了thymeleaf模板error.html。(如果使用JSP的话,Spring Boot将根据InternalResourceViewResolver的设置映射到error.jsp)。如果有的话,实际的映射将取决于你或Spring Boot设置的ViewResolver】
If no view-resolver mapping for /error can be found, Spring Boot defines its own fall-back error page - the so-called “Whitelabel Error Page” (a minimal page with just the HTTP status information and any error details, such as the message from an uncaught exception). In the sample applicaiton, if you rename the error.html template to, say, error2.html then restart, you will see it being used.
【如果Spring Boot找不到/error的视图解析器映射,那么Spring Boot定义了自己的回退错误页面,即所谓的“WhiteLabel错误页”(一个最小的页,只包含HTTP状态信息和任何错误详细信息,例如来自未捕获异常的消息)。在示例应用程序中,如果将error.html模板重命名为,比如,error2.html,然后重新启动,您将看到WhiteLabel错误页面被使用。】
If you are making a RESTful request (the HTTP request has specified a desired response type other than HTML) Spring Boot returns a JSON representation of the same error information that it puts in the “Whitelabel” error page.
【如果你正在进行一个RESTful请求(HTTP请求指定了一个所需的响应类型,而不是HTML),那么Spring Boot将返回一个JSON表示,该表示与它在“WhiteLabel”错误页中放置的错误信息相同。】
$> curl -H "Accept: application/json" http://localhost:8080/no-such-page {"timestamp":"2018-04-11T05:56:03.845+0000","status":404,"error":"Not Found","message":"No message available","path":"/no-such-page"}
Spring Boot also sets up a default error-page for the container, equivalent to the
<error-page> directive in web.xml (although implemented very differently). Exceptions thrown outside the Spring MVC framework, such as from a servlet Filter, are still reported by the Spring Boot fallback error page. The sample application also shows an example of this.
【SpringBoot同样为容器设置了默认的错误页面,相当于web.xml中的<error-page>指令(尽管实现方式非常不同)。在Spring MVC框架之外抛出的异常,例如来自servlet过滤器的异常,仍然由Spring Boot回退错误页面报告。示例应用程序还显示了一个这样的示例。】
A more in-depth discussion of Spring Boot error-handling can be found at the end of this article.
【在本文的最后,可以找到关于Spring Boot error-handling的更深入的讨论。】
The rest of this article applies regardless of whether you are using Spring with or without Spring Boot.
【本文的其余部分适用于无论您使用的是Spring 还是不使用 Spring Boot。】
Impatient REST developers may choose to skip directly to the section on custom REST error responses. However they should then read the full article as most of it applies equally to all web applications, REST or otherwise.
【坐不住的REST开发人员很可能会选择直接跳到自定义REST错误响应部分。然而,他们应该阅读完整的文章,因为大部分文章同样适用于所有Web应用程序,无论是REST还是其他应用程序。】
Using HTTP Status Codes
【使用HTTP响应状态码】
Normally any unhandled exception thrown when processing a web-request causes the server to return an HTTP 500 response. However, any exception that you write yourself can be annotated with the @ResponseStatus annotation (which supports all the HTTP status codes defined by the HTTP specification). When an annotated exception is thrown from a controller method, and not handled elsewhere, it will automatically cause the appropriate HTTP response to be returned with the specified status-code.
【通常,处理Web请求时引发的任何未处理异常都会导致服务器返回HTTP 500响应。但是,您自己编写的任何异常都可以使用@ResponseStatus注解进行注释(它支持由HTTP规范定义的所有HTTP状态代码)。当从控制器方法抛出带注释的异常,而不是在其他地方处理时,它将自动使用指定的状态代码返回适当的HTTP响应。】
For example, here is an exception for a missing order.
【例如,这里有一个缺少订单的例外。】
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order") // 404 public class OrderNotFoundException extends RuntimeException { // ... }
And here is a controller method using it:
【这里是一个使用它的控制器方法:】
@RequestMapping(value="/orders/{id}", method=GET) public String showOrder(@PathVariable("id") long id, Model model) { Order order = orderRepository.findOrderById(id); if (order == null) throw new OrderNotFoundException(id); model.addAttribute(order); return "orderDetail"; }
A familiar HTTP 404 response will be returned if the URL handled by this method includes an unknown order id.
【如果此方法处理的URL包含未知的订单ID,则返回熟悉的HTTP404响应。】
Controller Based Exception Handling
【基于控制器的异常处理】
Using @ExceptionHandler
【使用@ExceptionHandler】
You can add extra (@ExceptionHandler) methods to any controller to specifically handle exceptions thrown by request handling (@RequestMapping) methods in the same controller. Such methods can:
【您可以向任何控制器添加额外的(由@ExceptionHandler注释的)方法,以专门处理同一控制器中请求处理(@RequestMapping)方法引发的异常。这种方法可以:】
.Handle exceptions without the @ResponseStatus annotation (typically predefined exceptions that you didn’t write)
.Redirect the user to a dedicated error view
.Build a totally custom error response
【
.处理不使用@ResponseStatus注释的异常(通常是你没有写入的预定义异常
.将用户重定向到专用的错误视图
.生成完全自定义的错误响应
】
The following controller demonstrates these three options:
【以下控制器演示了这三个选项:】
@Controller public class ExceptionHandlingController { // @RequestHandler methods ... // Exception handling methods // Convert a predefined exception to an HTTP Status code @ResponseStatus(value=HttpStatus.CONFLICT, reason="Data integrity violation") // 409 @ExceptionHandler(DataIntegrityViolationException.class) public void conflict() { // Nothing to do } // Specify name of a specific view that will be used to display the error: @ExceptionHandler({SQLException.class,DataAccessException.class}) public String databaseError() { // Nothing to do. Returns the logical view name of an error page, passed // to the view-resolver(s) in usual way. // Note that the exception is NOT available to this view (it is not added // to the model) but see "Extending ExceptionHandlerExceptionResolver" // below. return "databaseError"; } // Total control - setup a model and return the view name yourself. Or // consider subclassing ExceptionHandlerExceptionResolver (see below). @ExceptionHandler(Exception.class) public ModelAndView handleError(HttpServletRequest req, Exception ex) { logger.error("Request: " + req.getRequestURL() + " raised " + ex); ModelAndView mav = new ModelAndView(); mav.addObject("exception", ex); mav.addObject("url", req.getRequestURL()); mav.setViewName("error"); return mav; } }
In any of these methods you might choose to do additional processing - the most common example is to log the exception.
【在这些方法中,你可以选择执行其他处理方式——例如最常见的是记录异常。】
Handler methods have flexible signatures so you can pass in obvious servlet-related objects such as HttpServletRequest, HttpServletResponse, HttpSession and/or Principle.
【异常处理方法具有灵活的方法签名,因此可以传递明显的servlet相关对象,如HttpServletRequest、HttpServletResponse、HttpSession 和/或Principle。】
Important Note: The Model may not be a parameter of any @ExceptionHandler method. Instead, setup a model inside the method using a ModelAndView as shown by handleError() above.
【重要提示:模型不应该是任何@ExceptionHandler方法的参数。相反,应该使用上面handleError()方法所演示的,在ModelAndView 内部设置模型。】
Exceptions and Views
【异常与视图】
Be careful when adding exceptions to the model. Your users do not want to see web-pages containing Java exception details and stack-traces. You may have security policies that expressly forbid putting any exception information in the error page. Another reason to make sure you override the Spring Boot white-label error page.
【在向模型添加异常时要小心。用户可能不希望看到包含Java异常细节和堆栈跟踪的网页。您可能有明确禁止将任何异常信息放入错误页面的安全策略。另一个原因是确保覆盖Spring Boot White Label错误页。】
Make sure exceptions are logged usefully so they can be analyzed after the event by your support and development teams.
【确保异常被有效地记录下来,这样你的支持和开发团队就可以在事件发生后对它们进行分析。】
Please remember the following may be convenient but it is not best practice in production.
【请记住,以下内容可能很方便,但这不是生产中的最佳实践。】
It can be useful to hide exception details in the page source as a comment, to assist testing. If using JSP, you could do something like this to output the exception and the corresponding stack-trace (using a hidden <div> is another option).
【将异常详细信息作为注释隐藏在页面源中,有助于测试。如果使用JSP,可以这样做:输出异常和相应的堆栈跟踪(使用一个隐藏的<div>是另一个选项)。】
<h1>Error Page</h1> <p>Application has encountered an error. Please contact support on ...</p> <!-- Failed URL: ${url} Exception: ${exception.message} <c:forEach items="${exception.stackTrace}" var="ste"> ${ste} </c:forEach> -->
For the Thymeleaf equivalent see support.html in the demo application. The result looks like this.
【关于等同Thymeleaf 的参见示例应用中的support.html。结果看起来像这样。】
Global Exception Handling
【全局异常处理】
使用 @ControllerAdvice 注解的相关类
A controller advice allows you to use exactly the same exception handling techniques but apply them across the whole application, not just to an individual controller. You can think of them as an annotation driven interceptor.
【controller advice 允许在应用中使用完全相同的异常处理技术,但在整个应用程序中应用它们,而不仅仅是对单个控制器。您可以将它们视为注解驱动的拦截器。】
Any class annotated with @ControllerAdvice becomes a controller-advice and three types of method are supported:
【任何使用 @ControllerAdvice注释的类都将成为controller-advice,并且支持三种类型的方法:】
.Exception handling methods annotated with @ExceptionHandler.
【用@ExceptionHandler 注释的异常处理方法。】
.Model enhancement methods (for adding additional data to the model) annotated with @ModelAttribute. Note that these attributes are not available to the exception handling views.
【使用@ModelAttribute 注解进行模型增强的方法(用于向模型添加附加数据)。注意,这些属性对异常处理视图不可用。】
。Binder initialization methods (used for configuring form-handling) annotated with @InitBinder.
【使用@InitBinder注解注释的Binder 初始化方法(用于配置表单处理)】
We are only going to look at exception handling - search the online manual for more on @ControllerAdvice methods.
【我们将只关注于异常处理——参见在线手册中搜索@ControllerAdvic方法的更多信息。】
Any of the exception handlers you saw above can be defined on a controller-advice class - but now they apply to exceptions thrown from any controller. Here is a simple example:
【在上面看到的任何异常处理程序都可以在controller-advice类上定义,但现在它们应用于从任何控制器抛出的异常。下面是一个简单的例子:】
@ControllerAdvice class GlobalControllerExceptionHandler { @ResponseStatus(HttpStatus.CONFLICT) // 409 @ExceptionHandler(DataIntegrityViolationException.class) public void handleConflict() { // Nothing to do } }
If you want to have a default handler for any exception, there is a slight wrinkle. You need to ensure annotated exceptions are handled by the framework. The code looks like this:
【如果你想拥有一个默认处理程序来应对任何异常,则会出现轻微的褶皱。因为你需要确保框架处理带注释的异常。代码如下:】
ControllerAdvice class GlobalDefaultExceptionHandler { public static final String DEFAULT_ERROR_VIEW = "error"; @ExceptionHandler(value = Exception.class) public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception { // If the exception is annotated with @ResponseStatus rethrow it and let // the framework handle it - like the OrderNotFoundException example // at the start of this post. // AnnotationUtils is a Spring Framework utility class. if (AnnotationUtils.findAnnotation (e.getClass(), ResponseStatus.class) != null) throw e; // Otherwise setup and send the user to a default error-view. ModelAndView mav = new ModelAndView(); mav.addObject("exception", e); mav.addObject("url", req.getRequestURL()); mav.setViewName(DEFAULT_ERROR_VIEW); return mav; } }
Going Deeper
【深入】
HandlerExceptionResolver
Any Spring bean declared in the DispatcherServlet’s application context that implements HandlerExceptionResolver will be used to intercept and process any exception raised in the MVC system and not handled by a Controller. The interface looks like this:
【在DispatcherServlet的应用程序上下文中声明的、实现HandlerExceptionResolver 接口的任何Spring Bean都将用于截获和处理MVC系统中引发的、不由控制器处理的任何异常。该接口如下所示:】
public interface HandlerExceptionResolver { ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); }
The handler refers to the controller that generated the exception (remember that @Controller instances are only one type of handler supported by Spring MVC. For example: HttpInvokerExporter and the WebFlow Executor are also types of handler).
【handler 引用生成异常的控制器(请记住,@controller实例只是Spring MVC支持的一种类型的handler 。例如:HttpInvokerExporter 和WebFlow Executor 也是handler 的类型)。】
Behind the scenes, MVC creates three such resolvers by default. It is these resolvers that implement the behaviours discussed above:
【在幕后,MVC默认创建三个这样的解析器。正是这些解析器实现了上述行为:】
.ExceptionHandlerExceptionResolver matches uncaught exceptions against suitable @ExceptionHandler methods on both the handler (controller) and on any controller-advices.
【ExceptionHandlerExceptionResolver 匹配未捕获的异常,待翻译确认】
.ResponseStatusExceptionResolver looks for uncaught exceptions annotated by @ResponseStatus (as described in Section 1)
【ResponseStatusExceptionResolver 查找由@ResponseStatus注释的未捕获异常(如第1节所述)】
.DefaultHandlerExceptionResolver converts standard Spring exceptions and converts them to HTTP Status Codes (I have not mentioned this above as it is internal to Spring MVC).
【DefaultHandlerExceptionResolver 转换标准的Spring异常并将其转换为HTTP状态代码(在上述中,我没有提到这一点,因为它是Spring MVC内部的)】
These are chained and processed in the order listed - internally Spring creates a dedicated bean (the HandlerExceptionResolverComposite) to do this.
【在内部它们以一定的顺序组织成链,然后处理异常。Spring创建了一个专用的bean ——HandlerExceptionResolverComposite来干这件事情】
Notice that the method signature of resolveException does not include the Model. This is why @ExceptionHandler methods cannot be injected with the model.
【注意,resolveException 的方法签名不包括模型。这就是为什么@ExceptionHandler 方法不能与注入模型的原因。】
You can, if you wish, implement your own HandlerExceptionResolver to setup your own custom exception handling system. Handlers typically implement Spring’s Ordered interface so you can define the order that the handlers run in.
【如果愿意,你可以实现自己的HandlerExceptionResolver 来设置自己的自定义异常处理系统。处理程序通常实现Spring的Ordered接口,这样您就可以定义处理程序的运行顺序。】
SimpleMappingExceptionResolver
Spring has long provided a simple but convenient implementation of HandlerExceptionResolver that you may well find being used in your appication already - the SimpleMappingExceptionResolver. It provides options to:
【Spring长期以来提供了一个简单但方便的HandlerExceptionResolver 实现,您可能会发现它已经在应用程序中使用了——SimpleMappingExceptionResolver。它提供了以下选项:】
- Map exception class names to view names - just specify the classname, no package needed. 【映射异常类名为视图名——只要指定类名就可以了,不需要包路径】
- Specify a default (fallback) error page for any exception not handled anywhere else 【为任何其他地方未处理的异常指定默认的(回退)错误页面】
- Log a message (this is not enabled by default). 【记录一条日志信息(默认这个功能未启用)】
- Set the name of the exception attribute to add to the Model so it can be used inside a View (such as a JSP). By default this attribute is named exception. Set to null to disable. Remember that views returned from @ExceptionHandler methods do not have access to the exception but views defined to SimpleMappingExceptionResolver do. 【设置要添加到模型中的异常属性的名称,以便可以在视图(如JSP)中使用它。默认情况下,此属性命名为exception。设置为null 以禁用。请记住,从@ExceptionHandler方法返回的视图没有访问异常的权限,而定义给SimpleMappingExceptionResolver 的视图可以访问。】
Here is a typical configuration using Java Configuration:
【以下是一段使用Java配置的典型配置:】
@Configuration @EnableWebMvc // Optionally setup Spring MVC defaults (if you aren't using // Spring Boot & haven't specified @EnableWebMvc elsewhere) public class MvcConfiguration extends WebMvcConfigurerAdapter { @Bean(name="simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("DatabaseException", "databaseError"); mappings.setProperty("InvalidCreditCardException", "creditCardError"); r.setExceptionMappings(mappings); // None by default r.setDefaultErrorView("error"); // No default r.setExceptionAttribute("ex"); // Default is "exception" r.setWarnLogCategory("example.MvcLogger"); // No default return r; } ... }
Or using XML Configuration:
【或使用XML配置:】
<bean id="simpleMappingExceptionResolver" class= "org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <map> <entry key="DatabaseException" value="databaseError"/> <entry key="InvalidCreditCardException" value="creditCardError"/> </map> </property> <!-- See note below on how this interacts with Spring Boot --> <property name="defaultErrorView" value="error"/> <property name="exceptionAttribute" value="ex"/> <!-- Name of logger to use to log exceptions. Unset by default, so logging is disabled unless you set a value. --> <property name="warnLogCategory" value="example.MvcLogger"/> </bean>
The defaultErrorView property is especially useful as it ensures any uncaught exception generates a suitable application defined error page. (The default for most application servers is to display a Java stack-trace - something your users should never see). Spring Boot provides another way to do the same thing with its “white-label” error page.
【属性defaultErrorView 非常有用,因为它可以确保任何未补获的异常能够有一个合适的应用级定义的错误页面。(大多数应用服务器的默认值是显示Java堆栈跟踪-用户不应该看到的东西)。SpringBoot提供了另一种方法“white-label”错误页面来达到同样的效果。】
Extending SimpleMappingExceptionResolver
【扩展 SimpleMappingExceptionResolver】
It is quite common to extend SimpleMappingExceptionResolver for several reasons:
【扩展SimpleMappingExceptionResolver非常常见,原因有以下几种:】
.You can use the constructor to set properties directly - for example to enable exception logging and set the logger to use
【可以使用构造函数直接设置属性-例如启用异常日志记录并设置日志记录器】
.Override the default log message by overriding buildLogMessage. The default implementation always returns this fixed text:
【通过重写buildLogMessage来重写默认日志消息。默认实现始终返回此固定文本:】
.Handler execution resulted in exception
To make additional information available to the error view by overriding doResolveException
【执行Handler 引发的异常 通过重写doResolveException可使错误视图可以使用其他信息】
For example:
public class MyMappingExceptionResolver extends SimpleMappingExceptionResolver { public MyMappingExceptionResolver() { // Enable logging by providing the name of the logger to use setWarnLogCategory(MyMappingExceptionResolver.class.getName()); } @Override public String buildLogMessage(Exception e, HttpServletRequest req) { return "MVC exception: " + e.getLocalizedMessage(); } @Override protected ModelAndView doResolveException(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex) { // Call super method to get the ModelAndView ModelAndView mav = super.doResolveException(req, resp, handler, ex); // Make the full URL available to the view - note ModelAndView uses // addObject() but Model uses addAttribute(). They work the same. mav.addObject("url", request.getRequestURL()); return mav; } }
这部分代码参见 ExampleSimpleMappingExceptionResolver
Extending ExceptionHandlerExceptionResolver
【扩展ExceptionHandlerExceptionResolver】
It is also possible to extend ExceptionHandlerExceptionResolver and override its
doResolveHandlerMethodException method in the same way. It has almost the same signature (it just takes the new HandlerMethod instead of a Handler).
【也可以扩展exceptionhandlerexceptionresolver并重写其
doResolveHandlerMethodException 方法的方式相同。它具有几乎相同的签名(它只采用新的HandlerMethod 而不Handler)。】
To make sure it gets used, also set the inherited order property (for example in the constructor of your new class) to a value less than MAX_INT so it runs before the default ExceptionHandlerExceptionResolver instance (it is easier to create your own handler instance than try to modify/replace the one created by Spring). See ExampleExceptionHandlerExceptionResolver in the demo app for more.
【为了确保使用它,还将继承order属性(例如在新类的构造函数中)设置为小于MAX_INT的值,以便它在默认的ExceptionHandlerExceptionResolver 实例之前运行(创建自己的处理程序实例比尝试修改/替换一个Spring创建的)。有关详细信息,请参阅演示应用程序中的ExampleExceptionHandlerExceptionResolver。】
Errors and REST
RESTful GET requests may also generate exceptions and we have already seen how we can return standard HTTP Error response codes. However, what if you want to return information about the error? This is very easy to do. Firstly define an error class:
【RESTful GET请求也可能发生异常,我们已经了解了如何返回标准HTTP错误响应代码。但是,如果您想返回有关错误的信息,该怎么办?这很容易做到。首先定义一个错误类:】
public class ErrorInfo {
public final String url;
public final String ex;
public ErrorInfo(String url, Exception ex) {
this.url = url;
this.ex = ex.getLocalizedMessage();
}
}
Now we can return an instance from a handler as the @ResponseBody like this:
@ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MyBadDataException.class) @ResponseBody ErrorInfo handleBadRequest(HttpServletRequest req, Exception ex) { return new ErrorInfo(req.getRequestURL(), ex); }
什么时候使用?
As usual, Spring likes to offer you choice, so what should you do? Here are some rules of thumb. However if you have a preference for XML configuration or Annotations, that’s fine too.
【像往常一样,Spring 提供了选择,你应该怎么做?以下是一些经验法则。但是,如果您对XML配置或注释有偏好,那也没关系。】
- For exceptions you write, consider adding @ResponseStatus to them. 【对于自己编写的异常,考虑添加 @ResponseStatus注解】
- For all other exceptions implement an @ExceptionHandler method on a @ControllerAdvice class or use an instance of SimpleMappingExceptionResolver. You may well have SimpleMappingExceptionResolver configured for your application already, in which case it may be easier to add new exception classes to it than implement a @ControllerAdvice. 【对于所有其他异常,在@ControllerAdvice类上实现@ExceptionHandler方法或使用SimpleMappingExceptionResolver实例。您可能已经为应用程序配置了SimpleMappingExceptionResolver,在这种情况下,向其添加新的异常类可能比实现@ControllerAdvice更容易。】
- For Controller specific exception handling add @ExceptionHandler methods to your controller. 【对于特定于控制器的异常处理,请将@ExceptionHandler方法添加到控制器。】
- Warning: Be careful mixing too many of these options in the same application. If the same exception can be handed in more than one way, you may not get the behavior you wanted. @ExceptionHandler methods on the Controller are always selected before those on any @ControllerAdvice instance. It is undefined what order controller-advices are processed. 【警告:请小心在同一应用程序中不要混合太多这些选项。如果同一个异常可以通过多种方式传递,则可能无法获得所需的行为。@控制器上的@ExceptionHandler方法始终选择在任何@controlleradvice实例上的方法之前。】
上一篇: 秦朝的人都吃什么?秦朝的饮食文化介绍
下一篇: Mongodb与sql语句对照