Spring Boot Error Response

本文翻译自:Spring Boot Error Responses

在昨天的文章最后,我提到一个问题,就是我的例子对错误处理的设计不够。按照RESTful的设计,既然请求是借助HTTP的方法,那么返回信息也应该借助HTTP的状态码和其他信息。经过查找资料,决定将这篇文章中提到的例子实践一次,并用我的话总结下。

首先我们需要新建一个简单的Controller,代码如下:

@RestController
class GreetingController {

    @RequestMapping("/greet")
    String sayHello(@RequestParam("name") String name) {
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("The 'name' parameter must not be null or empty");
        }
        return String.format("Hello %s!", name);
    }
}

通过http请求客户端——httpie发送HTTP请求,这个工具比curl的好处是:返回值信息有语法高亮、对返回的JSON字符串自动格式化。启动服务器,使用命令http http://127.0.0.1:8080/greet?name=duqi发起请求,结果如下:

HTTP/1.1 200 OK
Content-Length: 11
Content-Type: text/plain;charset=UTF-8
Date: Sat, 05 Dec 2015 05:45:03 GMT
Server: Apache-Coyote/1.1
X-Application-Context: application

现在我们制造一个错误的请求,@RequestParam是获取URL中的参数,如果这个参数不提供则会出错。因此,我们发送一个命令http http://127.0.0.1:8080,看结果如何。

HTTP/1.1 400 Bad Request
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Sat, 05 Dec 2015 05:54:06 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
X-Application-Context: application

{
    "error": "Bad Request",
    "exception": "org.springframework.web.bind.MissingServletRequestParameterException",
    "message": "Required String parameter 'name' is not present",
    "path": "/greet",
    "status": 400,
    "timestamp": 1449294846060
}

可以看到,由于没有提供name参数,服务器返回的状态码是400:错误的请求。在响应体中的内容依次如下:

  • error : 错误信息;
  • exception:异常的类型,MissingServletRequestParameterExeption,见名知意,说明是缺少了某个请求参数;
  • message:对异常的说明
  • path:显示请求的URL路径;
  • status:表示返回的错误码
  • timestamp:错误发生的时间戳,调用System.currentMills()

如果我们给定name参数,却不给它赋值,又会如何?发送命令http http:127.0.0.1:8080/greet?name,则服务器的返回值如下:

HTTP/1.1 500 Internal Server Error
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Sat, 05 Dec 2015 06:01:24 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
X-Application-Context: application

{
    "error": "Internal Server Error",
    "exception": "java.lang.IllegalArgumentException",
    "message": "The 'name' parameter must not be null or empty",
    "path": "/greet",
    "status": 500,
    "timestamp": 1449295284160
}

对比上面,可以看出,这次返回的错误码是500,表示服务器内部错误;返回的异常类型是java.lang.IllegalArgumentException,表示参数不合法。服务器内部错误表示服务器抛出了异常缺没有处理,我们更愿意API返回400,告诉调用者自己哪里做错了。如何实现呢?利用@ExceptionHandler注解即可。

在GreetingController控制器中加入如下处理函数,用于捕获这个控制器的异常。

@ExceptionHandler
void handleIllegalArgumentException(IllegalArgumentException e, 
                        HttpServletResponse response) throws IOException {    
      response.sendError(HttpStatus.BAD_REQUEST.value());
}

再次发送命令http http:127.0.0.1:8080/greet?name,则返回下面的结果:

HTTP/1.1 400 Bad Request
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Sat, 05 Dec 2015 06:08:50 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
X-Application-Context: application

{
    "error": "Bad Request",
    "exception": "java.lang.IllegalArgumentException",
    "message": "The 'name' parameter must not be null or empty",
    "path": "/greet",
    "status": 400,
    "timestamp": 1449295729978
}

说明我们在服务器端捕获了IllegalArgumentException这个异常,并设置response的返回码为400。如果你想对多个异常都进行一样的处理,则上述异常处理代码可以修改为下面这样(给@ExceptionHandler传入参数):

