Java中包扫描的实现

news/2024/4/26 7:30:03/文章来源:https://blog.csdn.net/a141210104/article/details/130331860

用过spring框架后知道包扫描是一个非常好用的功能,只需要在某个包下写自己的类,框架就能自动帮我们加载到容器中,从而在各处使用,今天自己来实现一下包扫描。

原理其实很简单,就是找到某个目录下的所有class文件,然后使用类加载器加载到jvm中,再使用反射生成一个该类的对象即可。

需求

实现一个文件监控的服务,当文件或者目录发生变化时,根据不同的文件做不同的处理。要监控的目录要支持从配置文件中读取

思路

首先想到的是

  1. 监控目录,得知文件的变动信息
  2. 根据文件名字判断需要做什么处理

很容易写出面的伪代码:

// 当文件发生变化时
public dealOnChange(String filePath){if(filePath.equal("aa")){// todo}else if(filepath.equal("bb")){// todo}
}

上面代码确实很容易实现,但是扩展能力不强。当某天需要新增一个监控文件,就需要修改 dealOnChange 方法,增加一个分支判断,当修改了代码就有可能引入bug,不满足 OCP 原则。

OCP要求软件实体应该是可扩展的,但不应该修改。这意味着,如果需要更改行为,应该使用继承或组合来实现,而不是修改现有代码。它还要求代码应该是可重用的,而不是重新编写。这样,你就可以利用现有的代码,而不是重写它。

可以定义一个文件变动处理器接口,不同的文件变动时,使用不同处理器的实现类,伪代码如下:

interface FileChangeListener {void deal(String filePath);void select(String filePath);
}// 当文件发生变化时
class Main {List<FileChangeListener> fileChangeListenerList;public dealOnChange(String filePath){FileChangeListener  listener;for( FileChangeListener f : fileChangeListenerList) {if(f.select(filePath)) {listener = f;break}}// if listener  is null ,do other...listener.deal(filePath)}public static void main(String[] args) {// todo: 把所有实现了 FileChangeListener 接口的类都的对象都添加到 fileChangeListenerList 集合中// fileChangeListenerList = loadAllListenerClass()while(true) {// 假设getChange 可以获取到变化的文件路径String filePath = getChange();dealOnChange(filePath)}}
}

上面的伪代码可以实现这样的功能: 当需要增加一个监控文件时,只需新写一个类,然后实现 FileChangeListener 接口,在这个类中处理文件变化时需要做的动作。这样与第一版的区别是:增加功能时,不修改旧代码,而是新增代码

现在关键的问题来了,怎么实现 loadAllListenerClass():

加载指定目录下的class

因为一堆 class 文件可以打包成 jar包, 然后使用 java -jar 的方式运行。也可以不用打包,直接运行 java Main。 不同的运行方式导致从目录中找文件的方式也不一样。分别如下:

直接从目录中加载

public  List<Class<?>> getClassListFromDir(String packagePath) {// 先获取包的路径String localPath = this.getClass().getClassLoader().getResource(packagePath.replace(".","/")).getPath();File classFile = new File(localPath );List<Class<?>> klassList = new ArrayList<>();// 遍历这个目录下的所有文件(假设都是class文件)for (File file : Objects.requireNonNull(classFile.listFiles())) {try {// 拼接 class 文件的全限定名,并加载Class<?> klass = Class.forName(packagePath + "." + file.getName().replace(".class","") );Logger.info("加载配置处理器: " + klass.getName());// 如果是接口,跳过if(klass.isInterface()){continue;}// 将类对象添加到集合中klassList.add(klass);} catch (ClassNotFoundException e) {Logger.info("加载类失败: "+ e.getMessage());}}return klassList;
}

从 jar 中加载


/*** 扫描 jar包中的文件获取class* 需要特殊的工具读取jar包中内容,不能向读取目录一样* @param packagePath 要扫描的包路径* @return 类对象*/
public static List<Class<?>> getClassListFromJarFile(String packagePath) {// 得到 jar 包的位置String jarPath = Config.class.getProtectionDomain().getCodeSource().getLocation().getPath();List<Class<?>> klassList = new ArrayList<>();JarFile jarFile = null;try {jarFile = new JarFile(jarPath);} catch (IOException e) {Logger.info(e.getMessage());}List<JarEntry> jarEntryList = new ArrayList<JarEntry>();Enumeration<JarEntry> ee = jarFile.entries();packagePath = packagePath.replace(".","/");while (ee.hasMoreElements()) {JarEntry entry = ee.nextElement();// 过滤我们出满足我们需求的东西if (entry.getName().startsWith(packagePath) && entry.getName().endsWith(".class")) {jarEntryList.add(entry);}}for (JarEntry entry : jarEntryList) {String className = entry.getName().replace('/', '.');className = className.substring(0, className.length() - 6);// 也可以采用如下方式把类加载成一个输入流// InputStream in = jarFile.getInputStream(entry);try {Logger.info("加载配置处理器: " + className);Class<?> klass = Thread.currentThread().getContextClassLoader().loadClass(className);if(klass.isInterface()){continue;}klassList.add(klass);} catch (ClassNotFoundException e) {Logger.info("加载类失败: " + e.getMessage());}}return klassList;
}

到此关键的代码已经完成。拿到类对象后,就可以使用反射 klass.newInstance() 生成实例了。

本文中的示例的完整可运行代码已开源: 欢迎star

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

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

相关文章

部署LVS-NAT群集实验

一、 实验准备 负载调度器&#xff1a;内网关 ens33&#xff1a;192.168.109.12&#xff0c;外网关 ens37&#xff1a;12.0.0.1外网 Web节点服务器1&#xff1a;192.168.109.13 Web节点服务器2&#xff1a;192.168.109.14 NFS服务器&#xff1a;192.168.109.11 客户端&#xf…

基于 Windows 安装 ESP32 Arduino 软件开发环境

ESP32 Arduino 源码库&#xff1a;arduino-esp32ESP32 Arduino 环境搭建说明&#xff1a;About Arduino ESP32 其他软件环境需求&#xff1a; Git 环境 1、安装 Arduino 软件 可在 Arduino 官网 获取 Windows 端 Arduino 安装包&#xff0c;如下&#xff1a; 使用如下 .exe 一…

中文编程最高境界,不用编程,会用excel就会用,香不香?

一直以来&#xff0c;关于中文编程的争议从未消停过。现如今&#xff0c;中文编程发展又是如何&#xff1f; ★为了实现中文编程&#xff0c;从未停下脚步 我们知道&#xff0c;中国人一直以来为了实现中文编程付出了不懈的努力&#xff0c;前前后后研发了几十种中文编程语言。…

ModuleNotFoundError: No module named ‘d2l’

目录 1. 下载李沐老师分享的源代码 step1&#xff1a;下载李沐老师分享的源代码&#xff1a; step3&#xff1a;Anaconda Prompt中安装d2l(这个l是英文) step4&#xff1a;运行代码&#xff0c;成功&#xff1a; &#xff08;番外&#xff09;ModuleNotFoundError: No mod…

R语言的Meta分析【全流程、不确定性分析】方法与Meta机器学习技术应用

Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;最早出现于“循证医学”&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面。…

Tensorflow GPU 版本安装教程

非常详细的 Tensorflow GPU 版本安装教程 一、安装Anaconda二、TensorFlow GPU 一、安装Anaconda 这一步比较简单&#xff0c;也没有太多的需要注意的&#xff0c;去官网下载即可&#xff1a; 官网地址如下&#xff1a; https://www.anaconda.com/blog/individual-edition-2…

今晚直播 | 思码逸陆春蕊:面对研发效能度量落地难点,如何让数据说话?

本期分享 本期 DevData Talks 邀请到了思码逸高级咨询专家陆春蕊老师。陆春蕊老师曾就职于 Oracle 美国&#xff0c;在软件质量、项目管理方面有着丰富的经验。在研发效能领域为上百家客户提供了技术、数据分析、实践落地等方面的咨询&#xff0c;协助客户提升研发效能10%-30%…

centos系统安装mysql8.0

centos系统安装mysql8.0 环境说明开始1、查看centos7中是否有MariaDB&#xff0c;MariaDB与MySQL关系请自行查阅2、如果有MariaDB&#xff0c;需要将 步骤1 中查询到的mairadb全部卸载&#xff0c;否则MySQL安装会出现问题3、查看本机是否已经安装过MySQL4、如果安装过MySQL&am…

【内网渗透】春秋云镜Intitle WP

前言 第一次正式接触内网渗透的东西&#xff0c;写的很新手&#xff0c;也适合新手观看&#xff0c;有问题可以私信或评论&#xff0c;接下来会持续更新 信息收集 拿到地址先nmap扫端口 没什么发现&#xff0c;直接访问80端口&#xff0c;看到图标知道是thinkphp 第一台Th…

JAVA队列(Queue)用法附实例讲解

队列是什么 队列用于模拟队列这种数据结构&#xff0c;队列通常是指“先进先出”的容器。新元素插入&#xff08;offer&#xff09;到队列的尾部&#xff0c;访问元素&#xff08;poll&#xff09;操作会返回队列头部的元素。通常&#xff0c;队列不允许随机访问队列中的元素 …

MII、 RMII、 GMII、 RGMII 接口介绍

1、RGMII 接口概要 以太网的通信离不开物理层 PHY 芯片的支持&#xff0c;以太网 MAC 和 PHY 之间有一个接口&#xff0c;常用的接口有MII、 RMII、 GMII、 RGMII 等。 MII&#xff08;Medium Independent Interface&#xff0c; 媒体独立接口&#xff09;&#xff1a; MII 支持…

三闯港交所,主打性价比的乡村基如何夺魁“中式快餐第一股”?

曾被中金公司称为“中国大消费最燃赛道”的中式餐饮&#xff0c;正在密集掀起IPO的风潮。去年5月和7月&#xff0c;老乡鸡和老娘舅分别向上交所提交招股书&#xff0c;绿茶餐厅、杨国福麻辣烫、捞王等企业也在推进上市计划。 国内第四大中式快餐集团&#xff0c;占据约0.6%市场…

Linux 通过Chrony实现NTP

Linux实现NTP服务器时间同步&#xff0c;可以通过ntp服务实现&#xff0c;也可以通过chrony服务实现 两者区别主要有 Chrony运行于UDP的323端口&#xff0c;NTP运行于UDP的123端口 Chrony相比于NTP可以更快同步&#xff0c;能够最大同步的减少时间和频率的误差 Chrony能够更好…

考过HCIP入职心仪公司,分享华为认证学习经历及心得

我成功考过了HCIP&#xff0c;并通过HCIP技术拿下了3家心仪公司。 学习经历 考过或者了解过HCIP的朋友都知道&#xff0c;考试内容大多数是概念类的问题。因为我工作的缘故没有太多时间自学&#xff0c;所以我报了个线上培训班&#xff0c;这个我不建议大家盲目跟风&#xff0…

EEG源定位

导读 自从脑电图(EEG)被发现以来&#xff0c;人们希望EEG能提供一个了解大脑的窗口&#xff0c;研究人员一直试图用EEG无创定位大脑中产生头皮电位的神经元活动。20世纪50年代的早期探索使用电场理论从头皮电位分布推断大脑中电流偶极子的位置和方向&#xff0c;引发了大量定量…

第五章-数字水印-2-原理及实现

数字水印原理 根据之前图像获取位平面的操作可知&#xff0c;最低位位平面对整体图像的影响最小&#xff0c;因此数字水印的原理为在图像的最低有效位上嵌入隐藏信息&#xff0c;即在图像的最低位替换为数字水印位平面&#xff0c;完成数字的嵌入操作&#xff0c;对已嵌入数字…

【opencv】图像数字化——矩阵的运算( 5 乘法运算)

5 乘法运算 5.1使用“*”运算符 对于Mat对象的乘法&#xff0c;两个Mat只能同时是float或者double类型&#xff0c;对于其它数据类型的矩阵乘法会报错src1的列数等于src2的行数mn * npmp #include <opencv2/core/core.hpp> #include<iostream> using namesp…

实战iOS App 重签名

熟悉iOS开发的同学都知道,iOS应用的上架流程主要分为以下几步: 创建开发者账号借助辅助工具appuploader创建证书,描述文件iTunes connect创建App打包IPA上传App Store等待审核在签名的流程中,有一个App重签名的步骤,主要针对的是一些大公司有多个App的情况,多个App一个申…

数据库基础篇 《4. 运算符》

目录 1. 算术运算符 1&#xff0e;加法与减法运算符 2&#xff0e;乘法与除法运算符 3&#xff0e;求模&#xff08;求余&#xff09;运算符 2. 比较运算符 1&#xff0e;等号运算符 2&#xff0e;安全等于运算符 3&#xff0e;不等于运算符 4. 空运算符 5. 非空运算…

Unity 工具控件 之 Text 文本字间距调整(老版本的Unity编写工具控件/新版本Unity使用TMP)

Unity 工具控件 之 Text 文本字间距调整(老版本的Unity编写工具控件/新版本Unity使用TMP) 目录 Unity 工具控件 之 Text 文本字间距调整(老版本的Unity编写工具控件/新版本Unity使用TMP) 一、简单介绍 二、老版本 Unity Text 使用工具控件调整行间距 三、新版本 Unity Text…