使用 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 请求客户端 -- https://github.com/jkbrzt/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 https://spring.io/guides/gs/validating-form-input/ .
参考资料
模拟 GET/POST 请求的工具 http://www.jingwentian.com/t-517
Spring Boot Error Response
Spring Boot 1.x 系列
Spring Boot 的自动配置, Command-line-Runner http://www.javaadu.online/?p=487
了解 Spring Boot 的自动配置 http://www.javaadu.online/?p=495
Spring Boot 的 @PropertySource 注解在整合 Redis 中的使用 http://www.javaadu.online/?p=499
Spring Boot 项目中如何定制 HTTP 消息转换器 http://www.javaadu.online/?p=515
Spring Boot 整合 MongoDB 提供 Restful 接口 http://www.javaadu.online/?p=518
Spring 中 bean 的 scope http://www.javaadu.online/?p=521
Spring Boot 项目中使用事件派发器模式 http://www.javaadu.online/?p=526
本号专注于后端技术, JVM 问题排查和优化, Java 面试题, 个人成长和自我管理等主题, 为读者提供一线开发者的工作和成长经验, 期待你能在这里有所收获.
来源: https://www.cnblogs.com/javaadu/p/11742545.html