如何以低侵入方式获取业务系统使用的二方包版本号

news/2025/2/11 9:17:27/文章来源:https://www.cnblogs.com/linyb-geek/p/18437414

在软件开发的世界里,依赖管理是一项至关重要却又常常被忽视的工作。最近,领导给我布置了一个特别的任务 —— 了解我们部门提供给业务方的核心二方包及其版本号,以便为后续的兼容升级工作提供有力支持。这看似简单的需求,背后却蕴含着不少技术挑战和思考。在本文中,我将与大家分享在完成这个任务过程中的探索与实践,希望能为正在面临类似问题的你提供一些启发。

知识小科普:一方包、二方包、三方包的区别

在深入探讨获取二方包版本号的方法之前,我们先来科普一下一方包、二方包和三方包的概念。

一方包

一方包指的是本工程中各模块之间的相互依赖。它们就像是一个团队内部成员之间的协作,只在项目内部发挥作用,是项目实现自身功能的重要组成部分。比如,在一个电商项目中,用户模块、订单模块、支付模块等之间的相互调用所依赖的代码包,就属于一方包。一方包的使用范围局限于项目内部,对项目的功能实现起着关键作用。

二方包

二方包是公司内部的依赖库,通常是公司内部其他项目发布的 jar 包。可以把它想象成公司内部不同部门之间的协作,虽然不在同一个项目组,但都在公司这个大环境下,为了实现公司的整体目标而共享资源。例如,公司的基础服务团队开发了一套通用的日志记录组件,其他业务部门在开发项目时可以直接引用这个二方包,实现统一的日志记录功能。二方包的使用范围限于公司内部,能够提高公司内部开发的效率和代码的复用性。

三方包

三方包是来自公司之外的开源库,像大家熟知的 apache、google 等发布的依赖就属于三方包。它们就像是外界提供的各种工具和资源,供整个行业或公众使用。比如,在开发 Web 应用时,我们常常会使用 Apache 的 HttpComponents 库来处理 HTTP 请求,这个库就是一个典型的三方包。三方包的使用范围广泛,能够帮助我们快速集成各种功能,加速项目的开发进程。

总结来说,一方包、二方包和三方包的主要区别在于权限范围和使用范围:一方包限于项目内部,二方包限于公司内部,而三方包则是面向公众或行业开放的。

回到正题:获取业务系统使用的二方包版本号的挑战

由于核心二方包是我们部门开发提供的,我们清楚地知道有哪些核心二方包。但问题在于,我们并不了解业务方到底使用了哪些二方包以及相应的版本。这就好比我们生产了一堆工具,却不知道客户具体使用了哪些工具以及工具的版本。而获取业务系统使用的二方包及其版本号,就成为了我们完成领导需求的关键所在。接下来,我将详细介绍几种获取二方包版本号的方法及其优缺点。

方法一:拉通业务方获取信息

最直接的方法就是拉通各个业务方,让他们提供正在使用的二方包及其版本。这个方法看似简单可行,但在实际操作中却存在不少问题。首先,这个需求是我们发起的,对于业务方来说,他们只是配合方,这件事情的优先级对他们来说可能并不高。他们可能有自己手头更重要的业务任务需要处理,很难将大量的时间和精力投入到我们的这个需求中。其次,业务方在配合时会考虑投入产出比。如果我们不能清晰地向他们阐述这件事情对他们的价值和收益,他们可能会对配合工作产生抵触情绪,导致沟通成本大幅增加。因此,这种方法虽然直接,但效率较低,并不是最佳选择。

方法二:埋点上报方式获取信息

如何获取二方包版本

获取二方包版本的关键在于读取META-INF/MANIFEST.MF文件中的Implementation-Version属性。这个属性记录了二方包的版本信息。虽然在某些情况下,Implementation-Version可能为空,但对于我们提供的二方包来说,这个问题并不存在。因为我们在构建二方包时,在pom文件的 GAV(GroupId、ArtifactId、Version)配置中引入了生成Implementation-Version的插件。具体配置如下:

 <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.2.0</version><configuration><archive><manifest><addDefaultImplementationEntries>true</addDefaultImplementationEntries></manifest></archive></configuration></plugin></plugins></build>

使用该插件会生成形如下内容

Manifest-Version: 1.0
Implementation-Title: Spring Cloud Alibaba Sentinel
Implementation-Version: 2.1.0.RELEASE
Built-By: yizhan
Implementation-Vendor-Id: com.alibaba.cloud
Created-By: Apache Maven 3.5.0
Build-Jdk: 1.8.0_131
Implementation-URL: https://github.com/alibaba/spring-cloud-alibaba/spring-cloud-alibaba-sentinel
Implementation-Vendor: Pivotal Software, Inc.

