swagger动态开关实践

news/2024/4/27 13:23:07/文章来源:https://blog.csdn.net/fIsh1220Fish/article/details/127449860

swagger动态开关实践

  • 1. 背景
  • 2. 配置文件监听
    • 2.1 基于注解
    • 2.2 基于jdk
  • 3. swagger改造
    • 3.1 bean刷新
    • 3.2 方法重写
  • 4. 总结
  • 5. 参考资料

概要

1. 背景

系统漏洞扫描,扫出了swagger的问题。这个问题其实比较基础,那就是生产环境不应该开启swagger

但是,有的时候为了调用方便(主要是部署workflow流程图),还是需要临时开启swagger,并且业务要无感知。

有了上述背景介绍,可以快速将整个需求细化为2步操作:

  • 监听配置文件变化
  • 当配置文件变化时,动态修改swagger页面的开关

2. 配置文件监听

配置文件,就是我们常见的.properties.yml/yaml,这里以.properties为例。

通过之前的阅读和网上资料搜索,锁定了2种方案:

  • jdk1.7提供的watchService监听
  • spring cloud提供的@RefreshScope注解

相比较而言,显然第二种方案成本更低,那么我们优先尝试。

2.1 基于注解

Spring cloud中提供了@RefreshScope注解,这个注解的含义从其命名上就可以窥探,刷新范围。其大体的实现关系为:Scope -> GenericScope -> RefreshScope

首先,需要添加maven依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

仔细研究一下@RefreshScope,其继承自RefreshScope extends GenericScope,重点代码如下:

