zoukankan      html  css  js  c++  java
  • 多应用下 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;
        }
    }

    效果如下:

    实体注解实体注解
    接口描述接口描述
    接口参数接口参数
    执行接口执行接口
    返回地址返回地址
    条件装配 @Conditional 介绍

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

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPEElementType.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扩展@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-packagecom.springcloud.study.user.controller

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

    后续的规划

    1. 注解整理
      后续会将 Spring 注解进行一个统一的整理,包含一些使用说明或者原理等等,希望到时候能帮助到大家吧,目前计划两周一个吧;
    2. 开源项目
      Spring Cloud 的学习过于碎片化,希望通过自己搞一个开源项目,提升对各个组件的掌握能力,同时也能产出一套通用化权限管理系统,具备很高的灵活性、扩展性和高可用性,并且简单易用,这块是和未来做企业数字化转型相关的事是重合的,慢慢的会做一些企业级通用化的的功能开发;前端部分的话希望是采用Vue,但是这块有一个学习成本,还没有进行研究,目前还没排上日程。整体的里程碑是希望在6.22离职之前完成整套后端的开发,7月中旬完成第一次Commit。

    点点关注

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

    点关注
  • 相关阅读:
    Apache 配置多个HTTPS站点(转载)
    一个显示某段时间内每个月的方法 返回由这些月 (转载)
    支付宝支付出现 openssl_sign(): supplied key param cannot be coerced into a private key
    tp5 setInc 中一直返回 0
    数据库 SQLSTATE[22003]: Numeric value out of range: 1264 Out of range value for col
    MYSQL设置查询内存表大小
    PHP原生的mysql查询
    支付宝 APP支付 错误码
    centos6.5中安装完成扩展后 在modules 也能找到 但是在phpinfo中看不见
    configure: error: Cannot find php-config. Please use --with-php-config=PATH
  • 原文地址:https://www.cnblogs.com/wtzbk/p/13121209.html
Copyright © 2011-2022 走看看