SpringMvc集成Springfox使用Swagger写文档和测试

前言

swagger简介

swagger确实是个好东西,可以跟据业务代码自动生成相关的api接口文档,尤其用于restful风格中的>>项目,开发人员几乎可以不用专门去维护rest >api,这个框架可以自动为你的业务代码生成restfut风格的api,而且还提供相应的测试界面,自动显>>示json格式的响应。大大方便了后台开发人员与前端的沟通与联调成本。

springfox-swagger简介

Swagger 是一系列对 RESTful 接口进行规范描述和页面展示的工具. 通过 springfox-swagger 将
Swagger 与 Spring-MVC 整合, 可从代码中的注解获取信息,
并生成相应的文档。springfox本身只是利用自身的aop的特点,通过plug的方式把swagger集成了进来>>,它本身对业务api的生成,还是依靠swagger来实现。

springfox大致原理

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

注意要点

一:SpringMVC与Spring分别是两个容器,把Swagger注入到根容器(Spring)中,可能会导致异常

Spring是根容器,SpringMvc是子容器,为了能自动扫描出所有Controller类,以制定数据缓存供页>> 面api使用,有些bean需要依赖于SpringMvc中的一些bean,但这时候如果把这个注入到spring容器>>中了,因为rootcontext中没有spring mvc的context中的那些配置类时就会报错。

所以,我们解决方案是,1:保证这个类所在的路径刚好在springmvc的component-scan的配置的b ase-package范围内, 2:直接在Spring-mvc.xml配置文件中使用标签,把这个类直接进行注入,所以我们可以不使用@Configuration注解让Spring进行注入,而采用SpringMVC的配置文件的方式注入,当我们在上正式的时候,不进行注入就可以了。也不用在编辑代码去掉配置@Configuration,而重新编译一下了。


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

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

然而,同使用@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配置文件中来。


三:Controller类的参数,注意防止出现无限递归的情况

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

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

SpringMVC+SpringFox的集成

1. 增加依赖

<!--springfox-swagger需要的最小依赖 start--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.5.0</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.5.0</version></dependency><!--jackson用于将springfox返回的文档对象转换成JSON字符串--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>${version.jackson}</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>${version.jackson}</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>${version.jackson}</version></dependency><!--petStore是官方提供的一个代码参考, 可用于后期写文档时进行参考, 可不加--><dependency><groupId>io.springfox</groupId><artifactId>springfox-petstore</artifactId><version>2.5.0</version></dependency>
<!--最小依赖 end-->

2. spring-fox 提供了配置信息

