博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringBoot中使用springfox+swagger2书写API文档
阅读量:5281 次
发布时间:2019-06-14

本文共 11379 字,大约阅读时间需要 37 分钟。

随着前后端的分离,借口文档变的尤其重要,springfox是通过注解的形式自动生成API文档,利用它,可以很方便的书写restful API,swagger主要用于展示springfox生成的API文档。

官网地址:http://springfox.github.io/springfox/

Springfox大致原理

springfox的大致原理就是,在项目启动的过种中,spring上下文在初始化的过程,框架自动跟据配置加载一些swagger相关的bean到当前的上下文中,并自动扫描系统中可能需要生成api文档那些类,并生成相应的信息缓存起来。如果项目MVC控制层用的是springMvc那么会自动扫描所有Controller类,跟据这些Controller类中的方法生成相应的api文档。

Spring集成Springfox步骤及说明:

一、添加Swagger2依赖

io.springfox
springfox-swagger2
2.6.1
io.springfox
springfox-swagger-ui
2.6.1

二、application.properties中添加配置

#解决中文乱码问题banner.charset=UTF-8server.tomcat.uri-encoding=UTF-8spring.http.encoding.charset=UTF-8spring.http.encoding.enabled=truespring.http.encoding.force=truespring.messages.encoding=UTF-8#Swagger Configure Propertiessop.swagger.enable=truesop.swagger.packageScan=com.examplesop.swagger.title=UserController Restfull APIsop.swagger.description=UserController Restfull APIsop.swagger.version=3.0

三、创建SwaggerConfigProperties加载配置项

package com.example.config;import java.io.Serializable;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;@ConfigurationProperties(prefix = "sop.swagger")@Componentpublic class SwaggerConfigProperties implements Serializable {    /**     * 是否开启Swagger     */    private boolean enable = false;    /**     * 要扫描的包     */    private String packageScan;    /**     * 标题     */    private String title;    /**     * 描述     */    private String description;    /**     * 版本信息     */    private String version;    public boolean isEnable() {        return enable;    }    public void setEnable(boolean enable) {        this.enable = enable;    }    public String getPackageScan() {        return packageScan;    }    public void setPackageScan(String packageScan) {        this.packageScan = packageScan;    }    public String getTitle() {        return title;    }    public void setTitle(String title) {        this.title = title;    }    public String getDescription() {        return description;    }    public void setDescription(String description) {        this.description = description;    }    public String getVersion() {        return version;    }    public void setVersion(String version) {        this.version = version;    }}

四、创建Swagger2配置类

package com.example.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import springfox.documentation.builders.ApiInfoBuilder;import springfox.documentation.builders.PathSelectors;import springfox.documentation.builders.RequestHandlerSelectors;import springfox.documentation.service.ApiInfo;import springfox.documentation.spi.DocumentationType;import springfox.documentation.spring.web.plugins.Docket;import springfox.documentation.swagger2.annotations.EnableSwagger2;@Configuration@EnableSwagger2public class Swagger2 {    @Autowired    private SwaggerConfigProperties scp;    @Bean    public Docket createRestApi() {        return new Docket(DocumentationType.SWAGGER_2)            .apiInfo(apiInfo())            .select()            .apis(RequestHandlerSelectors.basePackage("com.example.web"))            .paths(PathSelectors.any())            .build();    }    private ApiInfo apiInfo() {        return new ApiInfoBuilder()            .title(scp.getTitle())            .description(scp.getDescription())            .version("1.0")            .build();    }}

五、创建model

