swagger简介

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

springfox-swagger简介

签于swagger的强大功能,java开源界大牛spring框架迅速跟上,它充分利用自已的优势,把swagger集成到自己的项目里,整了一个spring-swagger,后来便演变成springfox。springfox本身只是利用自身的aop的特点,通过plug的方式把swagger集成了进来,它本身对业务api的生成,还是依靠swagger来实现。

关于这个框架的文档,网上的资料比较少,大部分是入门级的简单使用。本人在集成这个框架到自己项目的过程中,遇到了不少坑,为了解决这些坑,我不得不扒开它的源码来看个究竟。此文,就是记述本人在使用springfox过程中对springfox的一些理解以及需要注意的地方。

springfox大致原理

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

因本人的项目就是SpringMvc,所以此文就以Srping
mvc集成springfox为例来讨论springfox的使用与原理。

SpringMvc集成springfox的步骤

首先,项目需要加入以下三个依赖:

<!-- sring mvc依赖
-->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-webmvc</artifactId>

<version>4.2.8.RELEASE</version>

</dependency>

<!-- swagger2核心依赖
-->

<dependency>

<groupId>io.springfox</groupId>

<artifactId>springfox-swagger2</artifactId>

<version>2.6.1</version>

</dependency>

<!-- swagger-ui为项目提供api展示及测试的界面
-->

<dependency>

<groupId>io.springfox</groupId>

<artifactId>springfox-swagger-ui</artifactId>

<version>2.6.1</version>

</dependency>

上面三个依赖是项目集成springmvc及springfox最基本的依赖,其它的依赖这里省略。其中第一个是springmvc的基本依赖,第二个是swagger依赖,第三个是界面相关的依赖,这个不是必须的,如果你不想用springfox自带的api界面的话,也可以不用这个,而另外自己写一套适合自己项目的界面。加入这几个依赖后,系统后会自动加入一些跟springfox及swagger相关jar包,我粗略看了一下,主要有以下这么几个:

springfox-swagger2-2.6.1.jar

swagger-annotations-1.5.10.jar

swagger-models-1.5.10.jar

springfox-spi-2.6.1.jar

springfox-core-2.6.1.jar

springfox-schema-2.6.1.jar

springfox-swagger-common-2.6.1.jar

springfox-spring-web-2.6.1.jar

guava-17.0.jar

spring-plugin-core-1.2.0.RELEASE.jar

spring-plug-metadata-1.2.0.RELEASE.jar

spring-swagger-ui-2.6.1.jar

jackson-databind-2.2.3.jar

jackson-annotations-2.2.3.jar

上面是我通过目测觉得springfox可能需要的jar,可能没有完全例出springfox所需的所有jar。从上面
jar可以看出pringfox除了依赖swagger之外,它还需要guava、spring-plug、jackson等依赖包(注意jackson是用于生成json必须的jar包,如果项目里本身没有加入这个依赖,为了集成swagger的话必须额外再加入这个依赖)。

springfox的简单使用

如果只用springfox的默认的配置的话,与springmvc集成起来非常简单,只要写一个类似于以下代码的类放到你的项目里就行了,代码如下:

@Configuration

@EnableWebMvc

@EnableSwagger2

publicclass
ApiConfig {

}

注意到,上面是一个空的java类文件,类名可以随意指定,但必须加入上述类中标出的@Configuration、@EnableWebMvc、@EnableSwagger2三个注解,这样就完成了springmvc与springfox的基本集成,有了三个注解,项目启动后就可以直接用类似于以下的地址来查看api列表了:

http://127.0.0.1:8080/jadDemo/swagger-ui.html