说明:下面例子是配置里两个分组,分组是什么呢?
分组其实就是按照一定的规则显示接口的文档,相当于文档的指定文档的目录。
- 通过.paths(PathSelectors.ant(“/user/**”)).build()配置匹配请求地址进行分组。
- 下面是定义了两个分组,userDocket,shopDocket,注意分组名称不可相同。
- 当分组没有效果时,可以参考此配置,还可以检查配置的顺序有可能导致分组的配置失效。

package com.pkk.ssms1.config;import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;/*** @author peikunkun* @version V1.0* @Title: mybatisproject* @Package com.pkk.ssms1.config* @Description: <Swagger配置类>* @date 2018/4/12 20:02*//*spring框架中本身就有的
* 有了这个注解后,spring会自动把这个类实例化成一个bean注册到spring上下文中
* 但是Spring和SpringMVC是俩个不同的容器,在使用的时候我们需要把其注入到SpringMVC容器器中,而此配置在上正式的时候,
* 需要取消掉,因为一般此api只是用于测试的时候用,我们可以采用xml配置的方式注册此bean*/
/*@Configuration*/
/*用来集成swagger 2的*/
@EnableSwagger2
/*启用srpingmvc了*/
@EnableWebMvc
public class MySwaggerConfig {private static final String INDEX_PAGE   = "http://localhost:8099/index.jsp";private static final String BASE_PACKAGE = "com.pkk.ssms1";@Beanpublic Docket userDocket() {ApiInfo apiInfo = new ApiInfoBuilder().title("用户信息操作").description("用于操作用户相关接口的文档").contact(new Contact("kunzai", INDEX_PAGE, "youmail")).version("1.0").build();return new Docket(DocumentationType.SWAGGER_2).groupName("userDocket")//配置pathMapping之后,会显示为/useruser/**/user/getInfo/*docket.pathMapping("/user*//**");*/.pathMapping("/")//设置只生成被Api这个注解注解过的Ctrl类中有ApiOperation注解的api接口的文档/*docket.select().apis(RequestHandlerSelectors.withClassAnnotation(Api.class)).apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)).build();*///指定要扫描的包.select().apis(RequestHandlerSelectors.basePackage(BASE_PACKAGE))/*设置此组只匹配user/**的请求*/.paths(PathSelectors.ant("/user/**")).build().apiInfo(apiInfo);}@Beanpublic Docket shopDocket() {ApiInfo apiInfo = new ApiInfoBuilder().title("商品信息操作").description("用于操作商品相关接口的文档").contact(new Contact("kunzai", INDEX_PAGE, "youmail")).version("1.0").build();return new Docket(DocumentationType.SWAGGER_2).groupName("shopDocket")//配置pathMapping之后,会显示为/shop/**/shop/getInfo/*docket.pathMapping("/shop*//**");*/.pathMapping("/")//设置只生成被Api这个注解注解过的Ctrl类中有ApiOperation注解的api接口的文档/*docket.select().apis(RequestHandlerSelectors.withClassAnnotation(Api.class)).apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)).build();*///指定要扫描的包.select().apis(RequestHandlerSelectors.basePackage(BASE_PACKAGE))/*设置此组只匹配user/**的请求,需要放到最后否则无效*/.paths(PathSelectors.ant("/shop/*")).build()/*最后设置并返回Docket*/.apiInfo(apiInfo);}
}

3:接口定义Controller

userController接口定义(属于userDocket)

package com.pkk.ssms1.controller;import javax.servlet.http.HttpServletRequest;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import com.pkk.ssms1.entity.User;import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;/*** @author peikunkun* @version V1.0* @Title: mybatisproject* @Package com.pkk.ssms1.controller* @Description: <>* @date 2018/4/12 20:13*/
@RequestMapping("/user")
@RestController
@Api(tags = "UserController", description = "用户信息相关")
public class UserController {/*https://github.com/catinred2/swagger_test/blob/master/src*/@RequestMapping("/getInfo")@ApiOperation(value = "获取用户信息", httpMethod = "GET", notes = "显示用户信息,不显示密码")public Object getInfo() {return "哈哈,我是信息";}@RequestMapping("/login")@ApiOperation(value = "登录", httpMethod = "POST", notes = "用户登录文档说明", consumes = "application/x-www-form-urlencoded")/*@ApiImplicitParams({@ApiImplicitParam(name = "username", value = "用户名", required = true, defaultValue = "kunzai", paramType = "query", dataType = "String"),@ApiImplicitParam(name = "password", value = "密码(MD5)", required = true, defaultValue = "admin", paramType = "query", dataType = "String"),@ApiImplicitParam(name = "deptid", value = "部门id", required = true, defaultValue = "1", paramType = "query", dataType = "Long"),@ApiImplicitParam(name = "cardid", value = "卡号", required = true, defaultValue = "1", paramType = "query", dataType = "Long"),})*/public Object login(@ApiParam User user, HttpServletRequest request) throws Exception {return "哈哈可以啊";}
}

shopController接口定义(属于shopDocket)

package com.pkk.ssms1.controller;import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import com.pkk.ssms1.entity.User;import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;/*** @author peikunkun* @version V1.0* @Title: mybatisproject* @Package com.pkk.ssms1.controller* @Description: <>* @date 2018/4/12 20:13*/
@RestController
@RequestMapping("/shop")
@Api(tags = "ShopController", description = "商户信息相关")
public class ShopController {/*https://github.com/catinred2/swagger_test/blob/master/src*/@RequestMapping("/getInfo")@ApiOperation(value = "获取商品信息", httpMethod = "GET", notes = "显示商品信息")public Object getInfo() {return "哈哈,我是信息";}@RequestMapping("/login")@ApiOperation(value = "商品信息登录", httpMethod = "POST", notes = "商品信息文档说明", consumes = "application/x-www-form-urlencoded")@ApiImplicitParams({@ApiImplicitParam(name = "username", value = "用户名", required = true, defaultValue = "kunzai", paramType = "query", dataType = "String"),@ApiImplicitParam(name = "password", value = "密码(MD5)", required = true, defaultValue = "admin", paramType = "query", dataType = "String"),@ApiImplicitParam(name = "deptid", value = "部门id", required = true, defaultValue = "1", paramType = "query", dataType = "Long"),@ApiImplicitParam(name = "cardid", value = "卡号", required = true, defaultValue = "1", paramType = "query", dataType = "Long"),})public Object login(@Valid User user, BindingResult bindingResult, HttpServletRequest request) throws Exception {if (bindingResult.hasErrors()) {System.out.println("使用注解@Valid,检验参数时,出现以下错误:" + bindingResult.getFieldError().getDefaultMessage());return "使用注解@Valid,检验参数时,出现以下错误:" + bindingResult.getFieldError().getDefaultMessage();}return "哈哈可以啊";}
}

实体类

package com.pkk.ssms1.entity;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;/*** @param* @author peikunkun* @version V1.0* @return* @Description: <用户实体类>* @date 2018/4/11 20:16*//*data相当于注解在类上,相当于同时使用了@ToString、@EqualsAndHashCode
、@Getter、@Setter和@RequiredArgsConstrutor这些注解,对于POJO类十分有用
@RequiredArgsConstructor: 会生成一个包含常量,和标识了NotNull的变量
的构造方法。生成的构造方法是private,如何想要对外提供使用可以使用staticName选项生成一个static方法。*/
@Data
@ApiModel(value = "User对象", description = "用户对象信息")
public class User implements BaseEntity {@ApiModelProperty(required = false, dataType = "Long", hidden = true)private Long   id;@ApiModelProperty(name = "username", example = "昆仔", dataType = "String", value = "用户名称")private String username;@ApiModelProperty(name = "password", example = "admin", dataType = "String", value = "用户密码")private String password;@ApiModelProperty(required = false, name = "deptid", hidden = true, example = "1", dataType = "Long", value = "部门id")private Long   deptid;@ApiModelProperty(name = "cardid", hidden = true, example = "1", dataType = "Long", value = "身份id")private Long   cardid;
}

文档常用的注解

- @Api 表示该类是一个 Swagger 的 Resource, 是对 Controller 进行注解的,表示标识这个类是swagger的资源(tags–表示说明 value–也是说明,可以使用tags替代 )
- @ApiOperation 表示对应一个 RESTful 接口, 对方法进行注解,表示一个http请求的操作(value用于方法描述 notes用于提示内容,tags可以重新分组(视情况而用) )
- @ApiResponse 表示对不同 HTTP 状态码的意义进行描述
- @ApiParam 表示对传入参数进行注解用于方法,参数,字段说明;表示对参数的添加元数据(说明或是否必填等)(name–参数名 value–参数说明 required–是否必填)
- @ApiModel()用于类 表示对类进行说明,用于参数用实体类接收(value–表示对象名description–描述 )
- @ApiModelProperty()用于方法,字段 表示对model属性的说明或者数据操作更改(value–字段说明 name–重写属性名字dataType–重写属性类型 required–是否必填 example–举例说明 hidden–隐藏)
- @ApiIgnore()用于类,方法,方法参数 表示这个方法或者类被忽略
- @ApiImplicitParam() 用于方法 表示单独的请求参数
- @ApiImplicitParams() 用于方法,包含多个 @ApiImplicitParam(name–参数名 value–参数说明dataType–数据类型 paramType–参数类型 example–举例说明)

传送门

注解说明传送门

springfox-swagger原理解析与使用过程中遇到的坑(原理分析的还是不错的)【精】

SpringMvc集成Springfox使用Swagger写文档和测试相关推荐

  1. SpringBoot集成knife4j实现Swagger接口文档

    前言:如果你是后台开发,提供restful接口给前端,建议你使用Swagger3提供restful的接口文档自动生成和在线接口调试.knife4j是对Swagger进一步封装,其优化了API文档的UI ...

  2. springboot 接口文档 请求 enum_Spring Boot集成SpringFox 3:生成Swagger接口文档

    SpringFox介绍 SpringFox是一个开源的API Doc的框架, 它的前身是swagger-springmvc,可以将我们的Controller中的方法以文档的形式展现.官方定义为:Aut ...

  3. swagger 扫描java文档_推荐一款在运行时通过javadoc生成Swagger API文档的库

    介绍 一般,我们使用Springfox生成swagger api文档,但Springfox不支持从javadoc中生成,只能通过注解的方式标注文档. 这样,当共享一些POJO类时,为了同时生成java ...

  4. Python Swagger 接口文档自动集成平台

    一 安装 当前virtualenv 环境下 pip install django-rest-swagger==2.2.0 在Django项目settings.py中 加入 插件模块 DEBUG = T ...

  5. swagger接口文档使用

    swagger接口文档 一,swagger简介 前后端分离 swagger 诞生 二,springboot集成swagger 依赖 编写helloworld接口 配置swagger ==> co ...

  6. SpringBoot集成knif4j创建在线API文档

    一直以来能够创建一个同项目一起发布的在线文档,曾经是很多程序员的梦想,偶然发现这个工具已经有了,测试之后发现还挺好用的,特地纪念. 这个工具就是knife4j,它是为Java MVC框架集成Swagg ...

  7. Spring Data REST API集成Springfox、Swagger

    原文: Documenting a Spring Data REST API with Springfox and Swagger 使用Spring Date REST,你可以迅速为Spring Da ...

  8. 整合Swagger接口文档

    Swagger接口文档:自动生成接口文档 1.添加依赖: <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagg ...

  9. 【愚公系列】2023年02月 WMS智能仓储系统-007.Swagger接口文档的配置

    文章目录 前言 一.Swagger接口文档的配置 1.安装包 2.注入 2.1 Swagger服务的注入 2.2 appsetting.json的配置 2.3 Swagger服务的封装 2.3.1 S ...

最新文章

  1. 机器视觉_Java机器学习,第2部分
  2. 数据分析系列:绘制散点图(matplotlib)
  3. ap的ht模式_AP6256 STA模式操作示例
  4. 使用 pg_dump 迁移 postgresql
  5. Asp.net如何截屏
  6. 50个数据可视化最有价值的图表(附完整Python代码,建议收藏
  7. 【转载保存】java牛逼的开源项目汇总
  8. ipv6路由协议配置_IPV6寻址,标头和路由协议
  9. maven的仓库、生命周期与插件
  10. Windows平台下使用Dokan实现文件系统的开发
  11. 超级详细备注的代码:Python帮助您高效通过英语六级考试
  12. 【java笔记】继承与多态
  13. 移除collection中元素的注意事项(应用collection.remove移除元素造成的错误)
  14. vagrant虚拟机网络设置
  15. java mybatis 代码生成器_mybatis自动生成java代码
  16. python合并单元格居中_Python实现Excel自动分组合并单元格
  17. 太牛X了! 呕心沥血整理的4000页Java学习手册文档!
  18. java 事务级别_java事务隔离级别
  19. 阅读:IA-GCN: Interactive Graph Convolutional Network forRecommendation
  20. warring:integer conversion resulted in a change of sign

热门文章

  1. 机器学习_决策树(信息熵,决策树,决策树优化,剪枝)
  2. Python3:使用函数计算-输入日期,计算该日期是当年中第多少天
  3. 小米股价持续下滑,雷军“老实厚道”的人设不保!
  4. laravel CSRF攻击
  5. 2022-07-26 微信服务商
  6. 为什么图层1剪切蒙版到图层2,图层1不见了?
  7. 《炬丰科技-半导体工艺》光学薄膜的湿刻蚀
  8. 四川大学计算机学云,四川大学网络空间安全学院陈兴蜀:云技术安全能力及运行监管...
  9. 准备分享一个用python写自动抢鞋的程序
  10. Winsoft Burn CD-DVD-SEO-狼术