Springfox swagger2 源码解析

举报
琴岛蛏子 发表于 2022/03/21 00:00:55 2022/03/21
【摘要】 Springfox swagger2 源码解析doc访问页面 http://localhost:8080/doc.htmlapi-json访问页面 http://localhost:8080/v2/api-docspom依赖<dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boo...

Springfox swagger2 源码解析

doc访问页面 http://localhost:8080/doc.html
api-json访问页面 http://localhost:8080/v2/api-docs

pom依赖

<dependency>
  <groupId>com.github.xiaoymin</groupId>
  <artifactId>knife4j-spring-boot-starter</artifactId>
  <version>3.0.2</version>
</dependency>

找starter的启动配置文件spring.factories文件找到启动类,做了一些bean的配置。

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration

根据路径/v2/api-docs顺藤摸瓜 找到 Swagger2ControllerWebMvc

@RequestMapping(
      method = RequestMethod.GET,
      produces = {APPLICATION_JSON_VALUE, HAL_MEDIA_TYPE})
  public ResponseEntity<Json> getDocumentation(
      @RequestParam(value = "group", required = false) String swaggerGroup,
      HttpServletRequest servletRequest) {

    String groupName = ofNullable(swaggerGroup).orElse(Docket.DEFAULT_GROUP_NAME);
    Documentation documentation = documentationCache.documentationByGroup(groupName);
    if (documentation == null) {
      LOGGER.warn("Unable to find specification for group {}", groupName);
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
    Swagger swagger = mapper.mapDocumentation(documentation);
    SwaggerTransformationContext<HttpServletRequest> context
        = new SwaggerTransformationContext<>(swagger, servletRequest);
    List<WebMvcSwaggerTransformationFilter> filters = transformations.getPluginsFor(DocumentationType.SWAGGER_2);
    for (WebMvcSwaggerTransformationFilter each : filters) {
      context = context.next(each.transform(context));
    }
    return new ResponseEntity<>(jsonSerializer.toJson(context.getSpecification()), HttpStatus.OK);
  }

}

从缓存中获取文档
Documentation documentation = documentationCache.documentationByGroup(groupName);

何时加载缓存文档呢?
由文档加载器DocumentationPluginsBootstrapper进行加载,类实现了SmartLifecycle接口,当spring加载完bean后,调用start()方法进行加载。

public class DocumentationPluginsBootstrapper
    extends AbstractDocumentationPluginsBootstrapper
    implements SmartLifecycle{
        
        public DocumentationPluginsBootstrapper(
            DocumentationPluginsManager documentationPluginsManager,
            List<RequestHandlerProvider> handlerProviders,
            DocumentationCache scanned,
            ApiDocumentationScanner resourceListing,
            TypeResolver typeResolver,
            Defaults defaults,
            PathProvider pathProvider,
            Environment environment) {
          super(
              documentationPluginsManager,
              handlerProviders,
              scanned,
              resourceListing,
              defaults,
              typeResolver,
              pathProvider);
        
          this.environment = environment;
        }
        // bean 加载完成后调用
        public void start() {
          if (initialized.compareAndSet(false, true)) {
            LOGGER.debug("Documentation plugins bootstrapped");
            super.bootstrapDocumentationPlugins();
          }
        }
    }

Spring SmartLifecycle 在容器所有bean加载和初始化完毕执行

在使用Spring开发时,我们都知道,所有bean都交给Spring容器来统一管理,其中包括每一个bean的加载和初始化。
有时候我们需要在Spring加载和初始化所有bean后,接着执行一些任务或者启动需要的异步服务,这样我们可以使用 SmartLifecycle 来做到。

这个和 @PostConstruct、@PreDestroy 的bean的初始化和销毁方法不同,Bean生命周期级别和容器生命周期级别在应用场景上是有区别的。

SmartLifecycle 是一个接口。当Spring容器加载所有bean并完成初始化之后,会接着回调实现该接口的类中对应的方法(start()方法)。

文档插件管理器 DocumentationPluginsManager
此类以PluginRegistry的方式注入了一系列插件

documentationPlugins()方法调用了DocumentationPlugin 文档插件,若没有则生成一个默认的插件Docket,通常在Swagger2Configuration配置文件会注入一个Docket。

@Component
public class DocumentationPluginsManager {
  @Autowired
  @Qualifier("documentationPluginRegistry")
  private PluginRegistry<DocumentationPlugin, DocumentationType> documentationPlugins;
  @Autowired
  @Qualifier("apiListingBuilderPluginRegistry")
  private PluginRegistry<ApiListingBuilderPlugin, DocumentationType> apiListingPlugins;
  @Autowired
  @Qualifier("parameterBuilderPluginRegistry")
  private PluginRegistry<ParameterBuilderPlugin, DocumentationType> parameterPlugins;
  @Autowired
  @Qualifier("expandedParameterBuilderPluginRegistry")
  private PluginRegistry<ExpandedParameterBuilderPlugin, DocumentationType> parameterExpanderPlugins;
  @Autowired
  @Qualifier("operationBuilderPluginRegistry")
  private PluginRegistry<OperationBuilderPlugin, DocumentationType> operationBuilderPlugins;
  @Autowired
  @Qualifier("operationModelsProviderPluginRegistry")
  private PluginRegistry<OperationModelsProviderPlugin, DocumentationType> operationModelsProviders;
  @Autowired
  @Qualifier("defaultsProviderPluginRegistry")
  private PluginRegistry<DefaultsProviderPlugin, DocumentationType> defaultsProviders;
  @Autowired
  @Qualifier("pathDecoratorRegistry")
  private PluginRegistry<PathDecorator, DocumentationContext> pathDecorators;
  @Autowired
  @Qualifier("apiListingScannerPluginRegistry")
  private PluginRegistry<ApiListingScannerPlugin, DocumentationType> apiListingScanners;
  @Autowired
  @Qualifier("responseBuilderPluginRegistry")
  private PluginRegistry<ResponseBuilderPlugin, DocumentationType> responsePlugins;
  @Autowired
  @Qualifier("modelNamesRegistryFactoryPluginRegistry")
  private PluginRegistry<ModelNamesRegistryFactoryPlugin, DocumentationType> modelNameRegistryFactoryPlugins;

