栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java > SpringBoot

利用 Spring Boot 设计风格良好的Restful API及错误响应

SpringBoot 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

利用 Spring Boot 设计风格良好的Restful API及错误响应

一、前言

网上经常会看到一些文章,旨在介绍如何使用Spring MVC或Spring Boot实现Restful接口,譬如:

 @RequestMapping(value = "/addUser", method = RequestMethod.POST)    public boolean addUser( User user) {
        System.out.println("开始新增...");        return userService.addUser(user);
    }    
    @RequestMapping(value = "/updateUser", method = RequestMethod.PUT)    public boolean updateUser( User user) {
        System.out.println("开始更新...");        return userService.updateUser(user);
    }    
    @RequestMapping(value = "/deleteUser", method = RequestMethod.DELETE)    public boolean delete(@RequestParam(value = "userName", required = true) int userId) {
        System.out.println("开始删除...");        return userService.deleteUser(userId);
    }    
    @RequestMapping(value = "/userId", method = RequestMethod.GET)    public User findByUserId(@RequestParam(value = "userId", required = true) int userId) {
        System.out.println("开始查询...");        return userService.findUserById(userId);
    }

对于如上实现方式,本人着实不敢恭维。试问,这算哪门子的Restful?自认为其与Restful无丝毫关系,有误导众人之嫌。

对于RESTful 的相关概念,以及其API的设计方法,本人极力推荐阮一峰大神的文章《RESTful API 设计指南》,仅此一篇足够已。在此基础上,本人将小试牛刀,介绍如何在Spring Boot项目中设计风格良好的Restful API,以及如何实现Restful的错误响应。

文笔拙劣,并且水平有限,望各位看官不吝赐教,相互交流~

二、项目介绍

本项目IDE使用 intellij idea 2018, 构建工具使用Maven,JDK使用1.8。方便起见,我们可以使用maven的原型插件maven-archetype-quickstart快速建立一个Java 工程,在此基础上再进行功能开发。

2.1 目录结构

image.png

如上目录结构,了解Spring Boot或Spring MVC开发的朋友应该再熟悉不过了。这是一个较简单用户(User)服务,目前只实现了对用户模型基本的增删改查功能,尚未考虑多种异常情况。

2.2 项目依赖

在pom.xml中引入如下依赖:


        
            junit
            junit
            4.11
            test
        

        
            org.springframework.boot
            spring-boot-starter-web
            1.5.14.RELEASE
        

        
            org.springframework.boot
            spring-boot-starter-data-jpa
            1.5.14.RELEASE
        

        
            io.springfox
            springfox-swagger2
            2.9.2
        

        
            io.springfox
            springfox-swagger-ui
            2.9.2
        

        
            mysql
            mysql-connector-java
            5.1.46
        
    

由上可知,我们分别引入了如下依赖:

  • spring-boot-starter-web
    众所周知,这是使用spring boot做web开发的必备依赖

  • spring-boot-starter-data-jpa
    本项目使用JPA作为ORM框架

  • springfox-swagger2与springfox-swagger-ui
    swagger是个好东西,可以用来生成RESTFUL接口的在线文档,而且更牛逼的是可以直接在文档中进行接口测试,代替Postman。在Spring Boot工程中,可以引入这两个依赖实现swagger的众多功能。

  • mysql-connector-java
    不必多言,使用mysql必备

2.3 Spring Boot 配置文件

在resources目录中定义配置文件application.properties:

spring.jpa.database=MySQL
spring.datasource.url=jdbc:mysql:/
    private String error;    
    private int code;    public Result(String error, int code)
    {        this.error = error;        this.code = code;
    }    public String getError()
    {        return error;
    }    public void setError(String error)
    {        this.error = error;
    }    public int getCode()
    {        return code;
    }    public void setCode(int code)
    {        this.code = code;
    }    public enum ErrorCode{        
        USER_NOT_FOUND(40401),        
        USER_ALREADY_EXIST(40001),
        ;        private int code;        public int getCode()
        {            return code;
        }

        ErrorCode(int code)
        {            this.code = code;
        }
    }
}

可以看到,该对象中还定义了错误码的枚举类。接着,我们修改返回接口的返回类型为Object:

    @GetMapping(value = "/users/{id}")    public Object getUser(@PathVariable("id") String id)    {        return userService.getUser(id);
    }

我们考虑被添加的用户已存在的错误情况,修改Service:

    public Object getUser(String id)
    {
        User currentInstance = userRepository.findOne(id);        if (currentInstance == null)
        {            return new Result("user " + id + "is exist!",
                    Result.ErrorCode.USER_ALREADY_EXIST.getCode());
        }        return userRepository.findOne(id);
    }

好了,我们现在使用Swagger进行测试,查找一个不存在的用户abc:

image.png


返回结果如我们所料,但是HTTP的响应码却还是200,应该是404。所以,紧靠这些无法满足我们的需求。当然,我们可以自定义拦截器实现响应码修改。这里,有一个更好的解决方案——Spring 全局异常处理机制。我们可以通过使用@ControllerAdvice注解定义全局统一的异常处理类来完成需求。


