您好,登錄后才能下訂單哦!
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等等注解的用法,網上很多這方面的文檔,我就不重復了。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。