问题

微服务化的时代,我们整个项目工程下面都会有很多的子系统,对于每个应用都有暴露 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. 【python爬虫】scrapy入门7:Scrapy中runspider和crawl的区别

    runspider:不依赖创建项目 命令:scrapy runspider myspider.py  等同于  pyhton myspider.py crawl:使用spider进行爬取,依赖项目创建 ...

  2. .NET CORE 依赖注入 实践总结

    知识点回顾 依赖包. Microsoft.Extensions.DependencyInjection.Abstractions 核心对象和方法. IServiceCollection.注入对象的容器 ...

  3. 【补充说明】Gauge框架在JS中的简单应用

    这里做一个总结 由于公司架构要用node来替代Java的部分服务,所以就研究了这个自动化测试框架:它可以很方便的测试我们的接口,而且还能使用断言[assert]来判断是否是我们预期的结果. 但是呢,由 ...

  4. Spring Cloud 学习笔记一

    一.spring cloud 搭建注册中心(Eureka server) 1.spring cloud中提供了多种分步式服务组件,其都依赖于注册中心(eureka),注册中心的服务者与发现者都通过Eu ...

  5. Shell编程案例:修改运维脚本输出效果

    1. 需求:每日运维检查脚本dailymonitor.sh显示对服务器测试结果,其中命令 zabbix_get -s 192.168.111.21 -p 10050 -k "net.tcp. ...

  6. Bootstrap解决页面缩小变形的办法

    bootstrap布局是应用得很广泛的一种网页布局方法,例如:我们用一种中间内容很流行的布局分布:3-6-3式布局.代码如下 <style type="text/css"&g ...

  7. MAVEN添加本地仓库和注意事项!

    将jer包加载本地仓库导命令 注意:电脑配置了maven的环境变量, 安装指定文件到本地仓库命令:mvn install:install-file -Dfile=       : 指定jar文件路径与 ...

  8. SpringBoot学习笔记(十五:OAuth2 )

    @ 目录 一.OAuth 简介 1.什么是OAuth 2.OAuth 角色 3.OAuth 授权流程 4.OAuth授权模式 4.1.授权码 4.2.隐藏式 4.3.密码式 4.4.凭证式 二.实践 ...

  9. Java实现 LeetCode 554 砖墙(缝隙可以放在数组?)

    554. 砖墙 你的面前有一堵方形的.由多行砖块组成的砖墙. 这些砖块高度相同但是宽度不同.你现在要画一条自顶向下的.穿过最少砖块的垂线. 砖墙由行的列表表示. 每一行都是一个代表从左至右每块砖的宽度 ...

  10. 第五届蓝桥杯JavaA组省赛真题

    解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论 题目1.猜年龄 题目描述 小明带两个妹妹参加元宵灯会.别人问她们多大了,她们调皮地说:"我们俩的年龄之积是年龄之和的6倍" ...