package com.example.model;import io.swagger.annotations.ApiModelProperty;public class User {    @ApiModelProperty(value = "主键")    private Long id;    @ApiModelProperty(value = "名字")    private String name;    @ApiModelProperty(value = "年龄")    private Integer age;    @ApiModelProperty(value = "密码")    private String password;    public Long getId() {        return id;    }    public void setId(Long id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public Integer getAge() {        return age;    }    public void setAge(Integer age) {        this.age = age;    }    public String getPassword() {        return password;    }    public void setPassword(String password) {        this.password = password;    }    @Override    public String toString() {        return "User{" +            "id=" + id +            ", name='" + name + '\'' +            ", age=" + age +            ", password=" + password +            '}';    }}

六、创建Controller

package com.example.web;import java.util.Collections;import java.util.HashMap;import java.util.Map;import com.example.model.User;import io.swagger.annotations.Api;import io.swagger.annotations.ApiImplicitParam;import io.swagger.annotations.ApiImplicitParams;import io.swagger.annotations.ApiOperation;import io.swagger.annotations.ApiResponse;import io.swagger.annotations.ApiResponses;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestHeader;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import springfox.documentation.annotations.ApiIgnore;@RestController@Api("userController相关api")public class UserController {    @ApiOperation("获取用户信息")    @ApiImplicitParams({        @ApiImplicitParam(paramType = "header", name = "username", dataType = "String", required = true,            value = "用户的姓名", defaultValue = "xiaoqiang"),        @ApiImplicitParam(paramType = "query", name = "password", dataType = "String", required = true, value = "用户的密码",            defaultValue = "xiaoxiong")    })    @ApiResponses({        @ApiResponse(code = 400, message = "请求参数没填好"),        @ApiResponse(code = 404, message = "请求路径没有或页面跳转路径不对")    })    @RequestMapping(value = "/getUser", method = RequestMethod.GET)    public User getUser(@RequestHeader("username") String username, @RequestParam("password") String password) {        User user = new User();        user.setName(username);        user.setPassword(password);        return user;    }    @ApiOperation(value = "创建用户", notes = "根据User对象创建用户")    @ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User")    @RequestMapping(value = "", method = RequestMethod.POST)    public String postUser(@RequestBody User user) {        Map
users = Collections.synchronizedMap(new HashMap
()); users.put(user.getId(), user); return "success"; } @ApiIgnore @RequestMapping(value = "/", method = RequestMethod.GET) public String home() { return "hello"; }}

完成上述代码添加上,启动Spring Boot程序,访问:http://localhost:8080/swagger-ui.html

。就能看到前文所展示的RESTful API的页面。我们可以再点开具体的API请求,以POST类型的/users请求为例,可找到上述代码中我们配置的Notes信息以及参数user的描述信息,如下图所示。

D0XknP8.png

七、API文档访问与调试

在上图请求的页面中,我们看到user的Value是个输入框?是的,Swagger除了查看接口功能外,还提供了调试测试功能,我们可以点击上图中右侧的Model Schema(黄色区域:它指明了User的数据结构),此时Value中就有了user对象的模板,我们只需要稍适修改,点击下方“Try it out!”按钮,即可完成了一次请求调用!

此时,你也可以通过几个GET请求来验证之前的POST请求是否正确。

相比为这些接口编写文档的工作,我们增加的配置内容是非常少而且精简的,对于原有代码的侵入也在忍受范围之内。因此,在构建RESTful API的同时,加入swagger来对API文档进行管理,是个不错的选择。

八、springfox、swagger.annotations注解部分参数介绍

