springfox 源码分析(八) 遍历接口获取Model对象

2019/05/24 springfox 浏览

我们通过读DocumentationPluginsBootstrapper代码中的start方法,了解到springfox根据我们外部提供的Docket对象进行初始化时,会通过Docket对象构建DocumentationContext对象来进行初始化操作

private DocumentationContext buildContext(DocumentationPlugin each) {
    return each.configure(defaultContextBuilder(each));
}

BuildContext方法是通过Docket对象来构建最终的DocumentaionContext对象

所以我们在研究Documentation之前,先来看DocumentationContext对象主要是包含哪些属性和方法

先来看DocumentationContext的源码:

public class DocumentationContext {
  //文档类型
  private final DocumentationType documentationType;
  //请求接口
  private final List<RequestHandler> handlerMappings;
  //接口信息,包括title、描述等信息
  private final ApiInfo apiInfo;
  //分组名称
  private final String groupName;
  //接口选择器
  private final ApiSelector apiSelector;

  private final AlternateTypeProvider alternateTypeProvider;
  //忽略的参数类型
  private final Set<Class> ignorableParameterTypes;
  //请求方法对应的响应状态码信息
  private final Map<RequestMethod, List<ResponseMessage>> globalResponseMessages;
  //全局参数
  private final List<Parameter> globalOperationParameters;
  //分组策略
  private final ResourceGroupingStrategy resourceGroupingStrategy;
  //路径Provider
  private final PathProvider pathProvider;
  //安全信息
  private final List<SecurityContext> securityContexts;
  //安全Scheme
  private final List<? extends SecurityScheme> securitySchemes;
  //接口信息
  private final Ordering<ApiListingReference> listingReferenceOrdering;
  //接口描述
  private final Ordering<ApiDescription> apiDescriptionOrdering;
  //接口信息
  private final Ordering<Operation> operationOrdering;
  private final GenericTypeNamingStrategy genericsNamingStrategy;
  private final Optional<String> pathMapping;
  private final Set<ResolvedType> additionalModels;
  //tag分组标签
  private final Set<Tag> tags;
  private Set<String> produces;
  private Set<String> consumes;
  //主机号
  private String host;
  //协议
  private Set<String> protocols;
  private boolean isUriTemplatesEnabled;
  //扩展属性
  private List<VendorExtension> vendorExtensions;
  //getter and setter and constructor
}

我们姑且称他为文档上下文环境吧,springfox是通过文档上下文(DocumentationContext)最终构建真正的Documenation对象,然后缓存在内存中,最终通过接口/v2/api-docs将Documentation对象转换为标准的Swagger对象输出.

DocumentationContextBuilder

在springfox的源码中,大量的使用了Builder构造器来进行目标对象的构建,所以文档上下文也一样,最终通过DocumentaionContextBuilder来构造创建

DocumentationPlugin中提供了根据DocumentationContextBuilder来创建DocumenationContext的方法

public interface DocumentationPlugin extends Plugin<DocumentationType> {
  /**
   * Creates a documentation context based on a given DocumentationContextBuilder
   *
   * @param builder - @see springfox.documentation.spi.service.contexts.DocumentationContextBuilder
   * @return context to use for building the documentation
   */
  DocumentationContext configure(DocumentationContextBuilder builder);
    
}

源码

根据DocumentaionPlugin对象来构建Builder