也即是说,在处理错误时,我们不再直接返回Result对象,而采用异常机制。其实,我个人也觉得代码中到处返回Result对象真是一个bad smell。在Java中,错误得情况难道还有比异常更好的表现方式么?

好吧,废话太多了,开始实现。新建一个全局异常GlobalException ,其作为众多自定义异常的父类:

public class GlobalException extends Exception {    private int code;    public GlobalException(String message)
    {        super(message);
    }    public GlobalException(String message, int code)
    {        super(message);        this.code = code;
    }    public void setCode(int code)
    {        this.code = code;
    }    public int getCode()
    {        return code;
    }
}

新建一个自定义异常NotFoundException,该异常专门用来表示各种类型资源不存在的异常情况:

public class NotFoundException extends GlobalException{    public NotFoundException(String message, int code)
    {        super(message, code);
    }
}

新建类RestExceptionHandler,使用注解@ControllerAdvice,如下:

@ControllerAdvicepublic class RestExceptionHandler{    private static Logger logger = LoggerFactory.getLogger(RestExceptionHandler.class);    @ExceptionHandler(value = NotFoundException.class)    @ResponseBody
    @ResponseStatus(HttpStatus.NOT_FOUND)    public Result handleResourceNotFoundException(NotFoundException e)
    {
        logger.error(e.getMessage(), e);        return new Result(e.getMessage(), e.getCode());
    }
}

如上,通过使用注解@ControllerAdvice,类RestExceptionHandler就可以实现全局异常的拦截处理功能。自定义的方法handleResourceNotFoundException旨在拦截NotFoundException异常,一旦拦截成功后,我们可以进行各种处理操作,并且返回自己想要的结果。

其中,注解@ExceptionHandler表示要拦截的异常;注解@ResponseStatus可以指定HTTP响应的状态码;当然,注解@ResponseBody也必不可少。

OK,让我们先修改之前的用户查找接口,并且抛出异常:

    public Object getUser(String id) throws NotFoundException    {
        User currentInstance = userRepository.findOne(id);        if (currentInstance == null)
        {            throw new NotFoundException("user " + id + " is not exist!", Result.ErrorCode.USER_NOT_FOUND.getCode());
        }        return userRepository.findOne(id);
    }

当然,Controller也要抛出异常:

    @GetMapping(value = "/users/{id}")    public Object getUser(@PathVariable("id") String id) throws NotFoundException    {        return userService.getUser(id);
    }

OK,重新请求一个不存在的用户尝试一下:


image.png


如上,如我们所愿,HTTP响应码也返回了,同时查看服务控制台:

com.mystudy.spring.exception.NotFoundException: user abc is not exist!
    at com.mystudy.spring.service.UserService.getUser(UserService.java:36) ~[classes/:na]
    at com.mystudy.spring.api.UserController.getUser(UserController.java:36) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-4.3.18.RELEASE.jar:4.3.18.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133) ~[spring-web-4.3.18.RELEASE.jar:4.3.18.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) ~[spring-webmvc-4.3.18.RELEASE.jar:4.3.18.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.18.RELEASE.jar:4.3.18.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.18.RELEASE.jar:4.3.18.RELEASE]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.18.RELEASE.jar:4.3.18.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) ~[spring-webmvc-4.3.18.RELEASE.jar:4.3.18.RELEASE]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) ~[spring-webmvc-4.3.18.RELEASE.jar:4.3.18.RELEASE]
    at org.springframework.web.servlet.frameworkServlet.processRequest(frameworkServlet.java:970) [spring-webmvc-4.3.18.RELEASE.jar:4.3.18.RELEASE]
    at org.springframework.web.servlet.frameworkServlet.doGet(frameworkServlet.java:861) [spring-webmvc-4.3.18.RELEASE.jar:4.3.18.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:635) [tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.springframework.web.servlet.frameworkServlet.service(frameworkServlet.java:846) [spring-webmvc-4.3.18.RELEASE.jar:4.3.18.RELEASE]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) [tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-8.5.31.jar:8.5.31]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.31.jar:8.5.31]
省略

没错,这才打开的正确方式!一旦接口抛出异常,Spring 马上拦截并进行处理,最后返回自定义的错误对象。当然,若接口一切正常,还是按正常逻辑返回模型对象。

同理,我们还可以新建多种其他异常,比如表示非法参数、权限不足等。需要注意的是,请不要为每种资源都新建异常,比如你不需要创建UserNotFoundException、BookNotFoundException等,否则会显得多么繁琐。

五、后语

至此,本文的目标已经达成,首先介绍了如何使用Spring Boot设计Restful API,然后介绍了常用的Restful 错误响应风格,最后利用Spring Boot的全局异常处理机制实现了Restful 的错误响应功能。



作者:宅楠军
链接:https://www.jianshu.com/p/d6424d98b02e


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/235537.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号