在上面只展示了如何使用,这里将对上面添加的swagger注解进行说明,笔记使用时参考了swagger annotations Api 手册,接下来进行部分常用注解使用说明介绍。

  • @ApiIgnore 忽略注解标注的类或者方法,不添加到API文档中
  • @ApiOperation 展示每个API基本信息
    • value api名称
    • notes 备注说明
  • @ApiImplicitParam 用于规定接收参数类型、名称、是否必须等信息
    • name 对应方法中接收参数名称
    • value 备注说明
    • required 是否必须 boolean
    • paramType 参数类型 body、path、query、header、form中的一种
    • body 使用@RequestBody接收数据 POST有效
    • path 在url中配置{}的参数
    • query 普通查询参数 例如 ?query=q ,jquery ajax中data设置的值也可以,例如 {query:”q”},springMVC中不需要添加注解接收
    • header 使用@RequestHeader接收数据
    • form 笔者未使用,请查看官方API文档
    • dataType 数据类型,如果类型名称相同,请指定全路径,例如 dataType = “java.util.Date”,springfox会自动根据类型生成模型
  • @ApiImplicitParams 包含多个@ApiImplicitParam
  • @ApiModelProperty 对模型中属性添加说明,例如 上面的PageInfoBeen、BlogArticleBeen这两个类中使用,只能使用在类中。
    • value 参数名称
    • required 是否必须 boolean
    • hidden 是否隐藏 boolean
    • 其他信息和上面同名属性作用相同,hidden属性对于集合不能隐藏,目前不知道原因
  • @ApiParam 对单独某个参数进行说明,使用在类中或者controller方法中都可以。注解中的属性和上面列出的同名属性作用相同

其他注解:https://github.com/swagger-api/swagger-core/wiki/Annotations#apimodel

九、springfox中的那些坑

springfox第一大坑:Controller类的参数,注意防止出现无限递归的情况。

Spring mvc有强大的参数绑定机制,可以自动把请求参数绑定为一个自定义的命令对像。所以,很多开发人员在写Controller时,为了偷懒,直接把一个实体对像作为Controller方法的一个参数。比如下面这个示例代码:

@RequestMapping(value = "update")
public String update(MenuVo menuVo, Model model){
}
这是大部分程序员喜欢在Controller中写的修改某个实体的代码。在跟swagger集成的时候,这里有一个大坑。如果MenuVo这个类中所有的属性都是基本类型,那还好,不会出什么问题。但如果这个类里面有一些其它的自定义类型的属性,而且这个属性又直接或间接的存在它自身类型的属性,那就会出问题。例如:假如MenuVo这个类是菜单类,在这个类时又含有MenuVo类型的一个属性parent代表它的父级菜单。这样的话,系统启动时swagger模块就因无法加载这个api而直接报错。报错的原因就是,在加载这个方法的过程中会解析这个update方法的参数,发现参数MenuVo不是简单类型,则会自动以递归的方式解释它所有的类属性。这样就很容易陷入无限递归的死循环。

为了解决这个问题,我目前只是自己写了一个OperationParameterReader插件实现类以及它依赖的ModelAttributeParameterExpander工具类,通过配置的方式替换掉到srpingfox原来的那两个类,偷梁换柱般的把参数解析这个逻辑替换掉,并避开无限递归。当然,这相当于是一种修改源码级别的方式。我目前还没有找到解决这个问题的更完美的方法,所以,只能建议大家在用spring-fox Swagger的时候尽量避免这种无限递归的情况。毕竟,这不符合springmvc命令对像的规范,springmvc参数的命令对像中最好只含有简单的基本类型属性。

springfox第二大坑:api分组相关,Docket实例不能延迟加载

springfox默认会把所有api分成一组,这样通过类似于http://127.0.0.1:8080/jadDemo/swagger-ui.html这样的地址访问时,会在同一个页面里加载所有api列表。这样,如果系统稍大一点,api稍微多一点,页面就会出现假死的情况,所以很有必要对api进行分组。api分组,是通过在ApiConf这个配置文件中,通过@Bean注解定义一些Docket实例,网上常见的配置如下:

@EnableWebMvc@EnableSwagger2public class ApiConfig {@Bean public Docket customDocket() {       return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());    }}

上述代码中通过@Bean注入一个Docket,这个配置并不是必须的,如果没有这个配置,框架会自己生成一个默认的Docket实例。这个Docket实例的作用就是指定所有它能管理的api的公共信息,比如api版本、作者等等基本信息,以及指定只列出哪些api(通过api地址或注解过滤)。

Docket实例可以有多个,比如如下代码:

