zoukankan      html  css  js  c++  java
  • Spring Boot提供RESTful接口时的错误处理实践

    本文首发于个人网站:http://www.javaadu.online/,如需转载,请注明出处

    使用Spring Boot开发微服务的过程中,我们会使用别人提供的接口,也会设计接口给别人使用,这时候微服务应用之间的协作就需要有一定的规范。

    • 基于rpc协议,我们一般有两种思路:(1)提供服务的应用统一将异常包起来,然后用错误码交互;(2)提供服务的应用将运行时异常抛出,抛出自定义的业务异常,服务的调用者通过异常catch来处理异常情况。

    • 基于HTTP协议,那么最流行的就是RESTful协议,服务提供方会自己处理所有异常,并且返回的结果中会跟HTTP的状态码相结合,这篇文章我们就用一个例子来说明RESTful接口的错误处理如何做。

    首先我们需要新建一个简单的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

    Spring Boot 1.x系列

    1. Spring Boot的自动配置、Command-line-Runner
    2. 了解Spring Boot的自动配置
    3. Spring Boot的@PropertySource注解在整合Redis中的使用
    4. Spring Boot项目中如何定制HTTP消息转换器
    5. Spring Boot整合Mongodb提供Restful接口
    6. Spring中bean的scope
    7. Spring Boot项目中使用事件派发器模式

    本号专注于后端技术、JVM问题排查和优化、Java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。
    javaadu

  • 相关阅读:
    video 属性和事件用法大全
    微信小程序 组件通信相关知识整理
    JavaScript实现登录窗口的拖拽
    JS 各种宽高
    CSS3 Animation
    CSS3 Transition
    CSS3 Transform
    vue 回到页面顶部
    element-ui 动态换肤
    Chrome浏览器下自动填充的输入框背景
  • 原文地址:https://www.cnblogs.com/javaadu/p/11742545.html
Copyright © 2011-2022 走看看