@ExceptionHandler({IllegalArgumentException.class, NullPointerException.class})
void handleIllegalArgumentException(HttpServletResponse response) throws IOException {
    response.sendError(HttpStatus.BAD_REQUEST.value());
}

现在这个异常处理代码是加在当前的这个控制器中,因此它只处理属于这个控制器的响应,如果我们新建一个类,并用注解@ControllerAdvice修饰,并在这个类中定义上述的异常处理代码,则它会负责处理所有的请求。

Spring Boot 1.2.0以后,还支持在response修改对应的message,只要将对应的message信息传入sendError函数即可,例如:

@ExceptionHandler({IllegalArgumentException.class, NullPointerException.class})
void handleIllegalArgumentException(HttpServletResponse response) throws IOException {
    response.sendError(HttpStatus.BAD_REQUEST.value(), 
         "Please try again and with a non empty string as 'name'");
}

再次执行同样的命令,会收到下列反馈:

HTTP/1.1 400 Bad Request
Connection: close
Content-Type: application/json;charset=UTF-8
Date: Sat, 05 Dec 2015 06:21:05 GMT
Server: Apache-Coyote/1.1
Transfer-Encoding: chunked
X-Application-Context: application

{
    "error": "Bad Request",
    "exception": "java.lang.IllegalArgumentException",
    "message": "Please try again and with a non empty string as 'name'",
    "path": "/greet",
    "status": 400,
    "timestamp": 1449296465060
}

如果希望验证请求的参数,可以使用JSR-303 Bean Validation API,并参考Spring Validation。在spring.io上还有一个验证表单输入的例子Validating Form Input

参考资料

  1. 模拟GET/POST请求的工具
  2. Spring Boot Error Response

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏代码拾遗

深入理解Spring MVC

使用Spring Boot和web,thymeleaf的starter来设置初始工程。xml配置如下:

1042
来自专栏我是攻城师

关于自旋锁的公平和非公平模式

自旋锁是并发编程实战里面一个关于锁优化的非常重要的一个概念,通常情况下会配合CAS原语来实现轻量级的同步操作。

912
来自专栏pangguoming

Spring Boot Maven Plugin打包异常及三种解决方法:Unable to find main class

4122
来自专栏玩转JavaEE

SpringMVC常用配置

按:最近公众号文章主要是整理一些老文章,主要是个人CSDN上的博客,也会穿插一些新的技术点。 ---- 关于Spring、SpringMVC我们前面几篇博客都介...

2926
来自专栏SDNLAB

OpenDaylight Carbon二次开发实用指南

通过本文你将知道: Maven Archetype的基本原理以及如何使用Maven Archetype生成适用于不同版本的ODL子项目。 本文将着重讲解cli命...

43215
来自专栏程序员互动联盟

linux设备驱动第五篇:驱动中的并发与竟态

综述 在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争。 首先什么是并发与竟态呢?并发(concurr...

34810
来自专栏小勇DW3

Spring的原理性总结

Bean的生命过程可以借鉴Servlet的生命过程,了解其生命过程对于不管是思想还是以后的使用都很有帮助;

1.5K5
来自专栏张善友的专栏

WCF RESTful服务的Google Protocol Buffers超媒体类型

Protocol Buffers 是在一个很理想的结构化数据的语言中立的序列化格式。你可以考虑一下XML或JSON,但更轻,更小的协议缓冲区。 这种格式的广应用...

2237
来自专栏JavaEE

Thymeleaf的使用前言:一、thymeleaf简介:二、thymeleaf标准方言:三、thymeleaf与springboot集成案例:总结:

最近听说thymeleaf好像也挺流行的,还说是spring官方推荐使用,那thymeleaf究竟是什么呢?spring为什么推荐用它呢?怎么用呢?本文将为你揭...

1312
来自专栏WindCoder

Spring Boot REST API错误处理指南

本来是5号来的文章,无奈最近准备换工作,一直拖着没写,今天搜索偶然看见有人已经翻译完了,由于时间原因这次就直接转载下吧,现附上英文原文及相关信息,最后再附上译文...

1652

扫码关注云+社区