Swagger2原理详解与示例

8 篇文章 0 订阅

       在前后端分离开发的今天,接口文档显得尤为重要,其重要性有以下几点:1、约定接口信息,便于前后端分离开发;2、便于测试人员测试并检查数据;3、加快新员工上手工作的速度;4、后续工作交接。然而,接口文档的整理与编写又令繁重的开发工作雪上加霜。有没有好用的工具或者api可以简化这些工作呢?答案是肯定的,这也是我们今天要介绍的Swagger。话不多说,先来个Demo开开胃。

一、项目结构

(1) config:Swagger配置内容; (2) controller:测试接口;(3) spring:spring mvc的项目配置文件;

(4) web.xml:项目启动配置;    (5) pom.xml:项目依赖管理

二、项目内容

SwaggerConfig.java

@Configuration
@EnableSwagger2
@EnableWebMvc
public class SwaggerConfig {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.withClassAnnotation(Controller.class))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("example接口文档")
                .description("使用Swagger2构建RESTful APIs")
                .build();
    }

}

简单来说,这个文件就是用来配置Swagger2的,创建Docket对象来介绍接口信息。

(1) apis():根据配置决定生成接口的路径或注解;   (2) paths():用于控制哪些接口生成;

TestController.java

@Controller
@RequestMapping("/test")
@Api("测试用例")
public class TestController {

    @ResponseBody
    @GetMapping("/hello")
    @ApiOperation(nickname = "sayHello", value = "欢迎光临", notes = "欢迎光临", response = String.class)
    @ApiImplicitParams({
            @ApiImplicitParam(name = "name", value = "姓名", dataType = "String", paramType = "query")
    })
    public String sayHello(String name) {
        return "Hello," + name;
    }

}

(1) @Api:描述接口的作用,用于类层;(2) @ApiOperation:描述接口信息,用于方法层  (3) @ApiImplicitParams:用于介绍参数信息。

@ApiImplicitParams和@ApiParams的区别:前者一般用于请求url后直接带的参数,后者一般是用于以Body封装请求参数

servlet-mvc.xml

	<context:component-scan base-package="com.nieshk">
		<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController"/>
	</context:component-scan>

	<aop:aspectj-autoproxy/>
	<!-- enable autowire -->
	<context:annotation-config />

	<!-- 默认的注解映射的支持 -->
	<mvc:annotation-driven>
		<mvc:message-converters>
			<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
			<!--<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>-->
		</mvc:message-converters>
	</mvc:annotation-driven>

	<mvc:resources mapping="swagger-ui.html" location="classpath:/META-INF/resources/" />
	<mvc:resources mapping="/webjars/**" location="classpath:/META-INF/resources/webjars/" />

文件作用:针对每个Servlet进行配置的,因此它的配置是在servlet的配置中,配置使用的是init-param, 它的作用就是在servlet初始化的时候,加载配置信息,完成servlet的初始化操作

spring-core.xml

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="searchSystemEnvironment" value="true"/>
        <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
        <!--<property name="ignoreUnresolvablePlaceholders" value="true" />-->
    </bean>
    <!-- (Annotation方式配置services)enable component scanning (beware that this
        does not enable mapper scanning!) 扫描除controller之外的bean -->
    <context:component-scan base-package="com.nieshk" >
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <!-- enable autowire -->
    <context:annotation-config />

    <!-- enable transaction demarcation with annotations -->
    <tx:annotation-driven />

    <task:annotation-driven/>

文件作用:用来与容器间进行交互的接口的组合,也就是说,这个接口定义了一系列的方法,servlet通过这些方法可以很方便地与自己所在的容器进行一些交互。

由于笔者比较懒,剩下的web.xml和pom.xml不再进行累述。

三、运行效果

访问/swagger-ui.html出现以下页面

运行结果完美,输入参数,点击Try it out还能进行接口测试呢。

说好的原理详解呢?这难道只是挂羊头卖狗肉?老铁,说好的高逼格呢?说好的源码呢?

莫急莫急,接下来隆重介绍的就是Swagger2是怎么工作地。

四、原理详解

从SwaggerConfig.java中,可以看到在配置过程中使用了@Configuration、@EnableMvc、@EnableSwagger2三个注解。

1、 @Configuration:用于创建Bean对象、基于CGlib代理实现、用于全局配置。第一个和第三个好理解,关于CGlib代理可以参考文章《CGlib动态代理

2、 @EnableWebMvc:引入类DelegatingWebMvcConfiguration.java、通过@Bean注册了和<mvc:annotation-driven/>一样的组件、添加@Configuration类来导入SpringMvc的配置、实现WebMvcConfigConfigurer的联系。

这家伙可是启动时加载的,原因是:他继承了WebMvcConfigurationSupport,而后者实现了ApplicationContextAware接口(Spring容器会检测容器中的所有实现了ApplicationContextAware接口的Bean)。

关于@EnableWebMvc的详细介绍可以参考《详解@EnableWebMvc

