多应用下 Swagger 的使用,这可能是最好的方式!

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 1. 单应用下 Swagger 的集成与使用2. 条件装配 @Conditional 介绍3. 配置文件参数获取 @ConfigurationProperties单体应用下 Swagger 集成与使用

一、问题


  微服务化的时代,我们整个项目工程下面都会有很多的子系统,对于每个应用都有暴露 Api 接口文档需要,这个时候我们就会想到 Swagger 这个优秀 jar 包。但是我们会遇到这样的问题,假如说我们有5个应用,难道说我们每个模块下面都要去引入这个 jar 包吗?我作为一个比较懒的程序感觉这样好麻烦,于是乎我思考了一种我认为比较好的方式,如果大家觉得有什么不太好的地方希望指正,谢谢!


二、基础


开始之前大家首先要了解一些基础,主要有以下几个方面:

  1. 单应用下 Swagger 的集成与使用
  2. 条件装配 @Conditional 介绍
  3. 配置文件参数获取 @ConfigurationProperties
单体应用下 Swagger 集成与使用

关于这部分从3方面讲起分别是:什么是、为什么、如何用

什么是 Swagger ?

Swagger 是一个规范且完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。

为什么使用 Swagger ?

主要的优点:

  1. 支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。
  2. 提供 Web 页面在线测试 API:光有文档还不够,Swagger 生成的文档还支持在线测试。参数和格式都定好了,直接在界面上输入参数对应的值即可在线测试接口。

缺点的话就是但凡引入一个 jar 需要去了解下原理和使用,对于这个缺点我感觉相比于优点就是大巫见小巫,我简单看了一下源码,其实不算太难。

如何使用 Swagger

关于 Swagger 的使用其实也就是3板斧,大家一定很熟悉的;

第一板斧就是引入 jar 包,这里我使用的是2.9.2版本

<!-- swagger 相关 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger2.version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${swagger2.version}</version>
        </dependency>

第二板斧就是SpringBoot自动扫描配置类

/**
 * SwaggerConfig
 *
 * @author wangtongzhou
 * @since 2020-06-09 09:41
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                //生产环境的时候关闭 Swagger 比较安全
                .apiInfo(apiInfo())
                .select()
                //Api扫描目录
                .apis(RequestHandlerSelectors.basePackage("com.springboot2.learning"))
                .paths(PathSelectors.any())
                .build();
    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("learn")
                .description("learn")
                .version("1.0")
                .build();
    }
}

第三板斧使用 Swagger 注解

/**
 * 用户相关接口
 *
 * @author wangtongzhou
 * @since 2020-06-12 07:35
 */
@RestController
@RequestMapping("/user")
@Api(value = "用户相关接口")
public class UserController {
    @PostMapping("/")
    @ApiOperation("添加用户的接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "userName", value = "用户名", defaultValue =
                    "wtz"),
            @ApiImplicitParam(name = "age", value = "年龄", defaultValue = "20")
    })
    public User addUser(String userName, Integer age) {
        User user = new User();
        user.setAge(age);
        user.setUserName(userName);
        return user;
    }
    @GetMapping("/{userId}")
    @ApiOperation("根据用户id查询用户信息")
    @ApiImplicitParam(name = "userId", value = "用户id", defaultValue = "20")
    public User queryUserByUserId(@PathVariable Long userId) {
        User user = new User();
        user.setUserId(userId);
        return user;
    }
}
/**
 * 用户实体
 *
 * @author wangtongzhou
 * @since 2020-06-12 07:45
 */
@ApiModel
public class User {
    @ApiModelProperty(value = "用户名称")
    private String userName;
    @ApiModelProperty(value = "年龄")
    private Integer age;
    @ApiModelProperty(value = "用户id")
    private Long userId;
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Long getUserId() {
        return userId;
    }
    public void setUserId(Long userId) {
        this.userId = userId;
    }
}

效果如下:

1005447-20200612203949709-590754691.png

实体注解

1005447-20200612204034688-186631023.png

接口描述

1005447-20200612204120336-1887185053.png

接口参数

1005447-20200612204120336-1887185053.png

执行接口

1005447-20200612204224969-1285548421.png

返回地址

条件装配 @Conditional 介绍

@Conditional 是Spring4.0提供的注解,位于 org.springframework.context.annotation 包内,它可以根据代码中设置的条件装载不同的bean。比如说当一个接口有两个实现类时,我们要把这个接口交给Spring管理时通常会只选择实现其中一个实现类,这个时候我们总不能使用if-else吧,所以这个@Conditional的注解就出现了。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
    Class<? extends Condition>[] value();
}
使用方法

