问题

微服务化的时代,我们整个项目工程下面都会有很多的子系统,对于每个应用都有暴露 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;
    }
}

效果如下:

实体注解
接口描述
接口参数
执行接口
返回地址

条件装配 @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相关的所有注解

@Conditional扩展
注解介绍

配置文件参数获取 @ConfigurationProperties

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

比较好的方式

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

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

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


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。

点点关注

这边文章限于篇幅,过多的关注于使用了,后续会把上面几个注解的原理分析讲讲,欢迎大家点点关注,点点赞,感谢!

多应用下 Swagger 的使用,这可能是最好的方式!的更多相关文章

  1. HTTP协议下保证登录密码不被获取最健壮方式

    原文:http://www.cnblogs.com/intsmaze/p/6009648.html HTTP协议下保证登录密码不被获取最健壮方式   说到在http协议下用户登录如何保证密码安全这个问 ...

  2. .Net core 下Swagger如何隐藏接口的显示

    Swagger是这个非常强大的api文档工具,通常可以用来测试接口,和查看接口,就像这样: 非常的好用和快捷,这是一个小小的demo,我们在完成系统时,发布后,外部依旧可以用/swagger访问到这个 ...

  3. 魔改swagger:knife4j的另外一种打开方式

    之前公司使用了swagger作为文档管理工具,原生的swagger-ui非常丑,之后就用了开源项目 萧明 / knife4j 的swagger组件进行了swagger渲染,改造之后界面漂亮多了,操作也 ...

  4. HTTP协议下保证登录密码不被获取更健壮方式

    说到在http协议下用户登录如何保证密码安全这个问题:    小白可能第一想法就是,用户在登录页面输入密码进行登录时,前台页面对用户输入的密码进行加密,然后把加密后的密码作为http请求参数通过网络发 ...

  5. virtualbox下centos虚拟机安装,并网卡配置桥接方式上网,使得和host可以互Ping通。

    见:http://www.cnblogs.com/taoshiqian/p/7615993.html 注意: 1.host 主机什么都不要处理 2.将virtualbox 的对应虚拟机网络设置桥接 3 ...

  6. WPF 下两种图片合成或加水印的方式(转载)

    来源:http://www.cnblogs.com/lxblog/ 最近项目中应用多次应用了图片合成,为了今后方便特此记下. 在WPF下有两种图片合成的方式,一种还是用原来C#提供的GDI+方式,命名 ...

  7. ArcGIS下图层范围不正确的两种处理方式

    ArcGIS下图层范围不正确,偶尔能碰上这种情况,主要表现为“缩放至图层”时,其显示范围与该图层内所有要素的外包围盒范围不一致.针对这个问题,有两种解决办法. 方法一:导出数据.新创建含有要素的Sha ...

  8. MAC下安装多版本JDK和切换几种方式

    环境: MAC AIR,OS X 10.10,64位   历史: 过去 Mac 上的 Java 都是由 Apple 自己提供,只支持到 Java 6,并且OS X 10.7 开始系统并不自带(而是可选 ...

  9. 破解windows下MySQL服务启动不了的情况下不能对其进行全然卸载的解决方式

    下面的文章主要介绍的是在MySQL服务启动不了的情况下,不能对其进行全然卸载的实际解决的方法的描写叙述,下面就是对解决MySQL服务启动不了的情况下详细方案的描写叙述,希望在你今后的学习中会对你有所帮 ...

随机推荐

  1. 解析webpack插件html-webpack-plugin

    前言: 本文将分为基本概念.基础使用.模块的运用(问题解决)来进行阐述. 一.基本概念 我们为什么会需要HtmlWebpackPlugin插件? 在真实发布项目时,发布的是dist文件夹中的内容,但是 ...

  2. eclipse——管理远程资源的缓存,例如从Internet下载的资源。

    原文:Manage the cache of remote resources,such as those downloaded from the internet.

  3. pytest跳过指定的测试或模块

    参考Allure官方文档,pytest官方文档 实现setup/teardown 1.运行带指定标记的测试 @pytest.mark.tags ,这里的tags可以自定义 命令行执行:pytest - ...

  4. JVM调优总结(六)-新一代的垃圾回收算法

    垃圾回收的瓶颈 传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用带来的负担降到了最小,把应用的吞吐量推到了一个极限.但是他无法解决的一个问题,就是Full GC所带来的应用暂停.在一些对实时性要 ...

  5. tmux简单使用

    tmux简单使用 Tmux ("Terminal Multiplexer"的简称), 是一款优秀的终端复用软件,类似 GNU screen,但比screen更出色.tmux来自于O ...

  6. 如何基于 echarts 在柱状图或条形图上实现转换率?(有想法吗?)

    目录 需求 探索一 探索二 探索三 转换实践思路1 转换实践思路2 其他思路 探索四(揭晓答案) 答案篇说明 backgroundColor 用法 双柱合一 始终在轴的中间 百分在变,但是距离轴的距离 ...

  7. Chisel3 - 复合数据类型

    https://mp.weixin.qq.com/s/rXYqiZKuBpAYL8R94zxgRA   Chisel允许用户根据需要,把基本数据类型组合成为复合数据类型使用.如C语言里面的结构体,这样 ...

  8. ffmpeg转码步骤源码实现的一点点浅析

    ffmpeg转码实现的一点点浅析 ffmpeg转码过程对解码的处理封装在process_input()中(process_input()->decode_video()->decode() ...

  9. 【Flume】知识总结

    Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集.聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于收集数据:同时,Flume提供对数据进行简单处理,并 ...

  10. Java实现蓝桥杯十六进制转八进制

    基础练习 十六进制转八进制 时间限制:1.0s 内存限制:512.0MB 提交此题 锦囊1 锦囊2 问题描述 给定n个十六进制正整数,输出它们对应的八进制数. 输入格式 输入的第一行为一个正整数n ( ...