这确实是一个很神奇的效果,简单的三个注解,系统就自动显示出项目里所有Controller类的所有api了。现在,我们就这个配置类入手,简单分析它的原理。这个类中没有任何代码,很显然,三个注解起了至关重要的作用。其中@Configuration注解是spring框架中本身就有的,它是一个被@Component元注解标识的注解,所以有了这个注解后,spring会自动把这个类实例化成一个bean注册到spring上下文中。第二个注解@EnableWebMvc故名思义,就是启用srpingmvc了,在Eclipse中点到这个注解里面简单看一下,它就是通过元注解@Import(DelegatingWebMvcConfiguration.class)往spring
context中塞入了一个DelegatingWebMvcConfiguration类型的bean。我想,这个类的目的应该就是为swagger提供了一些springmvc方面的配置吧。第三个注解:@EnableSwagger2,看名字应该可以想到,是用来集成swagger
2的,他通过元注解:@Import({Swagger2DocumentationConfiguration.class}),又引入了一个Swagger2DocumentationConfiguration类型的配置bean,而这个就是Swagger的核心配置了。它里面的代码如下:

@Configuration

@Import({ SpringfoxWebMvcConfiguration.class,
SwaggerCommonConfiguration.class
})

@ComponentScan(basePackages = {

"springfox.documentation.swagger2.readers.parameter",

"springfox.documentation.swagger2.web",

"springfox.documentation.swagger2.mappers"

})

publicclassSwagger2DocumentationConfiguration
{

@Bean

public JacksonModuleRegistrar
swagger2Module() {

returnnewSwagger2JacksonModule();

}

}

这个类头部通过一些注解,再引入SpringfoxWebMvcConfiguration类和SwaggerCommonConfiguration类,并通过ComponentScan注解,自动扫描springfox
.swagger2相关的的bean到spring context中。这里,我最感兴趣的是SpringfoxWebMvcConfiguration这个类,这个类我猜应该就是springfox集成mvc比较核心的配置了,点进去,看到以下代码:

@Configuration

@Import({ModelsConfiguration.class
})

@ComponentScan(basePackages = {

"springfox.documentation.spring.web.scanners",

"springfox.documentation.spring.web.readers.operation","springfox.documentation.spring.web.readers.parameter","springfox.documentation.spring.web.plugins","springfox.documentation.spring.web.paths"

})

@EnablePluginRegistries({ DocumentationPlugin.class,

ApiListingBuilderPlugin.class,

OperationBuilderPlugin.class,

ParameterBuilderPlugin.class,

ExpandedParameterBuilderPlugin.class,

ResourceGroupingStrategy.class,

OperationModelsProviderPlugin.class,

DefaultsProviderPlugin.class,

PathDecorator.class

})

publicclassSpringfoxWebMvcConfiguration
{

}

这个类中下面的代码,无非就是通过@Bean注解再加入一些新的Bean,我对它的兴趣不是很大,我最感兴趣的是头部通过@EnablePluginRegistries加入的那些东西。springfox是基于spring-plug的机制整合swagger的,spring-plug具体是怎么实现的,我暂时还没有时间去研究spring-plug的原理。但在下文会提到自己写一个plug插件来扩展swagger的功能。上面通过@EnablePluginRegistries加入的plug中,还没有时间去看它全部的代码,目前我看过的代码主要有ApiListingBuilderPlugin.class,
OperationBuilderPlugin.class,ParameterBuilderPlugin.class,
ExpandedParameterBuilderPlugin.class,

第一个ApiListingBuilderPlugin,它有两个实现类,分别是ApiListingReader和SwaggerApiListingReader。其中ApiListingReader会自动跟据Controller类型生成api列表,而SwaggerApiListingReader会跟据有@Api注解标识的类生成api列表。OperationBuilderPlugin插件就是用来生成具体api文档的,这个类型的插件,有很多很多实现类,他们各自分工,各做各的事情,具体我没有仔细去看,只关注了其中一个实现类:OperationParameterReader,这个类是用于读取api参数的Plugin。它依赖于ModelAttributeParameterExpander工具类,可以将Controller中接口方法参数中非简单类型的命令对像自动解析它内部的属性得出包含所有属性的参数列表(这里存在一个可能会出现无限递归的坑,下文有介绍)。而ExpandedParameterBuilderPlugin插件,主要是用于扩展接口参数的一些功能,比如判断这个参数的数据类型以及是否为这个接口的必须参数等等。总体上说,整个springfox-swagger内部其实是由这一系列的plug转运起来的。他们在系统启动时,就被调起来,有些用来扫描出接口列表,有些用来读取接口参数等等。他们共同的目地就是把系统中所有api接口都扫描出来,并缓存起来供用户查看。那么,这一系列表plug到底是如何被调起来的,它们的执行入口倒底在哪?