这里介绍一个MySQL和Oracle选择方式,开始之前首先在properties文件中增加sql.name=mysql的配置,接下来步骤如下

  1. 实现Conditional接口, 实现matches方法
/**
 * mysql条件装配
 *
 * @author wangtongzhou
 * @since 2020-06-13 08:01
 */
public class MysqlConditional implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String sqlName = context.getEnvironment().getProperty("sql.name");
        if ("mysql".equals(sqlName)){
            return true;
        }
        return false;
    }
}
/**
 * oracle条件装配
 *
 * @author wangtongzhou
 * @since 2020-06-13 08:02
 */
public class OracleConditional implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String sqlName=context.getEnvironment().getProperty("sql.name");
        if ("oracle".equals(sqlName)){
            return true;
        }
        return false;
    }
}
  1. 在需要判断条件的bean上,加上@Conditional(***.class)即可在满足条件的时候加载对应的类
/**
 * conditional
 *
 * @author wangtongzhou
 * @since 2020-06-13 08:01
 */
@Configuration
public class ConditionalConfig {
    @Bean
    @Conditional(MysqlConditional.class)
    public Mysql mysql() {
        return new Mysql();
    }
    @Bean
    @Conditional(OracleConditional.class)
    public Oracle oracle() {
        return new Oracle();
    }
}
  1. 调用测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class ConditionalTests {
    @Autowired
    private ApplicationContext applicationContext;
    @Test
    public void test_conditional() {
        Mysql mysql = (Mysql) applicationContext.getBean("mysql");
        Assert.assertNotNull(mysql);
        Assert.assertTrue("mysql".equals(mysql.getSqlName()));
    }
}
其他扩展注解
  1. @@ConditionalOnClass({Docket.class, ApiInfoBuilder.class})
    当存在Docket和ApiInfoBuilder类的时候才加载Bean;
  2. @ConditionalOnMissingClass不存在某个类的时候才会实例化Bean;
  3. @ConditionalOnProperty(prefix = "swagger", value = "enable", matchIfMissing = true)当存在swagger为前缀的属性,才会实例化Bean;
  4. @ConditionalOnMissingBean当不存在某个Bean的时候才会实例化;

这里就介绍这几个常用,org.springframework.boot.autoconfigure.condition这个下面包含全部的关于@Conditional相关的所有注解

1005447-20200613161307864-1188727482.png

@Conditional扩展

1005447-20200613161637204-614640467.png

注解介绍

配置文件参数获取 @ConfigurationProperties

@ConfigurationProperties是SpringBoot加入的注解,主要用于配置文件中的指定键值对映射到一个Java实体类上。关于这个的使用就在下面的方式引出。


三、比较好的方式


关于开篇中引入的问题,解题流程主要是以下3步:

  1. 抽象一个公共 Swagger jar;
  2. 如何定制化 Swagger jar;
  3. 使用定制化完成以后的 Swagger jar;
抽象一个公共 Swagger jar

主要是就是将 Swagger jar 和一些其他需要的 jar 进行引入,使其成为一个公共的模块,软件工程中把这个叫做单一原则;

1005447-20200613170541538-1679626274.png

Maven包的引入

 

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
    </dependencies>
如何定制化 Swagger jar;

定制化就是将 Swagger 相关的属性进行配置化的处理,这里也可以分为两步;

  1. 将公共的属性抽象成配置化的类,这里就是关于@ConfigurationProperties的使用,将配置文件中的 swagger 开头的属性映射到配置类的属性当中;
/**
 * Swagger基本属性
 *
 * @author wangtongzhou
 * @since 2020-05-24 16:58
 */
@ConfigurationProperties("swagger")
public class SwaggerProperties {
    /**
     * 子系统
     */
    private String title;
    /**
     * 描述
     */
    private String description;
    /**
     * 版本号
     */
    private String version;
    /**
     * api包路径
     */
    private String basePackage;
    public String getTitle() {
        return title;
    }
    public SwaggerProperties setTitle(String title) {
        this.title = title;
        return this;
    }
    public String getDescription() {
        return description;
    }
    public SwaggerProperties setDescription(String description) {
        this.description = description;
        return this;
    }
    public String getVersion() {
        return version;
    }
    public SwaggerProperties setVersion(String version) {
        this.version = version;
        return this;
    }
    public String getBasePackage() {
        return basePackage;
    }
    public SwaggerProperties setBasePackage(String basePackage) {
        this.basePackage = basePackage;
        return this;
    }
}
  1. 公共属性赋值配置到 Swagger 的配置类中,该配置类中进行一些类条件的判断和插件Bean是否已经注入过,然后就是将配置类中的属性,赋值到 Swagger 的初始化工程中;
