内容简介:1. 概览本文将举例说明
1. 概览
本文将举例说明 如何使用Spring来实现REST API的异常处理 。我们将同时考虑Spring 3.2和4.x推荐的解决方案,同时也会考虑以前的解决方案。
在Spring 3.2之前 ,Spring MVC应用程序中处理异常的两种主要方式是:HandlerExceptionResolver或注解@ExceptionHandler。这两种方式都有明显的缺点。
在3.2之后 ,我们有了新的注解@ControllerAdvice来解决前两个解决方案的局限性。
所有这些都有一个共同点——它们很好地处理了 关注点分离。 应用程序可以像往常一样抛出异常以表示某种类型的故障——这些异常将被单独处理。
2. 解决方案 1 – 控制器作用域的注解 @ExceptionHandler
第一个解决方案是在@Controller作用域有效——我们将定义一个处理异常的方法,并给这个方法添加@ExceptionHandler注解:
public class FooController{ //... @ExceptionHandler({ CustomException1.class, CustomException2.class}) public void handleException() { // } }
这种方法有 一个很大的缺陷 ——添加了@ExceptionHandler注解的方法 只针对特定的控制器 ,而不是全局的整个应用程序。当然,在每个控制器中都添加@ExceptionHandler 注解的办法使它无法很好的适应常规的异常处理机制。
@ExceptionHandler在作用域方面的缺陷通常是通过让 所有控制器都扩展一个控制器基类的方式来解决 ——然而,对于应用程序来说,这可能是一个问题,因为不管出于什么原因,总有一些控制器不能从这个基控制器扩展。例如,这些控制器可能不能直接修改,或者一些控制器可能已经从别的基类扩展,而这个基类可能在另一个jar中或者不能直接修改。
接下来,我们将讨论另一种解决异常处理问题的方法——一种全局的、不包括对现有组件的任何更改。
3. 解决方案 2 – HandlerExceptionResolver
第二个解决方案是定义一个 HandlerExceptionResolver——它将处理应用程序抛出的任何异常。它还允许我们在REST API中 实现统一的异常处理机制。
在使用自定义解析器之前,让我们回顾一下现有的异常解析器。
3.1. ExceptionHandlerExceptionResolver
这个解析器在Spring 3.1中引入,并且在 DispatcherServlet中是默认启用的。它实际上是前面介绍的@ExceptionHandler机制的核心组成部分。
3.2. DefaultHandlerExceptionResolver
DefaultHandlerExceptionResolver是在Spring 3.0中引入的,并且在DispatcherServlet中是默认启用的。它用于将Spring中的标准异常解析为对应的HTTP状态码,即客户端错误——4xx和服务器错误——5xx状态码。这是Spring异常的完整列表,以及这些异常对应的HTTP状态码。
虽然它确实正确地设置了响应的状态码,但有一个缺陷是 它不会改变响应体。 对于REST API来说,状态码实际上并 没有足够的信息 显示给客户端——响应也必须有一个响应体,以便服务器能够提供更多关于故障的信息。
这个缺陷可以通过ModelAndView配置视图解析和渲染错误内容来解决,但是这个解决方案很显然不是最理想的——这就是为什么在Spring 3.2中提供了更好的选项——我们将在本文的后半部分讨论这个问题。
3.3. ResponseStatusExceptionResolver
这个解析器也是在Spring 3.0中引入,并且在DispatcherServlet中是默认启用的。它的主要职责是根据自定义异常上配置的注解 @ResponseStatus ,将这些自定义异常映射到设定的HTTP状态码。
通过这个方式创建的一个自定义异常可能看起来是这样的:
@ResponseStatus(value = HttpStatus.NOT_FOUND) public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException() { super(); } public ResourceNotFoundException(String message, Throwable cause) { super(message, cause); } public ResourceNotFoundException(String message) { super(message); } public ResourceNotFoundException(Throwable cause) { super(cause); } }
与DefaultHandlerExceptionResolver一样,这个解析器在处理响应体方面是有缺陷的——它确实重新设定了响应的状态码,但是响应体仍然是空的。
3.4. SimpleMappingExceptionResolver和 AnnotationMethodHandlerExceptionResolver
SimpleMappingExceptionResolver 已经存在了相当长一段时间——它来自于较早的Spring MVC模型, 与REST服务不太相关。 它被用来映射异常类名到视图名。
在Spring 3.0中引入了AnnotationMethodHandlerExceptionResolver,通过注解@ExceptionHandler来处理异常,但是在Spring 3.2时已经被ExceptionHandlerExceptionResolver 废弃。
3.5. 自定义HandlerExceptionResolver
在为Spring RESTful 服务提供良好的错误处理机制方面,DefaultHandlerExceptionResolver和ResponseStatusExceptionResolver组合还有很长的路要走。缺陷是——正如前面提到的——无法控制响应体。
理想情况下,我们希望能够输出JSON或XML,这取决于客户端请求的格式(通过Accept头)。
这就足以创建一个新的、自定义的异常解析器:
@Component public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver { @Override protected ModelAndView doResolveException (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { try { if (ex instanceof IllegalArgumentException) { return handleIllegalArgument((IllegalArgumentException) ex, response, handler); } ... } catch (Exception handlerException) { logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException); } return null; } private ModelAndView handleIllegalArgument (IllegalArgumentException ex, HttpServletResponse response) throws IOException { response.sendError(HttpServletResponse.SC_CONFLICT); String accept = request.getHeader(HttpHeaders.ACCEPT); ... return new ModelAndView(); } }
这里需要注意的一个细节是请求本身是可用的,因此应用程序可以考虑由客户端发送的Accept头。例如,如果客户端要求application/json ,在出现错误的情况下,应用程序仍然应该返回用application/json 编码的响应体。
另一个重要的实现细节是返回一个ModelAndView ——这 是响应体 ,它将允许应用程序设置它所需要的任何东西。
对于Spring REST服务的异常处理来说,这种方法是一种一致且易于配置的机制。但是它有一些限制:它与低层的HtttpServletResponse交互,它适合使用ModelAndView的旧MVC模型——所以仍然有改进的空间。
4. 新的解决方案 3 – 使用新的注解 @ControllerAdvice (Spring 3.2及以上版本)
Spring 3.2使用新的注解@ControllerAdvice为全局的@ExceptionHandler提供支持。这就形成了一种脱离旧MVC模型的机制,使用ResponseEntity以及注解@ExceptionHandler的类型安全性和灵活性:
@ControllerAdvice public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(value = { IllegalArgumentException.class, IllegalStateException.class }) protected ResponseEntity<Object> handleConflict( RuntimeException ex, WebRequest request) { String bodyOfResponse = "This should be application specific"; return handleExceptionInternal(ex, bodyOfResponse, new HttpHeaders(), HttpStatus.CONFLICT, request); } }
新的@ControllerAdvice注解允许把以前多个分散的@ExceptionHandler合并到 一个单一的、全局的错误处理组件中。
实际的机制非常简单,但也非常灵活:
● 它允许对响应体和HTTP状态码进行完全控制
● 它允许将几个异常映射到相同的方法,以便一起处理
● 它充分利用了新的REST风格的 ResposeEntity响应这里要特别注意一个细节, @ExceptionHandler声明的异常类要与其修饰方法的参数类型相匹配。 如果这两个地方不匹配,编译器将不会提示——它没有理由去提示,Spring也不会提示。
然而,当异常在运行时被抛出时, 异常解析机制将会失败:
-
java.lang.IllegalStateException: No suitable resolver for argument [0] [type=...]
-
HandlerMethod details: ...
5. 处理Spring Security中拒绝访问
当一个经过身份认证的用户试图访问他没有足够权限访问的资源时,就会出现拒绝访问。
5.1. MVC – 自定义错误页
首先,让我们看一下MVC风格的解决方案,看看如何定制一个拒绝访问的错误页面:
使用XML配置:
<http> <intercept-url pattern="/admin/*" access="hasAnyRole('ROLE_ADMIN')"/> ... <access-denied-handler error-page="/my-error-page" /> </http>
使用 Java 配置:
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/*").hasAnyRole("ROLE_ADMIN") ... .and() .exceptionHandling().accessDeniedPage("/my-error-page"); }
当用户试图访问资源但没有足够的权限时,它们将被重定向到“/my-error-page“。
5.2. 自定义AccessDeniedHandler
接下来,让我们看看如何编写自定义AccessDeniedHandler:
@Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle (HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) throws IOException, ServletException { response.sendRedirect("/my-error-page"); } }
现在让我们 使用XML配置进行配置:
<http> <intercept-url pattern="/admin/*" access="hasAnyRole('ROLE_ADMIN')"/> ... <access-denied-handler ref="customAccessDeniedHandler" /> </http>
或者 使用Java配置:
@Autowired private CustomAccessDeniedHandler accessDeniedHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/*").hasAnyRole("ROLE_ADMIN") ... .and() .exceptionHandling().accessDeniedHandler(accessDeniedHandler) }
请注意,在我们的 CustomAccessDeniedHandler中,我们可以通过重定向或显示一条自定义错误信息的方式来定制响应。
5.3. REST和方法级的安全性
最后,让我们看看如何处理方法级的安全性注解@PreAuthorize、@PostAuthorize和@Secure引发的拒绝访问。
当然,我们将使用之前讨论过的全局异常处理机制来处理新的AccessDeniedException :
@ControllerAdvice public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler({ AccessDeniedException.class }) public ResponseEntity<Object> handleAccessDeniedException( Exception ex, WebRequest request) { return new ResponseEntity<Object>( "Access denied message here", new HttpHeaders(), HttpStatus.FORBIDDEN); } ... }
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 使用 ES2015 处理数组
- 使用Python进行异常处理
- 使用 canvas 对图像进行处理
- 使用 Canvas 对图像进行处理
- 使用 canvas 对图像进行处理
- 使用 Canvas 对图像进行处理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。