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请求客户端——发送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。
参考资料
spring boot 1.x系列
- spring boot的自动配置、command-line-runner
- 了解spring boot的自动配置
- spring boot的@propertysource注解在整合redis中的使用
- spring boot项目中如何定制http消息转换器
- spring boot整合mongodb提供restful接口
- spring中bean的scope
- spring boot项目中使用事件派发器模式
本号专注于后端技术、jvm问题排查和优化、java面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。