SwaggarカスタムModel、Enum(Spring Foxソース分析)
29977 ワード
Springfoxソース分析-カスタムModel、Enum
まずSprigfoxとSwaggerの関係を言います。
Swaggerは規範です。
springfox-swagerはSpring生態系に基づくこの規範の実現である。
スプリングfox-swaguer-uiはswaguer-uiのパッケージで、Springのサービスを利用できるようにしています。
仕事中はSwaggar Jsonに基づいていくつか処理をする必要があるため、しかしSwaggar Jsonのフォーマットはそんなに需要を満たすのではありません。
本論文のsprigfox-swagerバージョン番号:
1.GET方法のパラメータオブジェクト
第一の問題は、方法がGET要求である場合、パラメータはカスタムObjectであり、展示時(生成されたJSON)は本Objectの記述を含まない。ですから、これらのModelの記述はいつ発生するかを見てください。
何事にも終始があり、SpringFoxは
このクラスは、SmartLifecycleインターフェースを実現し、
次に
上記の
最初の Operation ModelsProvidePlugin:処理リターンタイプ、パラメータタイプなどの SwaggarrOperation ModelsProvider:swaggerの注釈提供の値のタイプ、 先に
じゃ、どうやってこの問題を解決しますか?は、 を注入する。.swaggar-boot strap-uiのような第三者クラスの工具類を利用しています。 は、カスタムコレクタを追加する 問題が解決する
2.Enumの説明形式
問題は、エニュメレート・クラスについて、生成されたJSONファイルに記述されるのは、元のパラメータオブジェクトにおける次のようなフォーマットである。
しかし、実際の
前の問題の最後に Default Model Provider:デフォルトでは、毎回model Conteetをmodel に変換します。 CachingModelProvider:gavaキャッシュカードを宣言しました。まずキャッシュから取って、ない場合は初期化プロセッサを呼び出して、モデルに変換してキャッシュに入れます。
具体的な実施は、書き換え
ここに来て、二つの問題が解決されます。Springfoxのロード過程も基本的に紹介しました。
まずSprigfoxとSwaggerの関係を言います。
Swaggerは規範です。
springfox-swagerはSpring生態系に基づくこの規範の実現である。
スプリングfox-swaguer-uiはswaguer-uiのパッケージで、Springのサービスを利用できるようにしています。
仕事中はSwaggar Jsonに基づいていくつか処理をする必要があるため、しかしSwaggar Jsonのフォーマットはそんなに需要を満たすのではありません。
本論文のsprigfox-swagerバージョン番号:
2.6.0
本文は問題から出発して、関連のソースコードを探求します。1.GET方法のパラメータオブジェクト
第一の問題は、方法がGET要求である場合、パラメータはカスタムObjectであり、展示時(生成されたJSON)は本Objectの記述を含まない。ですから、これらのModelの記述はいつ発生するかを見てください。
何事にも終始があり、SpringFoxは
springfox.documentation.spring.web.plugins
下のDocumentationPluginsBootstrapper
にあります。このクラスは、SmartLifecycleインターフェースを実現し、
@Component
を介してコンテナに注入されたbeanを実現し、コンテナ初期化後に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) {
//
DocumentationType documentationType = each.getDocumentationType();
if (each.isEnabled()) {
//
scanDocumentation(buildContext(each));
}
}
}
}
buildContext
メソッドを呼び出して、Docetオブジェクトを介してDcumentain Contextオブジェクトを作成します。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
インターフェースであり、実装クラスはWebMvcRequestHandlerProvider
であり、requestHandlers
方法は、Spring内のすべての要求マッピングを受信する。次に
DocumentationContextBuilder
の構造過程を見ます。documentationPluginsManager.createContextBuilder
public DocumentationContextBuilder createContextBuilder(DocumentationType documentationType,
DefaultConfiguration defaultConfiguration) {
return defaultsProviders.getPluginFor(documentationType, defaultConfiguration)
.create(documentationType)
.withResourceGroupingStrategy(resourceGroupingStrategy(documentationType));
}
defaultsProviders
は、同じプラグインインターフェースDefaultsProviderPlugin
であり、実装クラスDefaultConfiguration
しかないが、@Compoent
を使用していないので、代替値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
に関連パラメータを設定し、DocumentationContextBuilder
を取得しました。上記の
buildContext
方法に戻って、defaultContextBuilder
方法が実行されました。次にconfigure
です。return each.configure(this.defaultContextBuilder(each));
DocumentationPlugin
は1つの実装クラスDocket
だけです。ここに来たらちょっと熟知しています。Docket
オブジェクトは、私たち開発者が外部で@Bean
を介して作成したものであり、外部割当の対象値は最終的にDocumentationContext
に統合される。ここのconfig
は二次的な割り当てです。一般的に自分で定義したDocket
オブジェクトを見てもいいです。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();
}
}
ここには実際にデフォルトのパラメータしか設定されていません。しかし、インターフェース、定義、モデルなどのキー情報は初期化されていません。最初の
start()
に戻って、scanDocumentation(buildContext(each))
のscanDocumentation
を見てください。private void scanDocumentation(DocumentationContext context) {
scanned.addDocumentation(resourceListing.scan(context));
}
scan
はApiDocumentationScanner
クラスに位置している。public Documentation scan(DocumentationContext context) {
ApiListingReferenceScanResult result = apiListingReferenceScanner.scan(context);
...
Multimap apiListings = apiListingScanner.scan(listingContext);
...
apiListingReferenceScanner.scan
はApiListingReferenceScanner
クラスに位置しています。public ApiListingReferenceScanResult scan(DocumentationContext context) {
...
// Docket .select()
ApiSelector selector = context.getApiSelector();
// package ( ) , 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));
}
これまですべてのインターフェースを取得し、パケット化してきましたが、ArayListMultimapはguava
の方法です。ApiDocumentationScanner
のscan
に戻る方法は、apiListingScanner.scan
を参照してください。public Multimap scan(ApiListingScanningContext context) {
...
for (ResourceGroup resourceGroup : sortedByName(requestMappingsByResourceGroup.keySet())) {
...
for (RequestMappingContext each : sortedByMethods(requestMappingsByResourceGroup.get(resourceGroup))) {
// Controller , Model
models.putAll(apiModelReader.read(each.withKnownModels(models)));
apiDescriptions.addAll(apiDescriptionReader.read(each));
}
each.withKnownModels
は複製オブジェクトであり、主に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
からModel
に変換し、pluginsManager.modelContexts
を見て、modelContexts
をどうやって取りますか?public Set modelContexts(RequestMappingContext context) {
DocumentationType documentationType = context.getDocumentationContext().getDocumentationType();
// ModelContext
for (OperationModelsProviderPlugin each : operationModelsProviders.getPluginsFor(documentationType)) {
each.apply(context);
}
return context.operationModelsBuilder().build();
}
OperationModelsProviderPlugin
には、2つの実装クラスがあり、ドキュメントタイプによって取得される。@ApiResponse
、@ApiOperation
などのOperationModelsProviderPlugin
を見ます@Override
public void apply(RequestMappingContext context) {
//
collectFromReturnType(context);
//
collectParameters(context);
//
collectGlobalModels(context);
}
ここに来たら、この問題(GET方法の要求Objectは記述しない)の答えが出てきます。collectParameters
を見にきましたprivate void collectParameters(RequestMappingContext context) {
//
List parameterTypes = context.getParameters();
for (ResolvedMethodParameter parameterType : parameterTypes) {
//
if (parameterType.hasParameterAnnotation(RequestBody.class)
|| parameterType.hasParameterAnnotation(RequestPart.class)) {
ResolvedType modelType = context.alternateFor(parameterType.getParameterType());
context.operationModelsBuilder().addInputParam(modelType);
}
}
}
解決されました。フィルタリングは2つしか処理されません。@RequestBody
と@ReuqestPart
によって注釈表示されていますが、GET方法のパラメータはこの2つの注釈を使用することができません。(もちろん仕様から言えば、GET法もこのパラメータをすべきではない)。OperationModelsProviderPlugin
の他の実施形態SwaggerOperationModelsProvider
は、主に@ApiOperation
を使用した場合のマスタ属性値と@ApiResponse
の応答状態コードに関する型番を収集し、詳細なリストを省略する。apiModelReader.read
のmodelContexts
はModel
に変換されたmodelProvider.modelFor()
はModelProvider
によって実現され、次の問題は詳細に説明される。じゃ、どうやってこの問題を解決しますか?
Docket
のadditionalModels
方法を用いて、構成クラスにTypeResolver
return new Docket(DocumentationType.SWAGGER_2)
.additionalModels(typeResolver.resolve(xxx))
...
OperationModelsProviderPlugin
のapply
方法を書き換える。または直接書き換えてもいいです。たとえば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);
}
} ...
}}
2.Enumの説明形式
問題は、エニュメレート・クラスについて、生成されたJSONファイルに記述されるのは、元のパラメータオブジェクトにおける次のようなフォーマットである。
"xxx": {...}
"periodUnit":{
"type":"string",
"enum":[
"MINUTE",
"HOUR"
...
]}
一般的なエニュメレーション使用は、collectParameters
、すなわちMINUTE(1,“ ”)
、code
を含む。しかし、実際の
name
の値は2つのうちの1つになります。また、以下のような重用可能な外部参照は生成されません。"schema":{
"$ref":"#/definitions/xxxForm"
}
注意:再利用可能な問題は、構成によりenum
で処理されてもよい。3.0+
の値を強制的にenum
またはcode
に設定する必要がある場合、またはより多くのコンテンツを拡張する必要がある場合、name
クラスはいつ処理されるかを見に来てください。前の問題の最後に
enum
と述べたが、apiModelReader.read
からmodelContexts
に変換されたModel
方法はmodelProvider.modelFor()
によって実現されたものであり、実はModelProvider`はインターフェースであり、2つの実施形態がある:ModelProvider
の構成方法ではApiModelReader
を使用することが指定されていますが、初めてキャッシュにはないので、CachingModelProvider
に下ります。private void populateDependencies(ModelContext modelContext, Map modelMap) {
Map dependencies = modelProvider.dependencies(modelContext);
for (Model each : dependencies.values()) {
mergeModelMap(modelMap, each);
}
}
populateDependencies
のCachingModelProvider
はdependencies
に依存している。public Map dependencies(ModelContext modelContext) {
return delegate.dependencies(modelContext);
}
だからDefaultModelProvider
の中の実現を見ます。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;
}
DefaultModelProvider
と上記のサブは、デフォルトでキャッシュされ、インターフェースが交替される。public Set dependentModels(ModelContext modelContext) {
return from(resolvedDependencies(modelContext))
.filter(ignorableTypes(modelContext))
.filter(not(baseTypes(modelContext)))
.toSet();
}
後は二つの濾過です。dependencyProvider.dependentModels
を見ますprivate List resolvedDependencies(ModelContext modelContext) {
...
List dependencies = newArrayList(resolvedTypeParameters(modelContext, resolvedType));
dependencies.addAll(resolvedArrayElementType(modelContext, resolvedType));
dependencies.addAll(resolvedPropertiesAndFields(modelContext, resolvedType));
...
}
ここは全部構造拡張タイプのresolvedDependencies
です。ResolvedType
という名前があります。名前を見たらそれです。中に入ります。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));
}}
resolvedPropertiesAndFields
、つまり対象の内部属性が表すModelを見ました。private FluentIterable nonTrivialProperties(ModelContext modelContext, ResolvedType resolvedType) {
return from(propertiesFor(modelContext, resolvedType))
.filter(not(baseProperty(modelContext)));
}
その後はModelProperty
です。private List<ModelProperty> propertiesFor(ModelContext modelContext, ResolvedType resolvedType) {
return propertiesProvider.propertiesFor(resolvedType, modelContext);
}
このnonTrivialProperties
はまだ一cacheのdefaultの策略で、直接に実現を見ます。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));
}
}...
}
propertiesFor
がpropertiesProvider.propertiesFor
方法で取得することが見られます。@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));
}
...
}
ここでは、クラスメンバーのタイプをList
に基づいて判断し、異なる処理を行う。enumはcandidateProperties
を使用しています。 ...
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));
}...
}};
次にAnnotatedMember
です。private ModelProperty beanModelProperty(
...
return schemaPluginsManager.property(
new ModelPropertyContext(propertyBuilder,
jacksonProperty,
typeResolver,
...
最後にpropertyFromBean
を呼び出しました。public ModelProperty property(ModelPropertyContext context) {
// ModelPropertyBuilderPlugin
for (ModelPropertyBuilderPlugin enricher : propertyEnrichers.getPluginsFor(context.getDocumentationType())) {
enricher.apply(context);
}
return context.getBuilder().build();
}
beanModelProperty
は一つのインターフェースであり、一つのインプリメンテーションクラスschemaPluginsManager.property
を参照してください。public void apply(ModelPropertyContext context) {
//
Optional<ApiModelProperty> 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());
}
}
コメントがあるかどうかを判断して、具体的な配置を設定することができます。ModelPropertyBuilderPlugin
は、ApiModelPropertyPropertyBuilder
によって示されるタイプであり、固定可能である。type
はenum
のallowableValues
であり、カスタムすることができ、enum
に加入することもできる。具体的な実施は、書き換え
value
のdescription
によって実施されてもよい。ここに来て、二つの問題が解決されます。Springfoxのロード過程も基本的に紹介しました。