/**
 * Swagger自动配置类
 *
 * @author wangtongzhou
 * @since 2020-05-24 16:35
 */
@Configuration
@EnableSwagger2
@ConditionalOnClass({Docket.class, ApiInfoBuilder.class})
@ConditionalOnProperty(prefix = "swagger", value = "enable", matchIfMissing = true)
@EnableConfigurationProperties(SwaggerProperties.class)
public class SwaggerConfig {
    @Bean
    @ConditionalOnMissingBean
    public SwaggerProperties swaggerProperties() {
        return new SwaggerProperties();
    }
    @Bean
    public Docket createRestApi() {
        SwaggerProperties properties = swaggerProperties();
        return new Docket(DocumentationType.SWAGGER_2)
                //生产环境的时候关闭 Swagger 比较安全
                .apiInfo(apiInfo(properties))
                .select()
                //Api扫描目录
                .apis(RequestHandlerSelectors.basePackage(properties.getBasePackage()))
                .paths(PathSelectors.any())
                .build();
    }
    private ApiInfo apiInfo(SwaggerProperties properties) {
        return new ApiInfoBuilder()
                .title(properties.getTitle())
                .description(properties.getDescription())
                .version(properties.getVersion())
                .build();
    }
}

完成以上两步,就完成了 Swagger 模块的定制化开发,接下来还要做一件事情,作为一个公共的模块,我们要让他自己进行自动化装配,解放我们的双手,我们在 resources 目录下增加一个 spring.factories 配置文件,内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.springcloud.study.swagger.config.SwaggerConfig

到此我们完成所有的开发;

使用定制化完成以后的 Swagger jar;

关于使用也分为两步,

  1. 引入 jar;
<dependency>
            <groupId>com.springcloud.study</groupId>
            <artifactId>common-swagger</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
  1. 定制化的属性配置;
# Swagger 配置项
swagger:
  title: 用户模块
  description: 用户子系统
  version: 1.0.0
  base-package: com.springcloud.study.user.controller

完成这两步就可以开启正常的使用了;


四、后续的规划


  1. 注解整理
    后续会将 Spring 注解进行一个统一的整理,包含一些使用说明或者原理等等,希望到时候能帮助到大家吧,目前计划两周一个吧;
  2. 开源项目
    Spring Cloud 的学习过于碎片化,希望通过自己搞一个开源项目,提升对各个组件的掌握能力,同时也能产出一套通用化权限管理系统,具备很高的灵活性、扩展性和高可用性,并且简单易用,这块是和未来做企业数字化转型相关的事是重合的,慢慢的会做一些企业级通用化的的功能开发;前端部分的话希望是采用Vue,但是这块有一个学习成本,还没有进行研究,目前还没排上日程。整体的里程碑是希望在6.22离职之前完成整套后端的开发,7月中旬完成第一次Commit。
相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
7月前
|
Java 应用服务中间件 网络安全
Nginx配置静态页面+springboot应用+swagger+SSL的实现
Nginx配置静态页面+springboot应用+swagger+SSL的实现
266 0
|
开发框架 中间件 .NET
Swashbuckle源码应用之最后一次修改Swagger中OpenApi.json机会
Swashbuckle源码应用之最后一次修改Swagger中OpenApi.json机会
111 0
|
29天前
|
Java 测试技术 API
详解Swagger:Spring Boot中的API文档生成与测试工具
详解Swagger:Spring Boot中的API文档生成与测试工具
38 4
|
5月前
|
数据可视化 Java API
Spring Boot与Swagger的集成
Spring Boot与Swagger的集成
|
5月前
|
Java API 开发者
在Spring Boot中集成Swagger API文档
在Spring Boot中集成Swagger API文档
|
2月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
79 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
2月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
245 1
|
3月前
|
前端开发 Java Spring
【非降版本解决】高版本Spring boot Swagger 报错解决方案
【非降版本解决】高版本Spring boot Swagger 报错解决方案
108 2
|
3月前
|
Java Spring
springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
本文介绍了如何在Spring Boot项目中集成Swagger 2.x和3.0版本,并提供了解决Swagger在Spring Boot中启动失败问题“Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerEx”的方法,包括配置yml文件和Spring Boot版本的降级。
springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
|
4月前
|
Java API Spring
springboot集成swagger
这篇文章介绍了如何在Spring Boot项目中集成Swagger 2.10.0来生成API文档,包括添加依赖、编写配置类、创建接口文档,并使用Knife4j美化Swagger界面。