Spring Boot 2.x 接口多版本(二)-- 整合Swa
2020-07-21 本文已影响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代码

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

4.通过start方法,我们可以找到DocumentationPluginsManager类。

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