<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 15623字,需瀏覽 32分鐘

           ·

          2020-12-23 07:20

          先說(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。在看DefaultConfigurationcreate方法:

          @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)化為?ModelmodelProvider.modelFor()是通過(guò)ModelProvider實(shí)現(xiàn),下一個(gè)問(wèn)題會(huì)詳細(xì)闡述。

          那么,如何解決這個(gè)問(wèn)題:

          1.使用?DocketadditionalModels方法,在配置類(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ě)OperationModelsProviderPluginapply方法,添加自定義收集器。或者直接重寫(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,“分鐘”),也就是包括了codename描述。

          但實(shí)際enum的值會(huì)是二者之一。且不會(huì)生成如下的可重用的外部引用。

          "schema":{
          ??????????"$ref":"#/definitions/xxxForm"
          }

          注意:可重用的問(wèn)題在3.0+可以通過(guò)配置處理。

          如果需要強(qiáng)制將enum的值設(shè)為codename,或拓展更多的內(nèi)容,就需要來(lái)看看,enum類(lèi)何時(shí)會(huì)被處理。

          上一個(gè)問(wèn)題的結(jié)尾說(shuō)到apiModelReader.read,?modelContexts轉(zhuǎn)化為?ModelmodelProvider.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);
          ??}
          }

          CachingModelProviderdependencies依賴(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就是enumvalue,可以自定義,還可以加入description.

          具體實(shí)現(xiàn)可以通過(guò)重寫(xiě)ApiModelPropertyPropertyBuilderapply實(shí)現(xiàn)。

          到這里,兩個(gè)問(wèn)題都得到解決。Springfox的加載過(guò)程也基本介紹了一遍。


          點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了
          瀏覽 51
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲在在线观看 | 免费毛片看片 | 九九精品视频在线观看 | 黄色性爱视频在线播放 | 久久黄色成人视频 |