3、@EnableSwagger2

开始之前,介绍几个注解功能:注解@Import,用来干嘛的呢?@Import用来导入@Configuration注解的配置类、声明@Bean注解的bean方法、导入ImportSelector的实现类或导入ImportBeanDefinitionRegistrar的实现类。@EnablePluginRegistries注解是spring-plugin模块提供的一个基于Plugin类型注册PluginRegistry实例到Spring上下文的注解,以下简称插件注册。

EnableSwagger2.java

@Import({Swagger2DocumentationConfiguration.class})
public @interface EnableSwagger2 {
}

可以看出,这个注解就一个作用,导入Swagger2DocumentationConfiguration.class,从类名可以知道,就是Swagger2生成接口文档的配置类嘛。

Swagger2DocumentationConfiguration.java

@Configuration
@Import({ SpringfoxWebMvcConfiguration.class, SwaggerCommonConfiguration.class })
@ComponentScan(basePackages = {
    "springfox.documentation.swagger2.readers.parameter",
    "springfox.documentation.swagger2.web",
    "springfox.documentation.swagger2.mappers"
})
public class Swagger2DocumentationConfiguration {
  @Bean
  public JacksonModuleRegistrar swagger2Module() {
    return new Swagger2JacksonModule();
  }
}
这个类有什么用呢?主要是以下几点:

(1) 声明了@Bean注解的swagger2Module()方法:返回Swagger2JacksonModule实例化对象,用于引入模块,对接口的各种属性进行转换,注册模块、安装模块,信息序列化等。

(2) 组件扫描:将指定路径下的包扫描,便于Spring在被指定的包及其下级包(sub packages)中寻找bean,这三个路径的Bean对象作用分别如下:

readers.parameter:获取@ApiParam注解的接口参数名称

web:提供获取json格式的接口文档信息

mappers:安全、许可证、参数序列化等

导入SwaggerCommonConfiguration.java

@Configuration
@ComponentScan(basePackages = {
    "springfox.documentation.swagger.schema",
    "springfox.documentation.swagger.readers",
    "springfox.documentation.swagger.web"
})
public class SwaggerCommonConfiguration {

  @Bean
  public static PropertySourcesPlaceholderConfigurer swaggerProperties() {
    PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
    propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(true);
    return propertySourcesPlaceholderConfigurer;
  }
}
这个文件主要干了这几件事:

(1) 声明@Bean注解的swaggerProperties()方法:实现对Classpath下的配置文件的注入

(2) 组件扫描:

schema:获取@ApiModel、@ApiModelProperty注解的接口数据模型信息,实现了ModelBuilderPlugin的接口方法

readers:获取@ApiOperation注解的接口操作信息,获取@ApiParam注解的参数信息,实现了ParameterBuilderPlugin、ExpandedParameterBuilderPlugin、OperationBuilderPlugin、OperationModelsProviderPlugin等接口方法。

web:获取@Api注解的接口类的信息,对RequestMapping信息进行资源分组,提供接口资源访问Controller,实现了ApiListingBuilderPlugin、ResourceGroupingStrategy的接口方法。

导入SpringfoxWebMvcConfiguration.java

@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",
    "springfox.documentation.spring.web.caching"
})
@EnablePluginRegistries({ DocumentationPlugin.class,
    ApiListingBuilderPlugin.class,
    OperationBuilderPlugin.class,
    ParameterBuilderPlugin.class,
    ExpandedParameterBuilderPlugin.class,
    ResourceGroupingStrategy.class,
    OperationModelsProviderPlugin.class,
    DefaultsProviderPlugin.class,
    PathDecorator.class
})
@EnableAspectJAutoProxy
public class SpringfoxWebMvcConfiguration {

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

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

  @Bean
  public static ObjectMapperConfigurer objectMapperConfigurer() {
    return new ObjectMapperConfigurer();
  }

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

}

这个文件干了这几件事:

(1) 声明@Bean注解方法:

defaults():接口的默认信息声明

resourceGroupCache():声明DocumentationCache,显而易见就是缓存的接口信息

objectMapperConfigurer:用于配置接口信息转换

jsonSerializer:json数据序列化

 

(2) 导入ModelsConfiguration.java

@Configuration
@ComponentScan(
    basePackages = {"springfox.documentation.schema"}
)
@EnablePluginRegistries({ModelBuilderPlugin.class, ModelPropertyBuilderPlugin.class, TypeNameProviderPlugin.class})
public class ModelsConfiguration {
    public ModelsConfiguration() {
    }

    @Bean
    public TypeResolver typeResolver() {
        return new TypeResolver();
    }
}

(2.1) 插件注册:注册了ModelBuilderPlugin、ModelPropertyBuilderPlugin、TypeNameProviderPlugin这些插件

(2.2)  声明TypeResolver类型解析器方法

 

(3) 组件扫描

scanners:接口信息扫描