我们把注意点放到上文SpringfoxWebMvcConfiguration这个类代码头部的ComponentScan注解内容上来,这一段注解中扫描了一个叫springfox.documentation.spring.web.plugins的package,这个package在springfox-spring-web-2.6.1.jar中可以找到。这个package下,我们发现有两个非常核心的类,那就是DocumentationPluginsManager和DocumentationPluginsBootstrapper。对于第一个DocumentationPluginsManager,它是一个没有实现任何接口的bean,但它内部有诸多PluginRegistry类型的属性,而且都是通过@Autowired注解把属性值注入进来的。接合它的类名来看,很容易想到,这个就是管理所有plug的一个管理器了。很好理解,因为ComponentScan注解的配置,所有的plug实例都会被spring实例化成一个bean,然后被注入到这个DocumentationPluginsManager实例中被统一管理起来。在这个package中的另一个重要的类DocumentationPluginsBootstrapper,看名字就可以猜到,他可能就是plug的启动类了。点进去看具体时就可以发现,他果然是一个被@Component标识了的组件,而且它的构造方法中注入了刚刚描述的DocumentationPluginsManager实例,而且最关键的,它还实现了SmartLifecycle接口。对spring
bean生命周期有所了解的人的都知道,这个组件在被实例化为一个bean纳入srping
context中被管理起来的时候,会自动调用它的start()方法。点到start()中看代码时就会发现,它有一行代码scanDocumentation(buildContext(each));就是用来扫描api文档的。进一步跟踪这个方法的代码,就可以发现,这个方法最终会通过它的DocumentationPluginsManager属性把所有plug调起一起扫描整个系统并生成api文档。扫描的结果,缓存在DocumentationCache这个类的一个map属性中。

以上就是,srpingMvc整合springfox的大致原理。它主要是通过EnableSwagger2注解,向srping
context注入了一系列bean,并在系统启动的时候自动扫描系统的Controller类,生成相应的api信息并缓存起来。此外,它还注入了一些被@Controller注解标识的Controller类,作为ui模块访问api列表的入口。比如springfox-swagger2-2.6.1.jar包中的Swagger2Controller类。这个Controller就是ui模块中用来访问api列表的界面地址。在访问http://127.0.0.1:8080/jadDemo/swagger-ui.html这个地址查看api列表时,通过浏览器抓包就可以看到,它是通过类似于http://127.0.0.1:8080/jadDemo/v2/api-docs?group=sysGroup这样的地址异步获得api信息(Json格式)并显示到界面上,这个地址后台对应的Controller入口就是上文的Swagger2Controller类,这个类收到请求后,直接从事先初始化好的缓存中的取出api信息生成json字符串返回。

了解了springfox的原理,下面来看看springfox使用过程中,我遇到的哪些坑。

springfox第一大坑:配置类生成的bean必须与spring
mvc共用同一个上下文。

前文描述了,在springmvc项目中,集成springfox是只要在项目写一个如下的没有任何业务代码的简单配置类就可以了。

@Configuration

@EnableWebMvc

@EnableSwagger2

publicclass
ApiConfig {

}