public class DocumentationContextBuilder {
  //安全参数
  private final List<SecurityContext> securityContexts = newArrayList();
  //忽律类型
  private final Set<Class> ignorableParameterTypes = newHashSet();
  //接口响应状态码
  private final Map<RequestMethod, List<ResponseMessage>> responseMessageOverrides = newTreeMap();
  //全局参数
  private final List<Parameter> globalOperationParameters = newArrayList();
  //类型规则
  private final List<AlternateTypeRule> rules = newArrayList();
  //默认接口状态响应吗
  private final Map<RequestMethod, List<ResponseMessage>> defaultResponseMessages = newHashMap();
  //protocols
  private final Set<String> protocols = newHashSet();
  private final Set<String> produces = newHashSet();
  private final Set<String> consumes = newHashSet();
  //扩展类型
  private final Set<ResolvedType> additionalModels = newHashSet();
  //分组tag
  private final Set<Tag> tags = newTreeSet(Tags.tagComparator());
  //扩展属性
  private List<VendorExtension> vendorExtensions = new ArrayList<VendorExtension>();
  //类型处理器
  private TypeResolver typeResolver;
  //接口集合
  private List<RequestHandler> handlerMappings;
  //接口信息
  private ApiInfo apiInfo;
  //分组名称
  private String groupName;
  //资源分组策略
  private ResourceGroupingStrategy resourceGroupingStrategy;
  //路径处理
  private PathProvider pathProvider;
  private List<? extends SecurityScheme> securitySchemes;
  private Ordering<ApiListingReference> listingReferenceOrdering;
  private Ordering<ApiDescription> apiDescriptionOrdering;
  //swagger文档类型
  private DocumentationType documentationType;
  private Ordering<Operation> operationOrdering;
  private boolean applyDefaultResponseMessages;
  //接口选择器
  private ApiSelector apiSelector = ApiSelector.DEFAULT;
  //主机
  private String host;
  //默认类型名称策略
  private GenericTypeNamingStrategy genericsNamingStrategy;
  //接口路径映射
  private Optional<String> pathMapping;
  
  private boolean isUrlTemplatesEnabled;
}

DocumentationContextBuilder基本覆盖了DocumentationContext的所有属性,而DocumentationContextBuilder没有提供构造函数来实例化参数,只提供了一个构造函数(文档类型),其余参数都是通过Builder构造器模式来赋值属性,最终通过Builder()方法来构建输出DocumentaionContext

构造函数

public DocumentationContextBuilder(DocumentationType documentationType) {
    this.documentationType = documentationType;
}

赋值属性

public DocumentationContextBuilder requestHandlers(List<RequestHandler> handlerMappings) {
    this.handlerMappings = handlerMappings;
    return this;
}
//more...

通过返回this对象的方式,提供了很多属性的赋值方法

build构造

public DocumentationContext build() {
    Map<RequestMethod, List<ResponseMessage>> responseMessages = aggregateResponseMessages();
    OrderComparator.sort(rules);
    return new DocumentationContext(documentationType,
        handlerMappings,
        apiInfo,
        groupName,
        apiSelector,
        ignorableParameterTypes,
        responseMessages,
        globalOperationParameters,
        resourceGroupingStrategy,
        pathProvider,
        securityContexts,
        securitySchemes,
        rules,
        listingReferenceOrdering,
        apiDescriptionOrdering,
        operationOrdering,
        produces,
        consumes,
        host,
        protocols,
        genericsNamingStrategy,
        pathMapping,
        isUrlTemplatesEnabled,
        additionalModels,
        tags,
        vendorExtensions);
  }

最终通过使用build()方法,调用DocumentationContext的构造函数,构建DocumentationContext对象

构造

所以,我们先来看DocumentationContextBuilder对象的创建,主要包含了那些参数、方法

/***
   * 构建文档builder
   * @param plugin
   * @return
   */
  private DocumentationContextBuilder defaultContextBuilder(DocumentationPlugin plugin) {
    DocumentationType documentationType = plugin.getDocumentationType();
    //获取RequestHandler
    //疑问:handlerProviders在何时初始化
    List<RequestHandler> requestHandlers = from(handlerProviders)
        .transformAndConcat(handlers())
        .toList();
    List<AlternateTypeRule> rules = from(nullToEmptyList(typeConventions))
          .transformAndConcat(toRules())
          .toList();
    return documentationPluginsManager
        .createContextBuilder(documentationType, defaultConfiguration)
        .rules(rules)
        .requestHandlers(combiner().combine(requestHandlers));
  }

