swagger簡介
swagger確實是個好東西,能夠跟據業務代碼自動生成相關的api接口文檔,尤爲用於restful風格中的項目,開發人員幾乎能夠不用專門去維護rest api,這個框架能夠自動爲你的業務代碼生成restfut風格的api,並且還提供相應的測試界面,自動顯示json格式的響應。大大方便了後臺開發人員與前端的溝通與聯調成本。html
springfox-swagger簡介
籤於swagger的強大功能,Java開源界大牛spring框架迅速跟上,它充分利用自已的優點,把swagger集成到本身的項目裏,整了一個spring-swagger,後來便演變成springfox。springfox自己只是利用自身的aop的特色,經過plug的方式把swagger集成了進來,它自己對業務api的生成,仍是依靠swagger來實現。前端
關於這個框架的文檔,網上的資料比較少,大部分是入門級的簡單使用。本人在集成這個框架到本身項目的過程當中,遇到了很多坑,爲了解決這些坑,我不得不扒開它的源碼來看個究竟。此文,就是記述本人在使用springfox過程當中對springfox的一些理解以及須要注意的地方。java
springfox大體原理
springfox的大體原理就是,在項目啓動的過種中,spring上下文在初始化的過程,框架自動跟據配置加載一些swagger相關的bean到當前的上下文中,並自動掃描系統中可能須要生成api文檔那些類,並生成相應的信息緩存起來。若是項目MVC控制層用的是springMvc那麼會自動掃描全部Controller類,跟據這些Controller類中的方法生成相應的api文檔。程序員
因本人的項目就是SpringMvc,因此此文就以SpringMvc集成springfox爲例來討論springfox的使用與原理。web
SpringMvc集成springfox的步驟正則表達式
首先,項目須要加入如下三個依賴:spring
<!– 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包,我粗略看了一下,主要有如下這麼幾個:json
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能夠看出springfox除了依賴swagger以外,它還須要guava、spring-plug、jackson等依賴包(注意jackson是用於生成json必須的jar包,若是項目裏自己沒有加入這個依賴,爲了集成swagger的話必須額外再加入這個依賴)。api
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故名思義,就是啓用springmvc了,在Eclipse中點到這個註解裏面簡單看一下,它就是經過元註解@Import(DelegatingWebMvcConfiguration.class)往spring context中塞入了一個DelegatingWebMvcConfiguration類型的bean。我想,這個類的目的應該就是爲swagger提供了一些springmvc方面的配置吧。第三個註解:@EnableSwagger2,看名字應該能夠想到,是用來集成swagger2的,他經過元註解:@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.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註解,向spring 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(MenuVo menuVo, Model model){ }
這是大部分程序員喜歡在Controller中寫的修改某個實體的代碼。在跟swagger集成的時候,這裏有一個大坑。若是MenuVo這個類中全部的屬性都是基本類型,那還好,不會出什麼問題。但若是這個類裏面有一些其它的自定義類型的屬性,並且這個屬性又直接或間接的存在它自身類型的屬性,那就會出問題。例如:假如MenuVo這個類是菜單類,在這個類時又含有MenuVo類型的一個屬性parent表明它的父級菜單。這樣的話,系統啓動時swagger模塊就因沒法加載這個api而直接報錯。報錯的緣由就是,在加載這個方法的過程當中會解析這個update方法的參數,發現參數MenuVo不是簡單類型,則會自動以遞歸的方式解釋它全部的類屬性。這樣就很容易陷入無限遞歸的死循環。
爲了解決這個問題,我目前只是本身寫了一個OperationParameterReader插件實現類以及它依賴的ModelAttributeParameterExpander工具類,經過配置的方式替換掉到springfox原來的那兩個類,偷樑換柱般的把參數解析這個邏輯替換掉,並避開無限遞歸。固然,這至關因而一種修改源碼級別的方式。我目前尚未找到解決這個問題的更完美的方法,因此,只能建議你們在用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等等註解的用法,網上不少這方面的文檔,我就不重複了。