因为@Configuration注解的作用,spring会自动把它实例化成一个bean注入到上下文。但切记要注意的一个坑就是:这个bean所在的上下文必须跟spring
mvc为同一个上下文。怎么解理呢?因为在实际的spring mvc项目中,通常有两个上下文,一个是跟上下文,另一个是spring mvc(它是跟上下文的子上下文)。其中跟上下文是就是web.xml文件中跟spring相关的那个org.springframework.web.context.request.RequestContextListener监听器,加载起来的上下文,通常我们会写一个叫spring-contet.xml的配置文件,这里面的bean最终会初始化到跟上下文中,它主要包括系统里面的service,dao等bean,也包括数据源、事物等等。而另一个上下文是就是spring
mvc了,它通过web.xml中跟spring mvc相关的那个org.springframework.web.servlet.DispatcherServlet加载起来,他通常有一个配置文件叫spring-mvc.xml。我们在写ApiConfig这个类时,如果决定用@Configuration注解来加载,那么就必须保证这个类所在的路径刚好在springmvc的component-scan的配置的base-package范围内。因为在ApiConfig在被spring加载时,会注入一列系列的bean,而这些bean中,为了能自动扫描出所有Controller类,有些bean需要依赖于SpringMvc中的一些bean,如果项目把Srpingmvc的上下文与跟上下文分开来,作为跟上下文的子上下文的话。如果不小心让这个ApiConfig类型的bean被跟上文加载到,因为root
context中没有spring mvc的context中的那些配置类时就会报错。

实事上,我并不赞成通过@Configuration注解来配置Swagger,因为我认为,Swagger的api功能对于生产项目来说是可有可无的。我们Swagger往往是用于测试环境供项目前端团队开发或供别的系统作接口集成使上。系统上线后,很可能在生产系统上隐藏这些api列表。
但如果配置是通过@Configuration注解写死在java代码里的话,那么上线的时候想去掉这个功能的时候,那就尴尬了,不得不修改java代码重新编译。基于此,我推荐的一个方法,通过spring最传统的xml文件配置方式。具体做法就是去掉@Configuration注解,然后它写一个类似于<bean
class="com.jad.web.mvc.swagger.conf.ApiConfig"/>这样的bean配置到spring的xml配置文件中。在root
context与mvc的context分开的项目中,直接配置到spring-mvc.xml中,这样就保证了它跟springmvc
的context一定处于同一个context中。

springfox第二大坑: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参数的命令对像中最好只含有简单的基本类型属性。

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

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

@EnableWebMvc

@EnableSwagger2

publicclass
ApiConfig {

@Bean

public Docket customDocket() {

return newDocket(DocumentationType.SWAGGER_2).apiInfo(apiInfo());

}

}

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

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

@EnableWebMvc

@EnableSwagger2