通过代码我们可以了解到:

  • 首先获取所有的RequestHnadler,而RequestHandlerProvider的默认实现类是WebMvcRequestHandlerProvider,该实现类会接收Spring中的所有请求Mapping,最终转化为WebMvcRequestHandler,WebMvcRequestHandler是接口RequestHnadler的实现,这等于是拿到了所有的接口
  • 获取AlternateTypeRule的规则列表
  • 初始化创建Builder,赋值请求接口、rules

了解了DocumentationContextBuilder的构造方式,在来看他的创建过程

通过代码知道是通过DocumentationPluginsManagercreateContextBuilder方法来构建

public DocumentationContextBuilder createContextBuilder(
    DocumentationType documentationType,
    DefaultConfiguration defaultConfiguration) {
    return defaultsProviders.getPluginFor(documentationType, defaultConfiguration)
        .create(documentationType)
        .withResourceGroupingStrategy(resourceGroupingStrategy(documentationType));
}

defaultsProviders是也是一个Plugin接口,但是我们在前面章节也介绍说过,他只有一个实现类DefaultConfiguration,但是该实现类并没有通过@Compoent注解注入到Spring的容器中,所以此处通过Plugin的实现给了一个默认值defaultConfiguration,其实,此处就是使用的defaultConfiguration

那么该默认配置的create方法做了那些操作呢?,继续跟踪代码:

public class DefaultConfiguration implements DefaultsProviderPlugin {

    private final Defaults defaults;
    private final TypeResolver typeResolver;
    private final ServletContext servletContext;

    public DefaultConfiguration(Defaults defaults,
                                TypeResolver typeResolver,
                                ServletContext servletContext) {

        this.servletContext = servletContext;
        this.defaults = defaults;
        this.typeResolver = typeResolver;
    }

    @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赋值了默认的相关参数,主要包括:

  • 默认忽略Class类型
  • 默认响应状态码消息
  • 类型解析器
  • 接口选择器
  • 接口排序

通过上面源码我们知道:

  • 首先defaultsProviders是也是一个Plugin接口,但是我们在前面章节也介绍说过,他只有一个实现类DefaultConfiguration,但是该实现类并没有通过@Compoent注解注入到Spring的容器中,所以此处通过Plugin的实现给了一个默认值defaultConfiguration,其实,此处就是使用的defaultConfiguration

  • defaultConfiguration是在DocumentationPluginsBootstrapper的构造函数中通过new的方式进行构造的

  • //通过DefaultConfiguration可以构建DocumentationContextBuilder
    this.defaultConfiguration = new DefaultConfiguration(defaults, typeResolver, servletContext);
    
  • 在此处的构建过程中,赋值了资源分组策略.

赋值

从源码过程中,我们已经了解到Builder赋值的参数主要包括:

  • Spring环境中的所有接口,最终是RequestHandler的集合,实际则是WebMvcRequestHandler的集合
  • 赋值AlternateTypeRule规则集合
  • 赋值分组策略属性(resourceGroupingStrategy),默认是ClassOrApiAnnotationResourceGrouping实现

DocumentationContext

获取到了DocumentationContextBuilder对象,此时在通过DocumentationPlugin的configure方法,构建DocumentationContext

来看Docket的configure方法

/**
   * Builds the Docket by merging/overlaying user specified values.
   * It is not necessary to call this method when defined as a spring bean.
   * NOTE: Calling this method more than once has no effect.
   *
   * @see DocumentationPluginsBootstrapper
   */
  public DocumentationContext configure(DocumentationContextBuilder builder) {
    return builder
        .apiInfo(apiInfo)
        .selector(apiSelector)
        .applyDefaultResponseMessages(applyDefaultResponseMessages)
        .additionalResponseMessages(responseMessages)
        .additionalOperationParameters(globalOperationParameters)
        .additionalIgnorableTypes(ignorableParameterTypes)
        .ruleBuilders(ruleBuilders)
        .groupName(groupName)
        .pathProvider(pathProvider)
        .securityContexts(securityContexts)
        .securitySchemes(securitySchemes)
        .apiListingReferenceOrdering(apiListingReferenceOrdering)
        .apiDescriptionOrdering(apiDescriptionOrdering)
        .operationOrdering(operationOrdering)
        .produces(produces)
        .consumes(consumes)
        .host(host)
        .protocols(protocols)
        .genericsNaming(genericsNamingStrategy)
        .pathMapping(pathMapping)
        .enableUrlTemplating(enableUrlTemplating)
        .additionalModels(additionalModels)
        .tags(tags)
        .vendorExtentions(vendorExtensions)
        .build();
  }

针对Builder的一些列二次赋值,最终通过build方法构造

我们的Docket对象是我们开发人员在外部通过Bean来创建的,来看Docket的部分代码:

public class Docket implements DocumentationPlugin {