那么,如何获取Implementation-Version呢?最容易想到的方法就是解析MANIFEST.MF文件。下面是一个解析工具类的示例代码:

public final class VersionNoFetcher {private VersionNoFetcher() {}/*** Return the full version string of the present codebase, or {@code null}* if it cannot be determined.* @return the version or {@code null}* @see Package#getImplementationVersion()*/public static VersionMeta getVersion(ClassLoader classLoader,String libraryClassName) {if(classLoader == null){classLoader = Thread.currentThread().getContextClassLoader();}return determineVersion(classLoader,libraryClassName);}private static VersionMeta determineVersion(ClassLoader classLoader,String libraryClassName) {try {Class<?> libraryClass = Class.forName(libraryClassName,true,classLoader);String implementationVersion = libraryClass.getPackage().getImplementationVersion();if (implementationVersion != null) {return new VersionMeta(implementationVersion, libraryClassName,libraryClass.getPackage().getName());}VersionMeta versionMeta = getVersionMetaFromCodeSource(libraryClassName, libraryClass);if(versionMeta != null){return versionMeta;}return getVersionMetaFromManifest(classLoader,libraryClass);} catch (Exception ex) {return new VersionMeta();}}private static VersionMeta getVersionMetaFromCodeSource(String libraryClassName, Class<?> libraryClass) throws IOException, URISyntaxException {String implementationVersion;CodeSource codeSource = libraryClass.getProtectionDomain().getCodeSource();if (codeSource == null) {return null;}URL codeSourceLocation = codeSource.getLocation();URLConnection connection = codeSourceLocation.openConnection();if (connection instanceof JarURLConnection) {implementationVersion =  getImplementationVersion(((JarURLConnection) connection).getJarFile());if (implementationVersion != null){return new VersionMeta(implementationVersion, libraryClassName, libraryClass.getPackage().getName());}}try (JarFile jarFile = new JarFile(new File(codeSourceLocation.toURI()))) {implementationVersion = getImplementationVersion(jarFile);if (implementationVersion != null){return new VersionMeta(implementationVersion, libraryClassName, libraryClass.getPackage().getName());}}return null;}private static VersionMeta getVersionMetaFromManifest(ClassLoader classLoader,Class<?> libraryClass) {try (InputStream is = classLoader.getResourceAsStream("META-INF/MANIFEST.MF")) {if (is == null) {return new VersionMeta();}// 创建Manifest对象并从输入流中读取Manifest manifest = new Manifest(is);// 获取主属性集Attributes attributes = manifest.getMainAttributes();// 从属性集中获取版本号String version = attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION);if (version != null) {return new VersionMeta(version, libraryClass.getName(), libraryClass.getPackage().getName());}} catch (Exception e) {}return new VersionMeta();}private static String getImplementationVersion(JarFile jarFile) throws IOException {return jarFile.getManifest().getMainAttributes().getValue(Attributes.Name.IMPLEMENTATION_VERSION);}}

2、如何上报

获取到二方包版本后,我们需要将这些信息上报到指定的服务端。一种常见的上报方式是通过 HTTP 请求。以下是一个上报的参考示例代码:

public final class VersionNoUtils {private static final String APP_ID_KEY = "appid";private static final String VERSION_NO_KEY = "versionNo";private VersionNoUtils(){}public static void reportVersion2RemoteSvc(String url,String appId,ClassLoader classLoader,String libraryClassNames)  {if(!StringUtils.hasText(libraryClassNames) || !StringUtils.hasText(url)){return;}Map<String,Object> params = new HashMap<>();params.put(APP_ID_KEY,appId);if(libraryClassNames.contains(EXTENSION_SEPARATOR)){String[] libClassNames = libraryClassNames.split(EXTENSION_SEPARATOR);for(String libClassName : libClassNames){addVersionForClass(classLoader, libClassName,params);}}else{addVersionForClass(classLoader, libraryClassNames,params);}sendVersion2RemoteSvc(url, params);}private static void sendVersion2RemoteSvc(String url, Map<String, Object> params) {if(!params.isEmpty()){try {String jsonContent = JsonConverter.mapToJson(params);HttpUtils.getInstance().postJson(url, jsonContent);} catch (IOException e) {}}}}

3、何时上报?

确定了获取版本和上报的方式后,接下来就是选择合适的上报时机。以下是几种常见的上报时机及其优缺点分析:

a、 在业务方项目编译期期间进行上报

可以利用 APT(Annotation Processing Tool)或者自定义 Maven 插件在编译期进行上报。这种方式的优点是对项目运行的性能影响最低,因为在编译期就完成了上报工作,不会对项目的运行时性能产生额外的开销。然而,它的缺点也很明显,一旦在编译期出现问题,排查起来会非常困难。因为编译期的错误信息往往比较复杂,涉及到编译工具和插件的配置等多个方面,定位问题的根源需要花费大量的时间和精力。

b、 在业务方项目 install 或者 deploy 期间进行上报

利用自定义 Maven 插件在业务方执行install或者deploy操作时进行上报。这种方式可以确保在项目发布到本地仓库或远程仓库之前完成版本号的上报。但是,这种方式需要业务方在他们的项目中引用相应的插件。对于业务方来说,引入这个插件可能不会给他们带来直接的收益,反而可能会增加项目的复杂性和维护成本。因此,业务方可能不太愿意配合这种方式。

c、 在业务方项目启动时上报

可以利用 Spring 提供的扩展点,在业务方项目启动时进行上报。Spring 提供了丰富的扩展点,如CommandLineRunner、ApplicationListener等,我们可以通过实现这些接口来完成上报工作。这种方式的优点是相对简单易行,而且对业务方的侵入性较小。我们可以将上报代码直接内嵌到我们提供的二方包中,业务方在使用二方包时,上报功能会自动生效,对业务方基本上是无感的。当然,这种无感是相对的,业务方通过抓包或者监控手段还是可以发现上报请求的存在。以下是一个在项目启动时上报的示例代码:

@Component
public class VersionFetchCommandLineRunner implements CommandLineRunner {ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {return new Thread(r, "report-version-thread");}});@Overridepublic void run(String... args) throws Exception {executorService.execute(() -> {reportVersion2RemoteSvc("http://localhost:8080/version/report", "10000", Thread.currentThread().getContextClassLoader(), "org.apache.catalina.startup.Tomcat,org.springframework.beans.factory.BeanFactory");});}
}

上报的内容示例如下:

{"org.apache.catalina.startup": "9.0.21", "appid": "10000", "org.springframework.beans.factory": "5.1.8.RELEASE"}

d、 在业务方项目关闭时上报

可以使用 JVM 钩子函数,或者监听 Spring 的关闭事件,在业务方项目关闭时进行上报。这种方式的优点是可以确保在项目运行的整个生命周期结束时完成上报工作,获取到项目在运行过程中使用的二方包版本信息。但是,它的缺点是如果项目在运行过程中出现异常终止等情况,可能会导致上报失败。而且,在项目关闭时进行上报,可能会对项目的关闭过程产生一定的影响,增加项目关闭的时间。
综合比较以上几种上报时机,在业务方项目启动或者关闭时进行上报是相对可行的方式。尤其是在项目启动时上报,通过将上报代码内嵌到二方包中,可以实现对业务方的低侵入性,同时保证上报工作的顺利进行。

总结

获取业务系统使用的二方包版本号这个需求,在大多数业务开发场景中可能并不常见,但在开发基础组件或进行依赖管理时却非常重要。通过本文介绍的埋点上报方式,我们可以以较低的侵入性获取到业务方使用的二方包及其版本号。在实施过程中,需要注意以下几点:首先,上报操作一定要使用异步方式,避免对业务造成堵塞;其次,如果使用自定义 Maven 插件进行上报,要注意类加载器的问题,因为 Maven 插件的类加载器是自定义类加载器;最后,在开发公共组件时,可以考虑添加HasFeatures功能,将公共组件的能力封装进去,方便后续的功能扩展和管理。

至于什么是HasFeatures,可以查看我之前的文章聊聊如何感知项目引入哪些功能特性

示例代码链接

本文涉及的示例代码已上传至 GitHub,你可以通过以下链接获取:https://github.com/lyb-geek/springboot-learning/tree/master/springboot-fetch-version
希望本文的内容能够对你有所帮助。如果你在实践过程中遇到任何问题,或者有更好的解决方案,欢迎在评论区留言交流。让我们一起在技术的道路上不断探索前行!

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

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

相关文章

Qt写Word文档-Windows

电脑没有安装微软的office,安装的是wps,用的是 QAxObject,所以只支持Windows系统一、pro文件添加 axcontainer 二、实现代码#include <QAxObject> #include <QDebug> // 创建Word应用程序对象 QAxObject* word = new QAxObject("kwps.Application");…

绝了,一招解决DeepSeek 提示“服务器繁忙,请稍后再试” 卡顿问题!(保姆级教程)

大家好,我是狂师。 现在 AI 圈里讨论最多的话题就是:"国产之光DeepSeek了"。 但用过的人也知道,是真的卡。动不动就提示:“服务器繁忙,请稍后再试”用官方App或网页版,估计10条回复中至少有8条会卡爆。对于重度使用的我来讲,经常会被官网的卡顿搞得差点吐血。…

揭秘 Sdcb Chats 如何解析 DeepSeek-R1 思维链

在上一篇文章中,我介绍了 Sdcb Chats 如何集成 DeepSeek-R1 模型,并利用其思维链(Chain of Thought, CoT)功能增强 AI 推理的透明度。DeepSeek-R1 强大的思维链能力给用户留下了深刻印象。本文将深入剖析 Sdcb Chats 实现这一功能的技术细节,重点介绍如何基于 OpenAI .NET…

全网最全的DeepSeek的使用指导资源,拿去用来操作其他的大模型也一样有用,你去找付费培训不如打赏我一毛

最近全网都在为火热的DeepSeek疯狂,不少商家培训都是出了付费培训,不少人都上当受骗。我就搜刮全网最全的使用,供大家使用,有使用文档,有提示词培训,有视频,应有尽有,现在我们就开始吧! 一、如何使用提示词 DeepSeek官网提供了很全面的提示词规则手册,包含了13个方向…

0帧起手将腾讯混元大模型集成到Spring AI的全过程解析

在前面,我们已经为大家铺垫了大量的知识点,并深入解析了Spring AI项目的相关内容。今天,我们将正式进入实战环节,从零开始,小雨将带领大家一步步完成将第三方大模型集成到Spring AI中的全过程。为了方便讲解,本次实战的示范将以腾讯的混元大模型为主,我们将逐步向你展示…

【5大误区】选择跨网文件安全交换系统的注意事项

网络隔离后,企业采用跨网文件安全交换系统可以显著提升工作效率、保障信息安全、满足合规要求、支持灵活工作模式以及增强市场竞争力。这些优势使得跨网文件交换系统成为现代企业不可或缺的工具。 一、选择跨网文件安全交换系统的常见误区 选择跨网文件安全交换系统时,企业和…

C# 深度学习:对抗生成网络(GAN)训练头像生成模型

通过生成对抗网络(GAN)训练和生成头像 目录通过生成对抗网络(GAN)训练和生成头像说明简介什么是 GAN什么是 DCGAN参数说明数据集处理权重初始化生成器判别器损失函数和优化器训练 说明 https://torch.whuanle.cn 电子书仓库:https://github.com/whuanle/cs_pytorch Maomi.Torc…

开源的 DeepSeek-R1「GitHub 热点速览」

春节假期回来,一睁眼全是王炸级的开源模型 DeepSeek-R1!GitHub 地址→github.com/deepseek-ai/DeepSeek-R1DeepSeek-R1 开源还不到一个月,Star 数就飙升至冲破天际的 70k。虽然目前仅开源了模型权重,但同时发布的技术论文详细地介绍了 DeepSeek-R1 所采用的训练技术,如模型…

C#/.NET/.NET Core优秀项目和框架2025年1月简报

前言 公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的详细介绍、功能特点、使用方式以及部分功能截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附…

Palo Alto Cortex XSOAR 6.13 for Linux - 安全编排、自动化和响应 (SOAR) 平台

Palo Alto Cortex XSOAR 6.13 for Linux - 安全编排、自动化和响应 (SOAR) 平台Palo Alto Cortex XSOAR 6.13 for Linux - 安全编排、自动化和响应 (SOAR) 平台 Security Orchestration, Automation and Response (SOAR) platform 请访问原文链接:https://sysin.org/blog/cort…

2025年01月总结及随笔之年前撞车

2025年01月总结及随笔之年前撞车1. 回头看 日更坚持了762天。读《数据保护:工作负载的可恢复性》更新完成 读《量子霸权》开更并更新完成 读《算法简史:从美索不达米亚到人工智能时代》开更并持续更新2023年至2025年01月底累计码字1936092字,累计日均码字2540字。 2025年01月…

[网络] 跨域问题及解决方案

同源策略及跨域问题 同源策略是一套浏览器安全机制,当一个源的文档和脚本,与另一个源的资源进行通信时,同源策略就会对这个通信做出不同程度的限制。 简单来说,同源策略对 同源资源 放行,对 异源资源 限制 因此限制造成的开发问题,称之为跨域(异源)问题 同源和异源 源(…