Swagger天天用,但它背后的實(shí)現(xiàn)原理很多人都不知道!

先說(shuō)一說(shuō)Springfox和Swagger的關(guān)系
Swagger 是一種規(guī)范。
springfox-swagger 是基于 Spring 生態(tài)系統(tǒng)的該規(guī)范的實(shí)現(xiàn)。
springfox-swagger-ui 是對(duì) swagger-ui 的封裝,使得其可以使用 Spring 的服務(wù)。
由于工作中遇到需要基于 Swagger Json 做一些處理,但 Swagger Json 的格式不是那么滿(mǎn)足需求。
本文springfox-swagger版本號(hào):
2.6.0
本文從問(wèn)題出發(fā),探索涉及的源碼。
1. GET 方法的參數(shù)對(duì)象
第一個(gè)問(wèn)題,當(dāng)方法是GET請(qǐng)求,但參數(shù)是一個(gè)自定義 Object,在展示時(shí)(生成的JSON)是不包括本 Object 描述的。所以,就要看看什么時(shí)候會(huì)生成這些 Model 的描述。
萬(wàn)事有始有終,SpringFox始就在:springfox.documentation.spring.web.plugins下的?DocumentationPluginsBootstrapper。
該類(lèi)實(shí)現(xiàn)了 SmartLifecycle 接口,實(shí)現(xiàn)此接口且通過(guò)@Component注入到容器的bean, 容器初始化后會(huì)執(zhí)行start()方法.
@Component
public?class?DocumentationPluginsBootstrapper?implements?SmartLifecycle?{
接著看 start 方法
@Override
public?void?start()?{
????if?(initialized.compareAndSet(false,?true))?{
????????//?拿到?DocumentationPlugin?插件
????????List?plugins?=?pluginOrdering()
????????????.sortedCopy(documentationPluginsManager.documentationPlugins());
????????for?(DocumentationPlugin?each?:?plugins)?{
????????????//獲取文檔類(lèi)型
????????????DocumentationType?documentationType?=?each.getDocumentationType();
????????????if?(each.isEnabled())?{
????????????????//?啟用則掃描生成文檔
????????????????scanDocumentation(buildContext(each));
????????????}?
????????}
????}
}
調(diào)用了?buildContext?方法, 通過(guò) Docket 對(duì)象創(chuàng)建 DocumentaionContext 對(duì)象
private?DocumentationContext?buildContext(DocumentationPlugin?each)?{
????return?each.configure(this.defaultContextBuilder(each));
}
再往下走
private?DocumentationContextBuilder?defaultContextBuilder(DocumentationPlugin?each)?{
????DocumentationType?documentationType?=?each.getDocumentationType();
????//?獲取所有的RequestHnadler
????List?requestHandlers?=?FluentIterable.from(this.handlerProviders).transformAndConcat(this.handlers()).toList();
????return?this.documentationPluginsManager.createContextBuilder(documentationType,?this.defaultConfiguration).requestHandlers(requestHandlers);
}
handlerProviders?是?RequestHandlerProvider?接口,實(shí)現(xiàn)類(lèi)是?WebMvcRequestHandlerProvider,其中?requestHandlers?方法會(huì)接收Spring中的所有請(qǐng)求映射。
接著看?DocumentationContextBuilder的構(gòu)造過(guò)程:documentationPluginsManager.createContextBuilder
public?DocumentationContextBuilder?createContextBuilder(DocumentationType?documentationType,
????????????????????????????????????????????????????????DefaultConfiguration?defaultConfiguration)?{
??return?defaultsProviders.getPluginFor(documentationType,?defaultConfiguration)
??????.create(documentationType)
??????.withResourceGroupingStrategy(resourceGroupingStrategy(documentationType));
}
defaultsProviders?是也是一個(gè)插件接口?DefaultsProviderPlugin,只有一個(gè)實(shí)現(xiàn)類(lèi)DefaultConfiguration,不過(guò)該類(lèi)未使用@Compoent注解,所以需要給一個(gè)替換值defaultConfiguration,也就是DefaultConfiguration。在看DefaultConfiguration的create方法:
@Override
public?DocumentationContextBuilder?create(DocumentationType?documentationType)?{
??return?new?DocumentationContextBuilder(documentationType)
??????????.operationOrdering(defaults.operationOrdering())
??????????.apiDescriptionOrdering(defaults.apiDescriptionOrdering())
??????????.apiListingReferenceOrdering(defaults.apiListingReferenceOrdering())
??????????.additionalIgnorableTypes(defaults.defaultIgnorableParameterTypes())
??????????.rules(defaults.defaultRules(typeResolver))
??????????.defaultResponseMessages(defaults.defaultResponseMessages())
??????????.pathProvider(new?RelativePathProvider(servletContext))
??????????.typeResolver(typeResolver)
??????????.enableUrlTemplating(false)
??????????.selector(ApiSelector.DEFAULT);
}
這里在給DocumentationContextBuilder設(shè)置相關(guān)參數(shù),至此拿到了?DocumentationContextBuilder
回到上面提到的buildContext方法,defaultContextBuilder方法執(zhí)行完畢,接下來(lái)是?configure
return?each.configure(this.defaultContextBuilder(each));
DocumentationPlugin只有一個(gè)實(shí)現(xiàn)類(lèi)Docket,到這里就有點(diǎn)熟悉了。Docket對(duì)象是我們開(kāi)發(fā)人員在外部通過(guò)@Bean來(lái)創(chuàng)建的,而外部賦值的對(duì)象值,最終都會(huì)整合到DocumentationContext。這里的config就是在二次賦值。可以看一下一般自己定義的Docket對(duì)象。
public?class?SwaggerConfig?{
????...
????@Bean
????public?Docket?docket()?{
????????...
????????return?new?Docket(DocumentationType.SWAGGER_2)
????????????????.groupName(SWAGGER_GROUP)
????????????????.apiInfo(new?ApiInfoBuilder().title("xx").version("1.0.0").build())
????????????????......
????????????????.select()
????????????????.apis(basePackage("xxx"))
????????????????.paths(PathSelectors.any())
????????????????.build();
????}
}
到這里實(shí)際只設(shè)置了默認(rèn)的參數(shù)。但接口,定義,模型等關(guān)鍵信息等都未初始化。
回到最初start(), 看看scanDocumentation(buildContext(each))的scanDocumentation
private?void?scanDocumentation(DocumentationContext?context)?{
??scanned.addDocumentation(resourceListing.scan(context));
}
其中?scan?位于?ApiDocumentationScanner類(lèi)
public?Documentation?scan(DocumentationContext?context)?{
??ApiListingReferenceScanResult?result?=?apiListingReferenceScanner.scan(context);
??...
??Multimap?apiListings?=?apiListingScanner.scan(listingContext);
??...
apiListingReferenceScanner.scan位于?ApiListingReferenceScanner類(lèi)
public?ApiListingReferenceScanResult?scan(DocumentationContext?context)?{
??...
??//?接口選擇器?在構(gòu)建Docket時(shí)通過(guò).select()默認(rèn)配置?
??ApiSelector?selector?=?context.getApiSelector();
??//?根據(jù)package路徑(一般)或注解區(qū)分,?過(guò)濾篩選掉不符規(guī)則的?RequestHandler?接口
??Iterable?matchingHandlers?=?from(context.getRequestHandlers())
??????.filter(selector.getRequestHandlerSelector());
??for?(RequestHandler?handler?:?matchingHandlers)?{
????//?接口分組?resourceGroup?=?Controller,RequestMapping?=?method
????ResourceGroup?resourceGroup?=?new?ResourceGroup(handler.groupName(),
????????handler.declaringClass(),?0);
????RequestMappingContext?requestMappingContext
????????=?new?RequestMappingContext(context,?handler);
????resourceGroupRequestMappings.put(resourceGroup,?requestMappingContext);
??}
??return?new?ApiListingReferenceScanResult(asMap(resourceGroupRequestMappings));
}
到這已經(jīng)拿到了所有接口并進(jìn)行了分組,其中ArrayListMultimap是guava的方法。
再回到?ApiDocumentationScanner的?scan方法,看?apiListingScanner.scan
public?Multimap?scan(ApiListingScanningContext?context)? {
??...
??for?(ResourceGroup?resourceGroup?:?sortedByName(requestMappingsByResourceGroup.keySet()))?{
????...
????for?(RequestMappingContext?each?:?sortedByMethods(requestMappingsByResourceGroup.get(resourceGroup)))?{
??????//?循環(huán)Controller下的所有接口的實(shí)例對(duì)象,?拿到該接口的所有Model
??????models.putAll(apiModelReader.read(each.withKnownModels(models)));
??????apiDescriptions.addAll(apiDescriptionReader.read(each));
????}
each.withKnownModels?是復(fù)制對(duì)象,主要看apiModelReader.read,讀取該接口的 Model 信息。
public?Map?read(RequestMappingContext?context)? {
?//?忽略的class
??Set?ignorableTypes?=?newHashSet(context.getIgnorableParameterTypes());
??Set?modelContexts?=?pluginsManager.modelContexts(context);
??Map?modelMap?=?newHashMap(context.getModelMap());
??for?(ModelContext?each?:?modelContexts)?{
????markIgnorablesAsHasSeen(typeResolver,?ignorableTypes,?each);
????Optional?pModel?=?modelProvider.modelFor(each);
????if?(pModel.isPresent())?{
??????mergeModelMap(modelMap,?pModel.get());
????}?else?{
????}
????populateDependencies(each,?modelMap);
??}
??return?modelMap;
}
就是從?modelContexts轉(zhuǎn)化為?Model,看看pluginsManager.modelContexts,怎么取modelContexts
public?Set?modelContexts(RequestMappingContext?context)? {
??DocumentationType?documentationType?=?context.getDocumentationContext().getDocumentationType();
??//?構(gòu)建接口的ModelContext集合
??for?(OperationModelsProviderPlugin?each?:?operationModelsProviders.getPluginsFor(documentationType))?{
????each.apply(context);
??}
??return?context.operationModelsBuilder().build();
}
OperationModelsProviderPlugin有兩個(gè)實(shí)現(xiàn)類(lèi),通過(guò)文檔類(lèi)型來(lái)獲取。
OperationModelsProviderPlugin:處理返回類(lèi)型,參數(shù)類(lèi)型等
SwaggerOperationModelsProvider:swagger注解提供的值類(lèi)型,
@ApiResponse,@ApiOperation等
先看OperationModelsProviderPlugin
@Override
public?void?apply(RequestMappingContext?context)?{
??//?收集返回類(lèi)型
??collectFromReturnType(context);
??//?收集參數(shù)類(lèi)型
??collectParameters(context);
??//?收集接口型號(hào)
??collectGlobalModels(context);
}
到了這,本問(wèn)題( GET 方法的請(qǐng)求Object不描述)的答案就要呼之欲出了。來(lái)看?collectParameters
private?void?collectParameters(RequestMappingContext?context)?{
??//?獲取所有類(lèi)型
??List?parameterTypes?=?context.getParameters();
??for?(ResolvedMethodParameter?parameterType?:?parameterTypes)?{
????//?過(guò)濾??
????if?(parameterType.hasParameterAnnotation(RequestBody.class)
??????????||?parameterType.hasParameterAnnotation(RequestPart.class))?{
????????ResolvedType?modelType?=?context.alternateFor(parameterType.getParameterType());
????????context.operationModelsBuilder().addInputParam(modelType);
??????}
??}
}
破案了,可以看到過(guò)濾時(shí)只會(huì)處理兩種:通過(guò)@RequestBody和@ReuqestPart注解標(biāo)注的, 而GET方法的參數(shù)是不可以使用這兩個(gè)注解的。(當(dāng)然從規(guī)范來(lái)說(shuō),GET方法也不應(yīng)該這種參數(shù))。
至于OperationModelsProviderPlugin的另一個(gè)實(shí)現(xiàn)類(lèi)SwaggerOperationModelsProvider主要是收集使用@ApiOperation時(shí)主句屬性值和@ApiResponse響應(yīng)狀態(tài)碼涉及到的型號(hào),不再詳細(xì)列出。
而apiModelReader.read中的?modelContexts轉(zhuǎn)化為?Model的modelProvider.modelFor()是通過(guò)ModelProvider實(shí)現(xiàn),下一個(gè)問(wèn)題會(huì)詳細(xì)闡述。
那么,如何解決這個(gè)問(wèn)題:
1.使用?Docket的additionalModels方法,在配置類(lèi)中注入?TypeResolver
return?new?Docket(DocumentationType.SWAGGER_2)
.additionalModels(typeResolver.resolve(xxx))
...
2.借助第三方類(lèi)庫(kù) 如swagger-bootstrap-ui的工具類(lèi)(我沒(méi)接,但可以..)
3.重寫(xiě)
重寫(xiě)OperationModelsProviderPlugin的apply方法,添加自定義收集器。或者直接重寫(xiě)?collectParameters也行。比如
private?void?collectGetParameters(RequestMappingContext?context)?{
???????...
???????for?(ResolvedMethodParameter?parameterType?:?parameterTypes)?{
???????????//?不存在@RequestBody注解
???????????if?(!parameterType.hasParameterAnnotation(RequestBody.class)...)?{
???????????...
???????????????if?(xxx)?{
???????????????????ResolvedType?modelType?=?context.alternateFor(parameterType.getParameterType());
???????????????????context.operationModelsBuilder().addInputParam(modelType);
???????????????}
???????????}?...
???????}}
問(wèn)題解決。
2. Enum的描述格式
問(wèn)題是對(duì)于枚舉類(lèi),在生成的JSON文件中描述是在原參數(shù)對(duì)象中的如下格式:
???"xxx":?{...}
???"periodUnit":{
??????"type":"string",
??????"enum":[
???????????????"MINUTE",
???????????????"HOUR"
???????????????...
?????????]}
一般枚舉使用會(huì)如MINUTE(1,“分鐘”),也就是包括了code和name描述。
但實(shí)際enum的值會(huì)是二者之一。且不會(huì)生成如下的可重用的外部引用。
"schema":{
??????????"$ref":"#/definitions/xxxForm"
}
注意:可重用的問(wèn)題在3.0+可以通過(guò)配置處理。
如果需要強(qiáng)制將enum的值設(shè)為code或name,或拓展更多的內(nèi)容,就需要來(lái)看看,enum類(lèi)何時(shí)會(huì)被處理。
上一個(gè)問(wèn)題的結(jié)尾說(shuō)到apiModelReader.read,?modelContexts轉(zhuǎn)化為?Model的modelProvider.modelFor()方法是通過(guò)ModelProvider實(shí)現(xiàn),其實(shí) ModelProvider`是接口,有兩個(gè)實(shí)現(xiàn)類(lèi):
DefaultModelProvider:默認(rèn),每次都會(huì)將modelContext轉(zhuǎn)換為model
CachingModelProvider:聲明了guava緩存池,先從緩存池取,沒(méi)有則調(diào)用初始化處理器,轉(zhuǎn)換為模型,再放入緩存池。
在ApiModelReader的構(gòu)造方法里指定了使用CachingModelProvider,不過(guò)第一次調(diào)用緩存里是沒(méi)有的,所以往下走到populateDependencies
private?void?populateDependencies(ModelContext?modelContext,?Map?modelMap) ?{
??Map?dependencies?=?modelProvider.dependencies(modelContext);
??for?(Model?each?:?dependencies.values())?{
????mergeModelMap(modelMap,?each);
??}
}
CachingModelProvider的dependencies依賴(lài)的是DefaultModelProvider的
public?Map?dependencies(ModelContext?modelContext)? {
??return?delegate.dependencies(modelContext);
}
所以看DefaultModelProvider中的實(shí)現(xiàn)
public?Map?dependencies(ModelContext?modelContext)? {
??Map?models?=?newHashMap();
??for?(ResolvedType?resolvedType?:?dependencyProvider.dependentModels(modelContext))?{
????ModelContext?parentContext?=?ModelContext.fromParent(modelContext,?resolvedType);
????Optional?model?=?modelFor(parentContext).or(mapModel(parentContext,?resolvedType));
????if?(model.isPresent())?{
??????models.put(model.get().getName(),?model.get());
????}
??}
??return?models;
}
dependencyProvider.dependentModels和上面一個(gè)路子,一默認(rèn)一緩存,交替接口。
public?Set?dependentModels(ModelContext?modelContext)? {
??return?from(resolvedDependencies(modelContext))
??????.filter(ignorableTypes(modelContext))
??????.filter(not(baseTypes(modelContext)))
??????.toSet();
}
后面是兩個(gè)過(guò)濾,暫且不提。看resolvedDependencies
private?List?resolvedDependencies(ModelContext?modelContext)? {
??...
??List?dependencies?=?newArrayList(resolvedTypeParameters(modelContext,?resolvedType));
??dependencies.addAll(resolvedArrayElementType(modelContext,?resolvedType));
??dependencies.addAll(resolvedPropertiesAndFields(modelContext,?resolvedType));
??...
}
這里都是在構(gòu)造拓展類(lèi)型?ResolvedType,有一個(gè)叫resolvedPropertiesAndFields,看名字就是它了,進(jìn)去
private?List?resolvedPropertiesAndFields(ModelContext?modelContext,?ResolvedType?resolvedType)? {
??...
??List?properties?=?newArrayList();
??for?(ModelProperty?property?:?nonTrivialProperties(modelContext,?resolvedType))?{
????...
????properties.addAll(maybeFromCollectionElementType(modelContext,?property));
????properties.addAll(maybeFromMapValueType(modelContext,?property));
????properties.addAll(maybeFromRegularType(modelContext,?property));
??}}
看到ModelProperty,也就是對(duì)象內(nèi)部屬性代表的Model了,那就看nonTrivialProperties方法
private?FluentIterable?nonTrivialProperties(ModelContext?modelContext,?ResolvedType?resolvedType)? {
??return?from(propertiesFor(modelContext,?resolvedType))
??????.filter(not(baseProperty(modelContext)));
}
之后是propertiesFor
private?List?propertiesFor(ModelContext?modelContext,?ResolvedType?resolvedType)? {
??return?propertiesProvider.propertiesFor(resolvedType,?modelContext);
}
這個(gè)propertiesProvider.propertiesFor仍是一cache一default的策略,直接看實(shí)現(xiàn)
public?List?propertiesFor(ResolvedType?type,?ModelContext?givenContext)? {
??...
??for?(Map.Entry?each?:?propertyLookup.entrySet())?{
????BeanPropertyDefinition?jacksonProperty?=?each.getValue();
????Optional?annotatedMember
????????=?Optional.fromNullable(safeGetPrimaryMember(jacksonProperty));
????if?(annotatedMember.isPresent())?{
??????properties.addAll(candidateProperties(type,?annotatedMember.get(),?jacksonProperty,?givenContext));
????}
??}...
}
可以看到?List通過(guò)?candidateProperties方法獲取
@VisibleForTesting
List?candidateProperties(
????ResolvedType?type,
????AnnotatedMember?member,
????BeanPropertyDefinition?jacksonProperty,
????ModelContext?givenContext)? {
??List?properties?=?newArrayList();
??if?(member?instanceof?AnnotatedMethod)?{
????properties.addAll(findAccessorMethod(type,?member)
????????.transform(propertyFromBean(givenContext,?jacksonProperty))
????????.or(new?ArrayList()));
??}?else?if?(member?instanceof?AnnotatedField)?{
????properties.addAll(findField(type,?jacksonProperty.getInternalName())
????????.transform(propertyFromField(givenContext,?jacksonProperty))
????????.or(new?ArrayList()));
??}?else?if?(member?instanceof?AnnotatedParameter)?{
????ModelContext?modelContext?=?ModelContext.fromParent(givenContext,?type);
????properties.addAll(fromFactoryMethod(type,?jacksonProperty,?(AnnotatedParameter)?member,?modelContext));
??}
?...
}
這里根據(jù)?AnnotatedMember判斷類(lèi)成員的類(lèi)型,進(jìn)行不同的處理。enum使用的是?propertyFromBean
?...
??public?List?apply(ResolvedMethod?input)? {
????ResolvedType?type?=?paramOrReturnType(typeResolver,?input);
????if?(!givenContext.canIgnore(type))?{
??????if?(shouldUnwrap(input))?{
??????????return?propertiesFor(type,?fromParent(givenContext,?type));
??????}
??????return?newArrayList(beanModelProperty(input,?jacksonProperty,?givenContext));
????}...
????}};
接著是?beanModelProperty
private?ModelProperty?beanModelProperty(
????...
??return?schemaPluginsManager.property(
??????new?ModelPropertyContext(propertyBuilder,
??????????jacksonProperty,
??????????typeResolver,
??????????...
最后調(diào)用了?schemaPluginsManager.property
public?ModelProperty?property(ModelPropertyContext?context)?{
??//?根據(jù)文檔類(lèi)型取出?ModelPropertyBuilderPlugin
??for?(ModelPropertyBuilderPlugin?enricher?:?propertyEnrichers.getPluginsFor(context.getDocumentationType()))?{
????enricher.apply(context);
??}
??return?context.getBuilder().build();
}
ModelPropertyBuilderPlugin是一個(gè)接口,看它的其中一個(gè)實(shí)現(xiàn)類(lèi)ApiModelPropertyPropertyBuilder
public?void?apply(ModelPropertyContext?context)?{
??//?取出元素的注解
??Optional?annotation?=?Optional.absent();
??...
??if?(annotation.isPresent())?{
????context.getBuilder()
????????.allowableValues(annotation.transform(toAllowableValues()).orNull())
????????.required(annotation.transform(toIsRequired()).or(false))
????????.readOnly(annotation.transform(toIsReadOnly()).or(false))
????????.description(annotation.transform(toDescription()).orNull())
????????.isHidden(annotation.transform(toHidden()).or(false))
????????.type(annotation.transform(toType(context.getResolver())).orNull())
????????.position(annotation.transform(toPosition()).or(0))
????????.example(annotation.transform(toExample()).orNull());
??}
}
可以看到通過(guò)判斷是否存在注解,再設(shè)置具體的配置。
其中type就是enum展示的類(lèi)型了,可以固定。allowableValues就是enum的value,可以自定義,還可以加入description.
具體實(shí)現(xiàn)可以通過(guò)重寫(xiě)ApiModelPropertyPropertyBuilder的apply實(shí)現(xiàn)。
到這里,兩個(gè)問(wèn)題都得到解決。Springfox的加載過(guò)程也基本介紹了一遍。

