`

Spring MVC中的异常处理

阅读更多
在一个良好的Rest架构的应用中,所有的异常都应该有对应的Http Status Code来表示具体的异常类型,这样可以客户端可以基于对应的Status Code做出最有利于自己的处理。

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

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

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

测试代码:
    @Test
    public void shouldGetStatus404WhenRquestIdBiggerThan10() throws Exception {
        mockMvc.perform(get("/requests/11")
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
                .param("userId", "xianlinbox")
        )
                .andExpect(status().isBadRequest());
    }

实现代码:
@RequestMapping(value = "/requests/{requestId}", method = RequestMethod.GET)
    public Request get(@PathVariable int requestId, @RequestParam(value = "userId") String userId) {
        if (requestId > 10) {
            throw new InvalidRequestIdException("Request id must less than 10");
        }
        return new Request(userId, requestId, "GET");
    }
    
    
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Request id must less than 10")
public class InvalidRequestIdException extends RuntimeException {
    public InvalidRequestIdException(String message) {
        super(message);
    }
}

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

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

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

    @Test
    public void shouldGetStatus404WhenRquestIdBiggerThan10() throws Exception {
        mockMvc.perform(get("/requests/11")
                .param("userId", "xianlinbox")
        )
                .andExpect(status().isBadRequest())
                .andExpect(content().string("Request id must less than 10"));

    }

    @Test
    public void shouldGetStatus500WhenUnexpectedErrorHappen() throws Exception {
        mockMvc.perform(get("/requests/100")
                .param("userId", "xianlinbox")
        )
                .andExpect(status().isInternalServerError())
                .andExpect(content().string("Unexpected Server Error"));
    }


实现代码:
    @ExceptionHandler(InvalidRequestIdException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public String handleInvalidRequestError(InvalidRequestIdException ex) {
        return ex.getMessage();
    }

    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    public String handleUnexpectedServerError(RuntimeException ex) {
        return ex.getMessage();
    }


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

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

测试代码:
同上:但是需要注意一点,使用@ControllerAdvice是以AOP的形式做了一个切面处理异常,因此,你必须模拟整个Web处理过程该注解才能起效。因此在Integration测试类中,你需要加上@WebAppConfiguration注解,同时使用WebApplicationContext构建MockMvc,使用基于Controller的standaloneSetup是不能达到想要的效果的。
@WebAppConfiguration
public class ApiControllerIntegrationTest {
    @Autowired
    private WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

    @Before
    public void setUp() throws Exception {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
...

实现代码:
@ControllerAdvice
public class ApiExceptionHandler {

    @ExceptionHandler(InvalidRequestIdException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ResponseBody
    public String handleInvalidRequestError(InvalidRequestIdException ex) {
        return ex.getMessage();
    }

    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public String handleUnexpectedServerError(RuntimeException ex) {
        return ex.getMessage();
    }
}

4
1
分享到:
评论
6 楼 sqz10200 2016-04-14  
博主写的不错,  思想和写的代码都挺好   我平时都是看老外的写的例子,  你这个写的确实不错. 我原本是想把web.xml中的error-page这快内容放到java代码内实现的,  我想去掉web.xml内所有的配置.  之前在google上查过找到http://stackoverflow.com/questions/10813993/using-spring-mvc-3-1-webapplicationinitializer-to-programatically-configure-ses
老外的这片文章和你的解决方法是一样的.  我这里有一个问题  对于这个error是基于spring的aop去实现的.   那么和之前写在web.xml的有什么区别呢?  其实这个注解也应该是基于原生的做的扩展吧...如果我不想用spring 方式  而是基于WebApplicationInitializer  这个是不是也可以实现呢?
5 楼 whhwkm 2016-01-17  
好文来顶,nice
4 楼 ningandjin 2014-10-17  
sulibra 写道
不用再配置文件里写些什么吗?

你的spring配置文件中需要添加annotation-driven的配置,告诉Spring容器根据Annotation加载。。
3 楼 sulibra 2014-10-16  
不用再配置文件里写些什么吗?
2 楼 ningandjin 2014-03-30  
sachxp 写道
非常感谢楼主,写的非常详细,很棒。拜读之后收益颇丰。

基于Controller的异常处理那段示例代码里漏了 @ResponseBody annotation,会导致Response无视 @ResponseStatus 的设置永远返回400错误。

查了一会儿才发现问题。


有用就好,我的例子是基于Spring4.0的,在Controller上使用的annotation是@RestController而不是@Controller, @RestController和@Controller的区别就是会为Controller的每个@RequestMapping方法加上@ResponseBody的annotation。
1 楼 sachxp 2014-03-27  
非常感谢楼主,写的非常详细,很棒。拜读之后收益颇丰。

基于Controller的异常处理那段示例代码里漏了 @ResponseBody annotation,会导致Response无视 @ResponseStatus 的设置永远返回400错误。

查了一会儿才发现问题。

相关推荐

    spring mvc统一处理异常

    spring mvc统一处理异常,通过@ControllerAdvice+@ExceptionHandler

    spring mvc异常处理

    spring mvc异常处理,详细参考http://blog.csdn.net/xiejx618/article/details/41918611

    Spring MVC中异常处理的三种方式

    主要给大家介绍了关于Spring MVC中异常处理的三种方式,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring MVC具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

    spring mvc异常简单处理

    spring mvc让servlet容器直接处理异常.详细看:http://blog.csdn.net/xiejx618/article/details/41695255

    Spring MVC入门教程

    十一、spring mvc 如何实现全局的异常处理? 十二、spring mvc 如何把全局异常记录到日志中? 十三、如何给spring3 MVC中的Action做JUnit单元测试? 十四、spring mvc 转发与重定向 十五、spring mvc 处理ajax请求 ...

    Spring MVC 教程 快速入门 深入分析

    十一、spring mvc 如何实现全局的异常处理? 十二、spring mvc 如何把全局异常记录到日志中? 十三、如何给spring3 MVC中的Action做JUnit单元测试? 十四、spring mvc 转发与重定向 十五、spring mvc 处理ajax请求 ...

    Spring MVC+MyBatis开发从入门到项目实战

    第3篇是Spring MVC技术入门,包括Spring MVC的背景介绍、架构整体剖析、环境搭建、处理器与映射器的讲解、前端控制器的源码分析、多种视图解析器的介绍、请求映射与参数绑定的介绍、Validation校验与异常处理和拦截...

    SpringMVCDemo:Spring MVC 框架知识案例

    1.创建第一个 Spring MVC 程序案例 2.Spring MVC @RequestMapping 注解案例 3.Spring MVC 请求参数的获取案例 4.Spring MVC 域对象共享数据案例 5.Spring MVC @ModelAttribute 注解案例 ...15.Spring MVC 异常处理案例

    使用Spring MVC统一异常处理实战

    NULL 博文链接:https://cgs1999.iteye.com/blog/1547197

    SpringMVC框架架构介绍

    十一、spring mvc 如何实现全局的异常处理? 十二、spring mvc 如何把全局异常记录到日志中? 十三、如何给spring3 MVC中的Action做JUnit单元测试? 十四、spring mvc 转发与重定向 十五、spring mvc 处理ajax请求...

    三. spring mvc 异常统一处理

    NULL 博文链接:https://gaojiewyh.iteye.com/blog/1297746

    Spring MVC REST异常处理佳实践(下)

     Spring 异常处理  Spring MVC 有两个主要方式来处理在调用 MVC 控制器(译注:Controller,下文统一为控制器)时抛出的异常:HandlerExceptionResolver 和 @ExceptionHandler 注解。  ...

    详解使用Spring MVC统一异常处理实战

    本篇文章主要介绍了详解使用Spring MVC统一异常处理实战,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    Spring MVC--9.自定义拦截器、异常处理

    Spring MVC--9.自定义拦截器、异常处理

    springmvc 异常统一处理的三种方式详解.docx

    那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的。下面将介绍使用Spring MVC统一处理异常的解决和实现过程。

    Spring MVC 3.0实战指南.ppt

    《Spring MVC 3.0实战指南》,参考《Spring 3.x企业应用开发实战》。 内容简介: 1、Spring MVC框架简介 2、HTTP请求地址映射 3、HTTP请求数据的绑定 4、数据转换、格式化、校验 5、数据模型控制 6、视图及解析器 7...

    Spring MVC REST异常处理佳实践(上)

    因为在 REST APIs 中并没有直接的 UI 概念,那么你该如何使用 Spring MVC 简单直观的表示异常或问题呢?  本文是系列文章两部分中的第一部分。在本文中,我们将涉及 RESTful 异常报告的佳实践。第二部分将会通过一...

    Spring MVC全局异常处理和单元测试_动力节点Java学院整理

    本篇文章主要介绍了Spring MVC全局异常处理和单元测试,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

    problem-spring-web, 在 spring Web MVC中,处理问题的库.zip

    problem-spring-web, 在 spring Web MVC中,处理问题的库 Web站点MVC问题 问题 spring 是一个库,它使得从 spring 应用程序中生成 application/problem json 响应变得容易。 工具填补了一个利基,它将问题库和mvc...

Global site tag (gtag.js) - Google Analytics