  public static final String DEFAULT_GROUP_NAME = "default";

  private final DocumentationType documentationType;
  private final List<SecurityContext> securityContexts = newArrayList();
  private final Map<RequestMethod, List<ResponseMessage>> responseMessages = newHashMap();
  private final List<Parameter> globalOperationParameters = newArrayList();
  private final List<Function<TypeResolver, AlternateTypeRule>> ruleBuilders = newArrayList();
  private final Set<Class> ignorableParameterTypes = newHashSet();
  private final Set<String> protocols = newHashSet();
  private final Set<String> produces = newHashSet();
  private final Set<String> consumes = newHashSet();
  private final Set<ResolvedType> additionalModels = newHashSet();
  private final Set<Tag> tags = newHashSet();

  private PathProvider pathProvider;
  private List<? extends SecurityScheme> securitySchemes;
  private Ordering<ApiListingReference> apiListingReferenceOrdering;
  private Ordering<ApiDescription> apiDescriptionOrdering;
  private Ordering<Operation> operationOrdering;

  private ApiInfo apiInfo = ApiInfo.DEFAULT;
  private String groupName = DEFAULT_GROUP_NAME;
  private boolean enabled = true;
  private GenericTypeNamingStrategy genericsNamingStrategy = new DefaultGenericTypeNamingStrategy();
  private boolean applyDefaultResponseMessages = true;
  private String host = "";
  private Optional<String> pathMapping = Optional.absent();
  private ApiSelector apiSelector = ApiSelector.DEFAULT;
  private boolean enableUrlTemplating = false;
  private List<VendorExtension> vendorExtensions = newArrayList();
}

我们通过Docket来外部赋值的对象值,最终都会构建到DocumentationContext上下文中,我们先来看我们开发中一般创建Docket对象过程

@Bean(value = "defaultApi")
@Order(value = 4)
public Docket defaultApi() {
    ParameterBuilder parameterBuilder=new ParameterBuilder();
    List<Parameter> parameters= Lists.newArrayList();
    parameterBuilder.name("token").description("token令牌").modelRef(new ModelRef("String"))
        .parameterType("header")
        .required(true).build();
    parameters.add(parameterBuilder.build());

    Docket docket=new Docket(DocumentationType.SWAGGER_2)
        .apiInfo(apiInfo())
        .groupName("默认接口")
        .select()
        .apis(RequestHandlerSelectors.basePackage("com.swagger.bootstrap.ui.demo.controller"))
        //.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
        .paths(PathSelectors.any())
        .build().globalOperationParameters(parameters)
        .securityContexts(Lists.newArrayList(securityContext())).securitySchemes(Lists.<SecurityScheme>newArrayList(apiKey()));
    return docket;
}

一般创建Docket对象,主要的赋值参数:

  • 分组名称
  • apiInfo信息
  • ApiSelector
  • 全局参数
  • 权限验证

主要包含了以上的一些信息,整个文档上下文环境构造完成,我们也可以以Debug的方式来跟踪代码,如下图:

除了默认的参数赋值外,接口、definitions、model等关键信息等都还没有初始化。

站内搜索

    TorchV Bot开放试用中......

    最新内容,关注“八一菜刀”公众号

    Table of Contents