  public Collection<DocumentationPlugin> documentationPlugins() throws IllegalStateException {
    List<DocumentationPlugin> plugins = documentationPlugins.getPlugins();
    ensureNoDuplicateGroups(plugins);
    if (plugins.isEmpty()) {
      return singleton(defaultDocumentationPlugin());
    }
    return plugins;
  }

例如 OperationBuilderPlugin
PluginRegistry<OperationBuilderPlugin, DocumentationType> operationBuilderPlugins

Control + H 查看其继承结构

image.png

栗子:
SwaggerResponseMessageReader实现了ResponseMessage的读取。read()方法从OperationContext上下文中获取ApiOperation、ResponseHeader、ApiResponse注解信息。
public class SwaggerResponseMessageReader implements OperationBuilderPlugin {
protected Compatibility<Set<ResponseMessage>, Set<Response>> read(OperationContext context) {}
}


PluginRegistry
Spring Plugin提供一个标准的Plugin接口供开发人员继承使用声明自己的插件机制,然后通过@EnablePluginRegistries注解依赖注入到Spring的容器中,Spring容器会为我们自动匹配到插件的所有实现子对象,最终我们在代码中使用时,通过依赖注入注解,注入PluginRegistry extends Plugin对象拿到插件实例进行操作。


项目中自定义swagger2Config配置文件,注入Docket对象,即一个DocumentationPlugin插件。
public class Docket implements DocumentationPlugin

@Configuration
public class Swagger2Config {

  @Bean
  public Docket createRestApi() {

    List<Response> globalResponses = new ArrayList<>();
    // 根据Enum构建了全局的Response
    for (ResponseCodeEnums item : ResponseCodeEnums.values()) {
      globalResponses.add(new ResponseBuilder()
        .code(String.valueOf(item.getCode()))
        .description(item.getDesc())
        .build());
    }

    return  new Docket(DocumentationType.OAS_30)
        .useDefaultResponseMessages(true)
        .globalResponses(HttpMethod.GET, globalResponses)
        .globalResponses(HttpMethod.POST, globalResponses)
        .apiInfo(apiInfo())
        .select()
        .apis(RequestHandlerSelectors.basePackage("com.dogs.doc.controller"))
        .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
        .paths(PathSelectors.any())
        .build();

  }

  private ApiInfo apiInfo() {
    return new ApiInfoBuilder()
        .title("Dogs APIs")
        .description("knife4j")
        .termsOfServiceUrl("")
        .version("3.0")
        .build();
  }

springfox的配置文件SpringfoxWebConfiguration

EnablePluginRegistries启用插件
Defaults组件实现了一些默认的设置,如全局Response 200、401、403的返回信息。

@Configuration
@Import({ ModelsConfiguration.class })
@ComponentScan(basePackages = {
    "springfox.documentation.spring.web.scanners",
    "springfox.documentation.spring.web.readers.operation",
    "springfox.documentation.spring.web.readers.parameter",
    "springfox.documentation.spring.web.plugins",
    "springfox.documentation.spring.web.paths"
})
@EnablePluginRegistries({ DocumentationPlugin.class,
    ApiListingBuilderPlugin.class,
    OperationBuilderPlugin.class,
    ParameterBuilderPlugin.class,
    ResponseBuilderPlugin.class,
    ExpandedParameterBuilderPlugin.class,
    OperationModelsProviderPlugin.class,
    DefaultsProviderPlugin.class,
    PathDecorator.class,
    ApiListingScannerPlugin.class,
    ModelNamesRegistryFactoryPlugin.class
})
public class SpringfoxWebConfiguration {

  @Bean
  public Defaults defaults() {
    return new Defaults();
  }

  @Bean
  public DocumentationCache resourceGroupCache() {
    return new DocumentationCache();
  }

  @Bean
  public JsonSerializer jsonSerializer(List<JacksonModuleRegistrar> moduleRegistrars) {
    return new JsonSerializer(moduleRegistrars);
  }

  @Bean
  public DescriptionResolver descriptionResolver(Environment environment) {
    return new DescriptionResolver(environment);
  }

  @Bean
  public HandlerMethodResolver methodResolver(TypeResolver resolver) {
    return new HandlerMethodResolver(resolver);
  }

  @Bean
  public PathProvider pathProvider() {
    return new DefaultPathProvider();
  }
}

总结:
swagger基于PluginRegistry的方式注册插件。 默认提供了配置类ApiInfo,用户可以自定义配置类,可配置的内容可以参考ApiInfo的属性。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。