@EnableWebMvc@EnableSwagger2public class ApiConfig {@Bean public Docket customDocket1() {       return new Docket(DocumentationType.SWAGGER_2).groupName("apiGroup1").apiInfo(apiInfo()).select().paths(PathSelectors.ant("/sys/**"));    }@Bean public Docket customDocket2() {       return new Docket(DocumentationType.SWAGGER_2).groupName("apiGroup2").apiInfo(apiInfo()).select().paths(PathSelectors.ant("/shop/**"));    }}

当在项目中配置了多个Docket实例时,也就可以对api进行分组了,比如上面代码将api分为了两组。在这种情况下,必须给每一组指定一个不同的名称,比如上面代码中的"apiGroup1"和"apiGroup2",每一组可以用paths通过ant风格的地址表达式来指定哪一组管理哪些api。比如上面配置中,第一组管理地址为/sys/开头的api第二组管理/shop/开头的api。当然,还有很多其它的过滤方式,比如跟据类注解、方法注解、地址正则表达式等等。分组后,在api 列表界面右上角的下拉选项中就可以选择不同的api组。这样就把项目的api列表分散到不同的页面了。这样,即方便管理,又不致于页面因需要加载太多api而假死。

然而,同使用@Configuration一样,我并不赞成使用@Bean来配置Docket实例给api分组。因为这样,同样会把代码写死。所以,我推荐在xml文件中自己配置Docket实例实现这些类似的功能。当然,考虑到Docket中的众多属性,直接配置bean比较麻烦,可以自己为Docket写一个FactoryBean,然后在xml文件中配置FactoryBean就行了。然而将Docket配置到xml中时。又会遇到一个大坑,就那是,spring对bean的加载方式默认是延迟加载的,在xml中直接配置这些Docket实例Bean后。你会发现,没有一点效果,页面左上角的下拉列表中跟本没有你的分组项。
这个问题曾困扰过我好几个小时,后来凭经验推测出可能是因为sping bean默认延迟加载,这个Docket实例还没加载到spring context中。实事证明,我的猜测是对的。我不知道这算是springfox的一个bug,还是因为我跟本不该把对Docket的配置从原来的java代码中搬到xml配置文件中来。

springfox其它的坑:

springfox还有些其它的坑,比如@ApiOperation注解中,如果不指定httpMethod属性具体为某个get或post方法时,api列表中,会它get,post,delete,put等所有方法都列出来,搞到api列表重复的太多,很难看。另外,还有在测试时,遇到登录权限问题,等等。这一堆堆的比较容易解决的小坑,因为篇幅有限,我就不多说了。还有比如@Api、@ApiOperation及@ApiParam等等注解的用法,网上很多这方面的文档,我就不重复了。

部分内容参考:http://m.w2bc.com/article/229092

转载于:https://www.cnblogs.com/lspz/p/6840229.html

你可能感兴趣的文章
codeforces A. Supercentral Point 题解
查看>>
mac 80端口映射 配置
查看>>
内部类
查看>>
linux简介
查看>>
倍福TwinCAT(贝福Beckhoff)基础教程2.2 TwinCAT常见类型使用和转换_数组
查看>>
SpringBoot | 第十三章:测试相关(单元测试、性能测试)
查看>>
phpunit 单元测试框架-代码覆盖率
查看>>
oracle 定时备份文件,
查看>>
执行robot framework 的测试用例 命令行pybot使用方式
查看>>
Lucidpress | Free Design Tool(Web打印)
查看>>
小论面向对象编程方法
查看>>
解决SpringSecurity阻止ajax的POST和PUT请求,导致403Forbidden的问题
查看>>
WPF样式之画刷结合样式
查看>>
DIV的高度自动拉伸(height属性)…
查看>>
Pastoralism in Ancient Inner Eurasia
查看>>
Servlet 的认识
查看>>
Bitmap重要属性整理Bitmap
查看>>
【听课笔记】算法导论2
查看>>
解决弹出层被FLASH覆盖(Flash置底)
查看>>
java反射工具类
查看>>