publicclass
ApiConfig {

@Bean

public Docket customDocket1() {

return newDocket(DocumentationType.SWAGGER_2)

.groupName("apiGroup1").apiInfo(apiInfo()).select()

.paths(PathSelectors.ant("/sys/**"));

}

@Bean

public Docket customDocket2() {

return newDocket(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等等注解的用法,网上很多这方面的文档,我就不重复了。

更多信息,大家可以关注我的原创公众号

本文链接:https://blog.csdn.net/w4hechuan2009/article/details/68892718

springfox-swagger原理解析与使用过程中遇到的坑的更多相关文章

  1. 爬取CVPR 2018过程中遇到的坑

    爬取 CVPR 2018 过程中遇到的坑 使用语言及模块 语言: Python 3.6.6 模块: re requests lxml bs4 过程 一开始都挺顺利的,先获取到所有文章的链接再逐个爬取获 ...

  2. VS2017 + EF + MySQL 我使用过程中遇到的坑

    原文:VS2017 + EF + MySQL 我使用过程中遇到的坑 写在前面: 第一次使用MySQL连接VS的时候本着最新版的应该就是最好的,在MySQL官网下载了最新版的MySQL没有并且安装完成之 ...

  3. MySql数据库GROUP BY使用过程中的那些坑

    MySql数据库GROUP BY使用过程中的那些坑 GROUP BY 语句用于结合合计函数,根据一个或多个列对结果集进行分组. 特别注意: group by 有一个原则,就是 select 后面的所有 ...

  4. 【Angular JS】网站使用社会化评论插件,以及过程中碰到的坑

    目前正在开发自己的网站,技术上使用Angular JS + Express JS + Mongo DB.由于网站会有文章发布,因此需要有评论功能.评论功能也可以自己开发,但由于现在社会化评论插件很多, ...

  5. [.NET跨平台]Jeuxs独立版本的便利与过程中的一些坑

    本文环境与前言 之前写过一篇相关的文章:在.NET Core之前,实现.Net跨平台之Mono+CentOS+Jexus初体验 当时的部署还是比较繁琐的,而且需要联网下载各种东西..有兴趣的可以看看, ...

  6. [.NET跨平台]Jexus独立版本的便利与过程中的一些坑

    本文环境与前言 之前写过一篇相关的文章:在.NET Core之前,实现.Net跨平台之Mono+CentOS+Jexus初体验 当时的部署还是比较繁琐的,而且需要联网下载各种东西..有兴趣的可以看看, ...

  7. HUE安装过程中的一些坑

    1. gcc: error: krb5-config:: No such file or directory 执行安装krb5-devel yum provides krb5-config 得到提示: ...

  8. 学习Nodejs:《Node.js开发指南》微博项目express2迁移至express4过程中填的坑

    <Node.js开发指南>项目地址https://github.com/BYVoid/microblog好不容易找到的基础版教程,但书中是基于express2的,而现在用的是express ...

  9. 全屏使用swiper.js过程中遇到的坑

    概述 swiper.js确实是一个很好用的插件,下面记录下我在全屏使用过程中遇到的一些坑和解决办法,供以后开发时参考,相信对其他人也有用. 通用方案 一般来说,swiper需要放在body的下一层,虽 ...

随机推荐

  1. raid 10

    首先先创建五个新的硬盘,步骤参照  raid 5 建好硬盘以后开启虚拟机 打开终端,更改好主机名以后,重新打开终端 输入命令:fdisk  -l 查看有没有加入进来  我们发现已经加入进来 然后开始分 ...

  2. Scrapy框架: 第一个程序

    首先创建项目: scrappy start project maitian 第二步: 明确要抓取的字段items.py import scrapy class MaitianItem(scrapy.I ...

  3. python基础之数据类型初始

    变量 贴标签 变量名规则:只能是字母下划线和数字组成,不能以数字开头,不能和关键字同名,不能使用拼音和中文,具有可描述性,区别大小写,变量名推荐写法:驼峰提,下划线(官方推荐) 变量赋值 常量 特性: ...

  4. 算法竞赛模板 KMP

    KMP算法图解: ① 首先,字符串“BBC ABCDAB ABCDABCDABDE”的第一个字符与搜索词“ABCDABD”的第一个字符,进行比较.因为B与A不匹配,所以搜索词后移一位. ② 因为B与A ...

  5. Ubuntu14.04.1LTS不能上网解决方法之一

    我们在装好了Ubuntu之后,打开浏览器,显示Server not found. 1.点击网络图标,显示如下: 2.进入etc/NetworkManager,找到 Networkmanager.con ...

  6. KiCAD原理图导出PDF方法

    KiCAD原理图导出为PDF 1.文件->绘制 2.按照下图选择保存目录和输出格式后,选择绘制当前页或者所有页

  7. sqlserver 将店铺表转换成可以用in查询的字符串

    create TABLE #tempshopt ( shopid varchar(max) ) set @aSql = 'insert into #tempshopt(shopid) select s ...

  8. leetcode-13双周赛-1256-加密数字

    题目描述:编码 方法一: class Solution(object): def encode(self, n): if n == 0: return "" n -= 1 A = ...

  9. React笔记03——React实现TodoList

    1 什么是JSX语法? 原生JS中,要向页面中挂载html标签,标签一定是被引号''包起来的:document.getElementById('root').append('<div>he ...

  10. Dart编程实例 - 相等和关系操作符

    Dart编程实例 - 相等和关系操作符 void main() { var num1 = 5; var num2 = 9; var res = num1>num2; print('num1 gr ...