@ManagedOperation(description = "Dispose of the current instance of bean name provided and force a refresh on next method execution.")
public boolean refresh(String name) {if (!name.startsWith(SCOPED_TARGET_PREFIX)) {// User wants to refresh the bean with this name but that isn't the one in the// cache...name = SCOPED_TARGET_PREFIX + name;}// Ensure lifecycle is finished if bean was disposableif (super.destroy(name)) {this.context.publishEvent(new RefreshScopeRefreshedEvent(name));return true;}return false;
}

基本原理就是:destory掉原有的bean后,重新发布监听事件监听bean的刷新,并使用cglib创建bean的代理,每次访问是对代理的访问。

有了RefreshScope后,还需要监听到文件改动,从而刷新对应的bean(根据bean的名称),这里参考了spring cloud中的fresh接口,该接口在不重启服务的情况下拿到最新的配置,具体步骤如下:

  1. 获取刷新之前的所有PropertySource
  2. 调用addConfigFilesToEnvironment方法获取最新的配置
  3. 调用changes方法更新配置信息
  4. 发布EnvironmentChangeEnvent事件
  5. 调用refreshScoperefreshAll方法刷新范围

但是在调用environmentListener的过程中,由于项目启动时pom文件没有打包配置文件,因此拿不到对应的环境变量,导致监听异常。

很遗憾,这条路走不通。

2.2 基于jdk

jdk1.7java.nio.file包下,提供了WatchService这个接口。

我们知道,nio的精髓其实就在于轮询,通过selector的轮询机制实现非阻塞的调用,而WatchService也完美的诠释了nio

简单看下WatchService的使用:

  1. 首先,构造WatchService
WatchService watchService = FileSystems.getDefault().newWatchService();
Paths.get(propertiesFile.getParent()).register(watchService,StandardWatchEventKinds.ENTRY_CREATE,StandardWatchEventKinds.ENTRY_MODIFY,StandardWatchEventKinds.ENTRY_DELETE);
  • 这里构造了WatchService实例,并在指定的路径上注册了该实例
  • 在注册实例的同时,还监听了createmodifydelete三种行为
  1. 接下来是启动WatchService
//启动一个线程监听内容变化,并重新载入配置
Thread watchThread = new Thread() {public void run() {while (true) {try {WatchKey watchKey = watchService.take();for (WatchEvent event : watchKey.pollEvents()) {// 刷新bean// envConfig.envListener();if (Objects.equals(event.context().toString(), fileName)){properties.load(new FileInputStream(propertiesFile));}watchKey.reset();}} catch (Exception e) {e.printStackTrace();}}};
};//设置成守护进程
watchThread.setDaemon(true);
watchThread.start();
  • 启用了一个额外的线程去跑,使用take方法取出watchKey。其实WatchService是以队列的形式对key进行存储,其提供的方法也和队列保持一致
  • watchKey中的event进行轮询,判断和我们指定的文件名一致时,进行对应操作。这里的操作可以自己随便定制,我这的操作是对配置文件的重新加载,也就是刷新配置文件中的值
  • 最后需要使用watchKey.reset()方法对watchKey进行重设,重设的原因在注释中写的很清楚:

If this watch key has been cancelled or this watch key is already in the ready state then invoking this method has no effect. Otherwise if there are pending events for the object then this watch key is immediately re-queued to the watch service. If there are no pending events then the watch key is put into the ready state and will remain in that state until an event is detected or the watch key is cancelled.

简单来说,就是watch key实际上是监听event变动的,当轮询的过程中watch key下有了新的(pending态)的event时,那么久会立刻将其加入到WatchService的队列中,否则将其置为ready状态,等待下一次event的唤醒(signal)或是取消掉该key(cancel)。

这里其实有个问题,那就是watch key是何时加入到WatchService这个队列中的?

通过几个参数生成出来的,如下图:
在这里插入图片描述

  1. 最后则是添加钩子,在shutdown时进行close
//当服务器进程关闭时把监听线程close掉
Runtime.getRuntime().addShutdownHook(new Thread() {@Overridepublic void run() {try{watchService.close();} catch(IOException e) {e.printStackTrace();}}
});

3. swagger改造

成功监听到配置文件的变更后,下一步需要做的就是根据配置文件中参数的不同,对swagger的显示页做控制。

先看看原本的swagger配置:

@Value("${swagger.button}")
private boolean swaggerButton;/*** @return*/
@Bean(name = "swaggerApi")
public Docket createRestApi() {if (swaggerButton) {// 测试环境return new Docket(DocumentationType.SWAGGER_2)// 也可以采用该参数进行控制,但是为了强制什么信息都不显示,使用了 if/else.enable(true)  .apiInfo(apiInfo()).select()// 对所有api进行监控.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))// 对所有路径进行监控.paths(path -> !"/error".equals(path)).build();} else {// 线上环境return new Docket(DocumentationType.SWAGGER_2).select().paths(PathSelectors.none()).build();}}private ApiInfo apiInfo() {return new ApiInfoBuilder().title("111") //大标题.contact(new Contact("222","","")) //创建人.description("API描述") //详细描述.version("1.0").build();
}
  • 项目启动时,@Bean标记的方法被初始化为spring bean存入ApplicationContext

当我们监听到配置文件变更时,需要刷新swaggerApi这个bean

3.1 bean刷新

刷新bean的方法比较简单,代码如下:

ApplicationContext applicationContext = SpringContextUtils.getApplicationContext();
//获取上下文
DefaultListableBeanFactory defaultListableBeanFactory =(DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();Docket swaggerApi = applicationContext.getBean("swaggerApi", Docket.class);
System.out.println("Old bean: \n");
System.out.println(swaggerApi);//销毁指定实例 swaggerApi是上文注解过的实例名称 name="swaggerApi"
defaultListableBeanFactory.destroySingleton("swaggerApi");
//按照旧有的逻辑重新获取实例
Docket restApi = Swagger2Config.createRestApi();
//重新注册同名实例,这样在其他地方注入的实例还是同一个名称,但是实例内容已经重新加载
defaultListableBeanFactory.registerSingleton("swaggerApi", restApi);Docket swaggerApiNew = applicationContext.getBean("swaggerApi", Docket.class);
System.out.println("New bean: \n");
System.out.println(swaggerApiNew);
  • ApplicationContext中获取beanFactory
  • beanFactory中销毁原来的bean
  • 重新注册一个同名bean

但是这么操作完后,发现修改完配置文件后,页面并没有任何变化,能访问的依然可以访问,证明这个思路是错误的

那么正确的解法是什么呢?

3.2 方法重写

顺藤摸瓜,我们看看swagger页面究竟是怎么出现的?
在这里插入图片描述

  • 当访问页面时,我们其实是在请求接口

那么这个接口是什么样的呢?
在这里插入图片描述

  • 接口的核心其实是从documentCache中,根据groupName字段拿到Document信息,而不是从ApplicationContext中取bean
  • 因此,需要调整的部分我们已经定位到了,那就是Swagger2Controller

接下来,重写这个class,增加几行代码,就完成了对swagger的控制:

// 增加swagger控制
boolean swaggerButton = Boolean.parseBoolean(WatchProperties.get("swagger.button"));
if (!swaggerButton) {return new ResponseEntity<Json>(HttpStatus.FORBIDDEN);
}

4. 总结

总结而言,由于已经有大量前人栽树的经验,我们更多的时候只需要享受乘凉的快感就可以。

但是在实践过程中,其实还是有很多需要总结和沉淀的地方:

  • swagger访问时,实际上是内部接口调用,而不是我所以为的加载spring factory中的bean。往大了说,其实所有的url访问无外乎接口调用(或者静态文件),那么你需要抽丝剥茧的其实是其真实路径下的代码,而不是自以为的一些经验

  • 如果项目没有采用最佳实践,而是自顾自的采用一些看上去很棒的方案,往往会给以后的功能扩展带来一些麻烦。因为既有的快速解决方案,在非最佳实践的场景下,一般都会失效

  • 之前实践的javaagent实现对jvm内部的监听,而WatchService实现对目录的监听,两者配合起来,可以完成很有意思的事情

5. 参考资料

  • 深入理解SpringCloud之配置刷新
  • spring cloud 配置文件热加载–@RefreshScope
  • WatchService 监听一个文件夹下所有层的变化
  • FileWatcherService
  • Springboot重新加载Bean
  • java中改变项目中引入的jar包的某个类的源码
  • Java 如何获取jar包或者项目的同级目录

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_404474.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FreeRTOS 软件定时器的使用

FreeRTOS中加入了软件定时器这个功能组件&#xff0c;是一个可选的、不属于freeRTOS内核的功能&#xff0c;由定时器服务任务&#xff08;其实就是一个定时器任务&#xff09;来提供。 软件定时器是当设定一个定时时间&#xff0c;当达到设定的时间之后就会执行指定的功能函数&…

el-switch接口实现

后台返回的数据&#xff1a; active-textswitch 打开时的文字描述string——inactive-textswitch 关闭时的文字描述string——active-valueswitch 打开时的值boolean / string / number—trueinactive-valueswitch 关闭时的值boolean / string / number—falseactive-colorswi…

Enzo丨艾美捷Enzo Ciglitazone解决方案

艾美捷Enzo Ciglitazone是一种噻唑烷二酮类降血糖药。它在遗传性肥胖的C57 Bl/6 ob/ob小鼠中显示抗高血糖活性&#xff0c;并且是选择性PPARγ激动剂&#xff08;EC50&#xff1d;3M&#xff09;。抑制人间充质干细胞中HUVEC分化和血管生成&#xff0c;并刺激脂肪生成和减少成骨…

区块链 — Overview

文章目录区块链的概念区块链数据结构区块链的基础技术哈希运算数字签名共识算法智能合约P2P网络区块链分类公有链联盟链私有链区块链的概念 狭义上&#xff0c;区块链是一种按照时间顺序将数据区块以顺序相连的方式组合成的一种链式数据结构&#xff0c;并以密码学方式保证的不…

深度神经网络图像识别,深度神经网络图像配准

如何用Python和深度神经网络寻找相似图像 代码首先&#xff0c;读入TuriCreate软件包import turicreate as tc我们指定图像所在的文件夹image&#xff0c;让TuriCreate读取所有的图像文件&#xff0c;并且存储到data数据框data tc.image_analysis.load_images(./image/)我们来…

《python 可视化之 matplotlib》第一章 折线图 plot

《python 可视化之 matplotlib》第一章 折线图 本章节内容包括以下几方面内容&#xff1a; 绘制曲线 yx2yx^2yx2;让曲线更加光滑&#xff1b;常见的相关属性设置&#xff1b;多条折线图的绘制&#xff1b;折线图之间的颜色填充&#xff1b;时间序列可视化&#xff1b;常见问题…

iNFTnews|在元宇宙中探索NFT的无限可能

元宇宙正在使我们当下的生活发生显著变化。 我们都玩过很多电子游戏&#xff0c;看过很多相关的科幻电影&#xff0c;也有过很多关于元宇宙进入我们日常生活后&#xff0c;我们周围的事物将会受到怎样的巨大影响的讨论。 我们很快就会看到&#xff0c;如此先进的技术突破将逐…

人工神经网络概念及组成,人工神经网络基本概念

1、什么是BP神经网络&#xff1f; BP算法的基本思想是&#xff1a;学习过程由信号正向传播与误差的反向回传两个部分组成&#xff1b;正向传播时&#xff0c;输入样本从输入层传入&#xff0c;经各隐层依次逐层处理&#xff0c;传向输出层&#xff0c;若输出层输出与期望不符&…

含汞废水的深度处理方法

CH-95 是一款为了从工业废水中去除回收汞和贵金属而专门开发的螯合树脂。拥有聚乙烯 异硫脲官能基的大孔树脂&#xff0c;这种树脂对汞有极高的选择性。钠&#xff0c;碱土&#xff0c;铁铜等重金属等不能干扰 其对汞的选择性去除。 CH-97 是一款含有附着甲基硫醇聚苯乙烯共…

基于PB的企业人力资源信息系统设计与实现

目 录 摘 要 I Abstract II 第1章 引言 1 1.1选题背景及意义 1 1.2发展现状 1 1.3论文结构 2 第2章 系统分析 3 2.1 系统目标 3 2.2 系统需求分析 3 第3章 系统设计 5 3.1 系统功能结构设计 5 3.2 数据库设计与实现 7 3.2.1数据库需求分析 7 3.2.2数据库概念结构设计 8 3.2.3数…

[oeasy]python0010 - python虚拟机解释执行py文件的原理

解释运行程序 &#x1f94a; 回忆上次内容 我们这次设置了断点 设置断点的目的是更快地调试调试的目的是去除​​bug​​别害怕​​bug​​一步步地总能找到​​bug​​这就是程序员基本功 调试​​debug​​ 我心中还是有疑问 ​​python3​​ 是怎么解释​​hello.py​​ 的…

Python实现SSA智能麻雀搜索算法优化支持向量机分类模型(SVC算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 麻雀搜索算法(Sparrow Search Algorithm, SSA)是一种新型的群智能优化算法&#xff0c;在2020年提出&am…

pytorch:常见的pytorch参数初始化方法总结

pytorch参数初始化1. 关于常见的初始化方法1&#xff09; 均匀分布初始化torch.nn.init.uniform_()2) 正态分布初始化torch.nn.init.normal_()3) 常量初始化torch.nn.init.constant_()4) Xavier均匀分布5&#xff09;Xavier正态分布初始化6) kaiming均匀分布初始化7) kaiming正…

除了pid还有什么控制算法,类似pid算法还有哪些

什么是专家PID&#xff1f;他和传统的PID有什么区别&#xff1f; PID是智能控制啊,比如要控制一个水管的水流量,通过流量计,开关阀,让PID来控制开关阀的开关大小使水流量正确.专家PID记得是PID的高级设置,某些个场合一般的PID无法使用,出现了了专用的,有特殊功能的.记忆中是这…

防火墙的ISP选路

拓补图&#xff1a; 实验目的&#xff1a; 让R1走ISP1的路径访问192.168.1.1&#xff0c;R2走ISP2的路径访问172.16.1.1 1. IP地址的配置略 2. 防火墙区域的划分&#xff08;防火墙的g1/0/2接口是属于ISP1接口&#xff0c;所以需要自己新建一个区域然后添加接口&#xff0c;…

测试界的飞虎队:测试人才战略——测试行业的精英战略(学习了)

一、前言 提到飞虎队&#xff0c;大家第一印象就是精英。相信绝大多数公司都希望能组件出一支优秀的测试队伍&#xff0c;来支撑自己的业务&#xff0c;很多公司都喊出了精英化战略。既然如此&#xff0c;就命中一个话题--测试人才战略。 有几个问题是不得不面对的&#xff1a…

算法 - 组合

目录 题目来源 题目描述 示例 提示 题目解析 算法源码 剪枝优化 题目来源 77. 组合 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 输入&#…

JavaScript——关于JavaScript、在HTML中嵌入JS代码的三种方式、变量

前言 近日无意间透过装了热水之后&#xff0c;散发出水蒸气的透明水杯看东西时&#xff0c;发现看到的东西呈现模糊效果&#xff0c;这种效果和毛玻璃效果类似。毛玻璃效果在web、手机系统上也有很多应用&#xff0c;如网页上的广告弹窗&#xff0c;手机系统的消息通知界面等等…

LeetCode刷题复盘笔记——131. 分割回文串(一文搞懂回溯解决经典的分割回文串问题)

今日主要总结一下&#xff0c;131. 分割回文串 题目&#xff1a;131. 分割回文串 Leetcode题目地址 题目描述&#xff1a; 给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正着读和反着读…

目标检测——day46 可转移交互性知识的人机交互检测

Transferable Interactiveness Knowledge for Human-Object Interaction Detection论文pdf下载&#xff08;含笔记&#xff09;Transferable Interactiveness Knowledge for Human-Object Interaction Detection1 INTRODUCTION3 PRELIMINARY4 METHOD4.1 Overview4.2 Representa…