分享

Spring MVC中的异常处理

 埃德温会馆 2015-12-16
     在一个良好的Rest架构的应用中,所有的异常都应该有对应的Http Status Code来表示具体的异常类型,这样可以客户端可以基于对应的Status Code做出最有利于自己的处理。

在Spring MVC中,异常处理机制有3个选项:
  • 基于Exception的,即只处理某个异常
  • 基于Controller的,即处理某个Controller中抛出的异常。
  • 基于Application的,即处理该Application抛出的所有异常

在我之前的文章(http://ningandjiao./blog/1982635)中,搭建了一个基于Spring4.0的Restful服务,下面就来为这个服务添加Error Handling机制,

基于Exception的异常处理
通常情况下,在应用中抛出的未被捕获的异常,最后会以500(HttpStatus.INTERNAL_SERVER_ERROR)返回客户端,但是,有时服务器的异常是由于客户端发送的请求不合规范导致,这时,我们就需要应该400(BAD_REQUEST)的status code。在Spring MVC中,做到这点非常容易,只需要在对应的Exception上加上@ResponseStatus注解即可。栗子:

测试代码:
Java代码  收藏代码
  1. @Test  
  2. public void shouldGetStatus404WhenRquestIdBiggerThan10() throws Exception {  
  3.     mockMvc.perform(get("/requests/11")  
  4.             .contentType(MediaType.APPLICATION_JSON)  
  5.             .accept(MediaType.APPLICATION_JSON)  
  6.             .param("userId", "xianlinbox")  
  7.     )  
  8.             .andExpect(status().isBadRequest());  
  9. }  

实现代码:
Java代码  收藏代码
  1. @RequestMapping(value = "/requests/{requestId}", method = RequestMethod.GET)  
  2.     public Request get(@PathVariable int requestId, @RequestParam(value = "userId") String userId) {  
  3.         if (requestId > 10) {  
  4.             throw new InvalidRequestIdException("Request id must less than 10");  
  5.         }  
  6.         return new Request(userId, requestId, "GET");  
  7.     }  
  8.       
  9.       
  10. @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Request id must less than 10")  
  11. public class InvalidRequestIdException extends RuntimeException {  
  12.     public InvalidRequestIdException(String message) {  
  13.         super(message);  
  14.     }  
  15. }  

这个方式有很大的局限性:

1. 只能处理自己写的Exception
2. 不能定制Response的消息体。

基于Controller的异常处理
在每个Controller中,可以定义处理各种异常的方法,在该方法添加@ExceptionHandler定义该方法处理的Exception(所有的Exception都支持),添加@ResponseStatus定义该Exception应该返回的Http Status Code,方法的返回值可以是定制化的异常信息, 这就解决了上面Exception方式的局限性。 栗子:
测试代码:

Java代码  收藏代码
  1. @Test  
  2. public void shouldGetStatus404WhenRquestIdBiggerThan10() throws Exception {  
  3.     mockMvc.perform(get("/requests/11")  
  4.             .param("userId", "xianlinbox")  
  5.     )  
  6.             .andExpect(status().isBadRequest())  
  7.             .andExpect(content().string("Request id must less than 10"));  
  8.   
  9. }  
  10.   
  11. @Test  
  12. public void shouldGetStatus500WhenUnexpectedErrorHappen() throws Exception {  
  13.     mockMvc.perform(get("/requests/100")  
  14.             .param("userId", "xianlinbox")  
  15.     )  
  16.             .andExpect(status().isInternalServerError())  
  17.             .andExpect(content().string("Unexpected Server Error"));  
  18. }  


实现代码:
Java代码  收藏代码
  1. @ExceptionHandler(InvalidRequestIdException.class)  
  2. @ResponseStatus(value = HttpStatus.BAD_REQUEST)  
  3. public String handleInvalidRequestError(InvalidRequestIdException ex) {  
  4.     return ex.getMessage();  
  5. }  
  6.   
  7. @ExceptionHandler(RuntimeException.class)  
  8. @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)  
  9. public String handleUnexpectedServerError(RuntimeException ex) {  
  10.     return ex.getMessage();  
  11. }  


这种处理方式其它都好,就是有个最大的弊端,只能处理一个Controller的异常,对于又多个Controller的情况就会搞出很多的重复代码。

基于Application的异常处理
我个人觉得一个好的异常处理机制应该是这样的,有一个集中的处理点负责所有的异常处理,在真正的业务逻辑的处理过程中,我只会关心正常的业务流程,一旦遇到异常,我只管抛出对应的异常和相关的信息就行了。幸运的是,Spring MVC就提供了这样的机制,开发者可以自己定义一个ExceptionHandler类处理所有的异常,在这个类上加上@ControllerAdvice注解,这个类就以AOP的形式注册到SpringMVC的处理链条中了,在应用任何地方抛出Exception,最后都会调用到这个handler中的方法上,在Handler类中,可以和上面一样使用@ExceptionHandler注解来区别对待不同的Exception。栗子:

测试代码:
同上:但是需要注意一点,使用@ControllerAdvice是以AOP的形式做了一个切面处理异常,因此,你必须模拟整个Web处理过程该注解才能起效。因此在Integration测试类中,你需要加上@WebAppConfiguration注解,同时使用WebApplicationContext构建MockMvc,使用基于Controller的standaloneSetup是不能达到想要的效果的。
Java代码  收藏代码
  1. @WebAppConfiguration  
  2. public class ApiControllerIntegrationTest {  
  3.     @Autowired  
  4.     private WebApplicationContext webApplicationContext;  
  5.   
  6.     private MockMvc mockMvc;  
  7.   
  8.     @Before  
  9.     public void setUp() throws Exception {  
  10.         mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();  
  11.     }  
  12. ...  

实现代码:
Java代码  收藏代码
  1. @ControllerAdvice  
  2. public class ApiExceptionHandler {  
  3.   
  4.     @ExceptionHandler(InvalidRequestIdException.class)  
  5.     @ResponseStatus(value = HttpStatus.BAD_REQUEST)  
  6.     @ResponseBody  
  7.     public String handleInvalidRequestError(InvalidRequestIdException ex) {  
  8.         return ex.getMessage();  
  9.     }  
  10.   
  11.     @ExceptionHandler(RuntimeException.class)  
  12.     @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)  
  13.     @ResponseBody  
  14.     public String handleUnexpectedServerError(RuntimeException ex) {  
  15.         return ex.getMessage();  
  16.     }  
  17. }  

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多