美文网首页
Spring Boot 2.x 接口多版本(二)-- 整合Swa

Spring Boot 2.x 接口多版本(二)-- 整合Swa

作者: 千年的心 | 来源:发表于2020-07-21 10:16 被阅读0次

在上篇文章Spring Boot 2.x 接口多版本中遗留了一个问题----和Swagger整合。和网上大多解决方案一致,我们也使用预先定义版本的方式。与网上不同的是,我们可以通过自定义的版本枚举自动注册Docket,而不用每一个都单独写。我们直接来看实现。
1.定义版本枚举

package com.yugioh.api.common.core.version;

/**
 * @author lieber
 */
public enum Versions {

    /**
     * 默认版本号
     */
    DEFAULT("1.0.0"),

    /**
     * 1.0.1
     */
    ONE_ZERO_ONE("1.0.1");

    private String value;

    Versions(String value) {
        this.value = value;
    }

    public String value() {
        return value;
    }
}

2.修改接口版本注解,版本使用枚举定义

package com.yugioh.api.common.core.version;

import org.springframework.web.bind.annotation.Mapping;

import java.lang.annotation.*;

/**
 * 接口版本
 *
 * @author lieber
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface ApiVersion {

    Versions[] value() default Versions.DEFAULT;

}

3.动态注册Docket

package com.yugioh.api.common.core.config;

import com.yugioh.api.common.core.version.ApiVersion;
import com.yugioh.api.common.core.version.Versions;
import com.yugioh.api.common.shiro.config.TokenConfig;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.paths.RelativePathProvider;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;

/**
 * Swagger配置
 *
 * @author lieber
 */
@Configuration
@EnableSwagger2
@Data
@ConfigurationProperties(prefix = "api.config.swagger", ignoreInvalidFields = true)
public class SwaggerConfig {

    private String title;

    private String description;

    private String version;

    private String name;

    private String url;

    private String email;

    private final TokenConfig tokenConfig;

    private final ApplicationContext applicationContext;

    @Autowired
    public SwaggerConfig(TokenConfig tokenConfig, ApplicationContext applicationContext) {
        this.tokenConfig = tokenConfig;
        this.applicationContext = applicationContext;
    }

    @PostConstruct
    public void init() {
        // Bean初始化完成后执行
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        ServletContext servletContext = applicationContext.getBean(ServletContext.class);
        SwaggerConfig config = applicationContext.getBean(SwaggerConfig.class);
        // 定义Bean
        for (Versions versions : Versions.values()) {
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(Docket.class);
            beanDefinitionBuilder.addConstructorArgValue(DocumentationType.SWAGGER_2);
            beanFactory.registerBeanDefinition(versions.name(), beanDefinitionBuilder.getBeanDefinition());
        }
        // 取出Bean并且对属性重新赋值
        for (Versions versions : Versions.values()) {
            Docket docket = (Docket) this.applicationContext.getBean(versions.name());
            if (docket != null) {
                this.create(docket, versions, config, servletContext);
            }
        }
    }


    private void create(Docket docket, Versions versions, SwaggerConfig config, ServletContext servletContext) {
        docket.apiInfo(config.apiInfo())
                .pathProvider(new RelativePathProvider(servletContext) {
                    @Override
                    public String getOperationPath(String operationPath) {
                        return operationPath.replaceAll("\\{version}", String.format("v%s", versions.value()));
                    }
                })
                .groupName(versions.value())
                .select()
                .apis(handler -> {
                    if (handler == null) {
                        return false;
                    }
                    // 判断方法上
                    ApiVersion annotationOnMethod = handler.getHandlerMethod().getMethodAnnotation(ApiVersion.class);
                    if (annotationOnMethod != null && annotationOnMethod.value().length > 0) {
                        for (Versions v : annotationOnMethod.value()) {
                            if (v == versions) {
                                return true;
                            }
                        }
                        // 如果方法头上有,那么不用判断类上了
                        return false;
                    }
                    // 判断类上
                    ApiVersion annotationOnClass = handler.getHandlerMethod().getBeanType().getAnnotation(ApiVersion.class);
                    if (annotationOnClass != null && annotationOnClass.value().length > 0) {
                        for (Versions v : annotationOnClass.value()) {
                            if (v == versions) {
                                return true;
                            }
                        }
                    }
                    return false;
                })
                .paths(PathSelectors.any())
                .build();
    }


    private ApiInfo apiInfo() {
        Contact contact = new Contact(name, url, email);
        return new ApiInfoBuilder()
                .title(title)
                .description(description)
                .contact(contact)
                .version(version)
                .build();
    }
}

以上就可以完成和Swagger的整合
-----------------------------------------------------------------手动分割线-----------------------------------------------------------------
下面说一下Swagger的原理。从Docket作为切入点。
步骤如下
1.通过Docket找到调用方Swagger2Controller代码


image.png

2.通过Swagger2Controller代码找到DocumentationCache发现所有路由是分组存在Map中的,那是什么时候怎么存进去的呢?继续看。
3.通过DocumentationCache的addDocumentation方法,我们找到了DocumentationPluginsBootstrapper,发现其实现了SmartLifecycle,那么会在Bean初始化完成后执行start方法


image.png
4.通过start方法,我们可以找到DocumentationPluginsManager类。
image.png
至此一切清晰明了了,在Bean初始化完成后,执行DocumentationPluginsBootstrapper的start方法,找到所有的注册DocumentationPlugin(实现类就是Docket),然后扫描文档存入DocumentationCache。

相关文章

网友评论

      本文标题:Spring Boot 2.x 接口多版本(二)-- 整合Swa

      本文链接:https://www.haomeiwen.com/subject/iwffhktx.html