浅谈springfox-swagger原理解析与使⽤过程中遇到的坑
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 {}
这确实是⼀个很神奇的效果,简单的三个注解,系统就⾃动显⽰出项⽬⾥所有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.aders.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.aders.operation","springfox.documentation.aders.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 {}
springboot aop这个类中下⾯的代码,⽆⾮就是通过@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⽂档的,这个类型的插件,有很多很多
实现类,他们各⾃分⼯,各做各的事情,具体我没有仔细去看,只关注了其中⼀个实现类:Operation
ParameterReader,这个类是⽤于读取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。对于第⼀个DocumentationPl
uginsManager,它是⼀个没有实现任何接⼝的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属性中。
了解了springfox的原理,下⾯来看看springfox使⽤过程中,我遇到的哪些坑。
springfox第⼀⼤坑:配置类⽣成的bean必须与spring mvc共⽤同⼀个上下⽂。
前⽂描述了,在springmvc项⽬中,集成springfox是只要在项⽬写⼀个如下的没有任何业务代码的简单配置类就可以了。
@Configuration
@EnableWebMvc
@EnableSwagger2
publicclass ApiConfig {
}
因为@Configuration注解的作⽤,spring会⾃动把它实例化成⼀个bean注⼊到上下⽂。但切记要注意的⼀个坑就是:这个bean所在的上下⽂必须跟spring mvc为同⼀个上下⽂。怎么解理呢?因为在实际的spring mvc项⽬中,通常有两个上下⽂,⼀个是跟上下⽂,另⼀个是spring mvc(它是跟上下⽂的⼦上下⽂)。其中跟上下⽂是就是l⽂件中跟spring相关的那个org.t.request.RequestContextListener,加载起来的上下⽂,通常我们会写⼀个叫l的配置⽂件,这⾥⾯的bean最终会初始化到跟上下⽂中,它主要包括系统⾥⾯的service,dao等bean,也包括数据源、事物等等。⽽另⼀个上下⽂是就是spring mvc了,
它通过l中跟spring mvc相关的那个org.springframework.web.servlet.DispatcherServlet加载起来,他通常有⼀个配置⽂件叫l。我们在写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.f.ApiConfig"/>这样的bean配置到spring的xml配置⽂件中。在root context与mvc的context分开的项⽬中,直接配置到l中,这样就保证了它跟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实例不能延迟加载
@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等等注解的⽤法,⽹上很多这⽅⾯的⽂档,我就不重复了。
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论