readers.operation:操作读取工具(模型、标签、响应等)

readers.parameter:参数读取(类型、模型、属性等)

plugins:Docket、接口选择器、DocumentationPlugins(用于管理接口信息注册的插件)、DocumentationPluginsBootstrapper(用于项目启动时加载接口文档配置信息)

paths:接口路径提供者、装饰器与适配器

caching:缓存切面,用于缓存接口信息

 

(4) 插件注册(一系列继承Plugin的接口)

(4.1) DocumentationPlugin:用于配置接口信息

boolean isEnabled():控制插件是否激活

DocumentationType getDocumentationType():获取文档类型

DocumentationContext configure():根据DocumentationContextBuilder创建文档内容

String getGroupName():获取插件分组信息

 

(4.2) ApiListingBuilderPlugin

void apply(ApiListingContext):实现接口方法,通过ApiListingBuilder覆盖ApiListing中属性

实现类:

ApiListingTagReader:获取@Api的tag信息、描述等

MediaTypeReader:获取@RequestMapping信息复制的consumes、produces

 

(4.3) OperationBuilderPlugin

void apply(OperationContext):实现接口方法,通过OperationBuilder重写Operation信息

实现类:

从插件的实现类中可以发现,这个插件就是为了读取接口的各种信息:请求方法、参数、描述、响应等一系列信息,获取请求参数时,主要是获取@ApiImplicitParam注解的参数信息

 

(4.4) ParameterBuilderPlugin

void apply(ParameterContext):获取输入参数和返回值,ParameterContext能用于重写参数属性

实现类:

实现类主要是获取参数的权限、数据类型、描述、名称、是否必须等信息,主要是获取@ApiParam注解的参数。

 

(4.5) ExpandedParameterBuilderPlugin:扩展接口参数的一些功能,比如判断这个参数的数据类型以及是否为这个接口的必须参数

void apply(ParameterExpansionContext):通过context中的ParameterBuilder重写参数

实现类:

实现类主要是获取扩展参数信息。

 

(4.6) ResourceGroupingStrategy

Set<ResourceGroup> getResourceGroups():根据请求路由与处理方法对请求参数进行分组,也就是根据@RequestMapping注解值进行分组。例如:TestController中@RequestMapping("/test),Test2Controller中@RequestMapping("/test)为同一个组;

String getResourceDescription():获取请求的描述

Integer getResourcePosition():获取资源位置

实现类:

ClassOrApiAnnotationResourceGrouping.java用于对@Api注解资源进行分组

SpringGroupingStrategy.java 用于对@RequestMapping注解资源进行分组

 

(4.7) OperationModelsProviderPlugin

void apply(RequestMappingContext):获取输入参数和返回值,RequestMappingContext能用于重写参数属性

实现类:

OperationModelsProvider.java 用于获取@RequestBody和@RequestPart注解的请求参数

SwaggerOperationModelsProvider.java 用于获取@ApiResponses和@ApiResponse注解的返回参数。

 

(4.8) DefaultsProviderPlugin

DocumentationContextBuilder create(DocumentationType):根据文档类型构造一个默认的文档内容构造器,如下:

  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);
  }

 

(4.9) PathDecorator

Function<String,String> decorator(PathContext):对RequestMappingContext进行转换

实现类:

以上就是@EnableSwagger2注解之后所引入的配置与工作。

 

五、 加载过程

       了解了Swagger2配置原理之后,咱们再深入了解一下这些配置是怎么加载并访问的吧。在Spring中,ApplicationContext事件机制是观察者设计模式的实现,通过ApplicationEvent类和ApplicationListener接口,可以实现ApplicationContext事件处理。如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean将自动被触发。这种事件机制都必须需要程序显示的触发。简而言之,当一个类实现了ApplicationListener接口,那么会在启动时加载。

       在Swagger2的源码里,有一个类DocumentationPluginsBootstrapper.java,这个类干了啥事呢?实现了ApplicationListener接口,启动时会触发onApplicationEvent() 方法,从而加载了Swagger的配置信息与接口信息。

(1) 项目启动时,基于Plugin类型注册PluginRegistry实例到Spring上下文的注解(插件注册)

(2) 调用DocumentationPluginsManager.documentationPlugins()方法获取注册的插件

(3) 遍历获取到的插件,创建DocumentationContextBuilder对象,创建DocumentationContext上下文,调用方法ApiDocumentationScanner.scan()获取Documentation并添加到DocumentationCache中

(4) ApiDocumentationScanner.scan()方法中,主要作用如下:

(5) 启动过程中将接口信息缓存在DocumentationCache后,访问/v2/api-docs接口

(6) 获取到缓存的Documentation信息

(7) 调用ServiceModelToSwagger2Mapper.mapDocumentation(documentation)将Documentation转换为Swagger对象

(8) 调用JsonSerializer.toJson()方法将Swagger对象转换为Json,返回Json格式的接口文档信息。

 

 

 

 

 

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值