多应用下 Swagger 的使用,这可能是最好的方式!
问题
微服务化的时代,我们整个项目工程下面都会有很多的子系统,对于每个应用都有暴露 Api 接口文档需要,这个时候我们就会想到 Swagger 这个优秀 jar 包。但是我们会遇到这样的问题,假如说我们有5个应用,难道说我们每个模块下面都要去引入这个 jar 包吗?我作为一个比较懒的程序感觉这样好麻烦,于是乎我思考了一种我认为比较好的方式,如果大家觉得有什么不太好的地方希望指正,谢谢!
基础
开始之前大家首先要了解一些基础,主要有以下几个方面:
- 单应用下 Swagger 的集成与使用
- 条件装配 @Conditional 介绍
- 配置文件参数获取 @ConfigurationProperties
单体应用下 Swagger 集成与使用
关于这部分从3方面讲起分别是:什么是、为什么、如何用
什么是 Swagger ?
Swagger 是一个规范且完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。
为什么使用 Swagger ?
主要的优点:
- 支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。
- 提供 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的配置,接下来步骤如下
- 实现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;
}
}
- 在需要判断条件的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();
}
}
- 调用测试
@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()));
}
}
其他扩展注解
- @@ConditionalOnClass({Docket.class, ApiInfoBuilder.class})
当存在Docket和ApiInfoBuilder类的时候才加载Bean; - @ConditionalOnMissingClass不存在某个类的时候才会实例化Bean;
- @ConditionalOnProperty(prefix = "swagger", value = "enable", matchIfMissing = true)当存在swagger为前缀的属性,才会实例化Bean;
- @ConditionalOnMissingBean当不存在某个Bean的时候才会实例化;
这里就介绍这几个常用,org.springframework.boot.autoconfigure.condition这个下面包含全部的关于@Conditional相关的所有注解
@Conditional扩展
注解介绍
配置文件参数获取 @ConfigurationProperties
@ConfigurationProperties是SpringBoot加入的注解,主要用于配置文件中的指定键值对映射到一个Java实体类上。关于这个的使用就在下面的方式引出。
比较好的方式
关于开篇中引入的问题,解题流程主要是以下3步:
- 抽象一个公共 Swagger jar;
- 如何定制化 Swagger jar;
- 使用定制化完成以后的 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 相关的属性进行配置化的处理,这里也可以分为两步;
- 将公共的属性抽象成配置化的类,这里就是关于@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;
}
}
- 公共属性赋值配置到 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;
关于使用也分为两步,
- 引入 jar;
<dependency>
<groupId>com.springcloud.study</groupId>
<artifactId>common-swagger</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 定制化的属性配置;
# Swagger 配置项
swagger:
title: 用户模块
description: 用户子系统
version: 1.0.0
base-package: com.springcloud.study.user.controller
完成这两步就可以开启正常的使用了;
后续的规划
- 注解整理
后续会将 Spring 注解进行一个统一的整理,包含一些使用说明或者原理等等,希望到时候能帮助到大家吧,目前计划两周一个吧; - 开源项目
Spring Cloud 的学习过于碎片化,希望通过自己搞一个开源项目,提升对各个组件的掌握能力,同时也能产出一套通用化权限管理系统,具备很高的灵活性、扩展性和高可用性,并且简单易用,这块是和未来做企业数字化转型相关的事是重合的,慢慢的会做一些企业级通用化的的功能开发;前端部分的话希望是采用Vue,但是这块有一个学习成本,还没有进行研究,目前还没排上日程。整体的里程碑是希望在6.22离职之前完成整套后端的开发,7月中旬完成第一次Commit。
点点关注
这边文章限于篇幅,过多的关注于使用了,后续会把上面几个注解的原理分析讲讲,欢迎大家点点关注,点点赞,感谢!
多应用下 Swagger 的使用,这可能是最好的方式!的更多相关文章
- HTTP协议下保证登录密码不被获取最健壮方式
原文:http://www.cnblogs.com/intsmaze/p/6009648.html HTTP协议下保证登录密码不被获取最健壮方式 说到在http协议下用户登录如何保证密码安全这个问 ...
- .Net core 下Swagger如何隐藏接口的显示
Swagger是这个非常强大的api文档工具,通常可以用来测试接口,和查看接口,就像这样: 非常的好用和快捷,这是一个小小的demo,我们在完成系统时,发布后,外部依旧可以用/swagger访问到这个 ...
- 魔改swagger:knife4j的另外一种打开方式
之前公司使用了swagger作为文档管理工具,原生的swagger-ui非常丑,之后就用了开源项目 萧明 / knife4j 的swagger组件进行了swagger渲染,改造之后界面漂亮多了,操作也 ...
- HTTP协议下保证登录密码不被获取更健壮方式
说到在http协议下用户登录如何保证密码安全这个问题: 小白可能第一想法就是,用户在登录页面输入密码进行登录时,前台页面对用户输入的密码进行加密,然后把加密后的密码作为http请求参数通过网络发 ...
- virtualbox下centos虚拟机安装,并网卡配置桥接方式上网,使得和host可以互Ping通。
见:http://www.cnblogs.com/taoshiqian/p/7615993.html 注意: 1.host 主机什么都不要处理 2.将virtualbox 的对应虚拟机网络设置桥接 3 ...
- WPF 下两种图片合成或加水印的方式(转载)
来源:http://www.cnblogs.com/lxblog/ 最近项目中应用多次应用了图片合成,为了今后方便特此记下. 在WPF下有两种图片合成的方式,一种还是用原来C#提供的GDI+方式,命名 ...
- ArcGIS下图层范围不正确的两种处理方式
ArcGIS下图层范围不正确,偶尔能碰上这种情况,主要表现为“缩放至图层”时,其显示范围与该图层内所有要素的外包围盒范围不一致.针对这个问题,有两种解决办法. 方法一:导出数据.新创建含有要素的Sha ...
- MAC下安装多版本JDK和切换几种方式
环境: MAC AIR,OS X 10.10,64位 历史: 过去 Mac 上的 Java 都是由 Apple 自己提供,只支持到 Java 6,并且OS X 10.7 开始系统并不自带(而是可选 ...
- 破解windows下MySQL服务启动不了的情况下不能对其进行全然卸载的解决方式
下面的文章主要介绍的是在MySQL服务启动不了的情况下,不能对其进行全然卸载的实际解决的方法的描写叙述,下面就是对解决MySQL服务启动不了的情况下详细方案的描写叙述,希望在你今后的学习中会对你有所帮 ...
随机推荐
- hdu6090 菊花图
Rikka with Graph Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) ...
- vscode环境配置(三)——解决控制台终端中文输出乱码
由于系统终端默认编码为GBK,所以需要修改为UTF-8 方法一 打开cmd输入chcp查看编码格式,查看以及修改如下图所示: 方法二
- 错误记录:Data too long for column 'xxx' at row 1
错误记录:Data too long for column 'xxx' at row 1 使用Flask-sqlalchemy操作数据时报错: "Data too long for colu ...
- CVE-2019-7238 poc
from requests.packages.urllib3.exceptions import InsecureRequestWarning import urllib3 import reques ...
- 如何短时间内快速通过Java面试
当然是刷题啊 1-10期[10期]Redis 面试常见问答[09期]说说hashCode() 和 equals() 之间的关系?[08期]说说Object类下面有几种方法呢?[07期]Redis中是如 ...
- jchdl - RTL实例 - MOS6502 CPU
https://mp.weixin.qq.com/s/OguQKMU64GGdinCJjgyeKw 实现MOS6502 CPU,主要是实现状态机. 参考链接 https://github.co ...
- ActiveMQ 笔记(二)部署和DEMO(队列、主题)
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.部署操作 1. 部署在linux 上的acvtiveMQ 要可以通过前台windows 的页面访问, ...
- Java实现 LeetCode 743 网络延迟时间(Dijkstra经典例题)
743. 网络延迟时间 有 N 个网络节点,标记为 1 到 N. 给定一个列表 times,表示信号经过有向边的传递时间. times[i] = (u, v, w),其中 u 是源节点,v 是目标节点 ...
- Java实现 LeetCode 710 黑名单中的随机数(黑白名单)
710. 黑名单中的随机数 给定一个包含 [0,n ) 中独特的整数的黑名单 B,写一个函数从 [ 0,n ) 中返回一个不在 B 中的随机整数. 对它进行优化使其尽量少调用系统方法 Math.ran ...
- Java实现 LeetCode 179 最大数
179. 最大数 给定一组非负整数,重新排列它们的顺序使之组成一个最大的整数. 示例 1: 输入: [10,2] 输出: 210 示例 2: 输入: [3,30,34,5,9] 输出: 9534330 ...