import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Logger;
public class PackageUtil {
private final static Logger log = Logger.getLogger(PackageUtil.class.getName());
public static List<Class<?>> scanClasses(String... packageNames) {
Set<Class<?>> scannedClasses = new HashSet<>();
Set<String> scannedResources = new HashSet<>();
for (String packageName : packageNames) {
String path = packageName.replace('.', '/');
Enumeration<URL> resources;
try {
resources = Thread.currentThread().getContextClassLoader().getResources(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
if (scannedResources.contains(resource.getFile()) || scannedResources.contains(resource.getFile() + "/")) {
continue;
}
scannedResources.add(resource.getFile());
if (resource.getProtocol().equals("file")) {
scanClassesInClasspath(packageName, resource, scannedClasses);
} else if (resource.getProtocol().equals("jar")) {
scanClassesInJar(packageName, resource, scannedClasses);
}
}
}
return new ArrayList<>(scannedClasses);
}
private static void scanClassesInClasspath(String packageName, URL resource, Set<Class<?>> scannedClasses) {
File directory = new File(resource.getFile());
if (directory.exists() && directory.list() != null) {
for (String fileName : Objects.requireNonNull(directory.list())) {
if (fileName.endsWith(".class")) {
String className = packageName + '.' + fileName.substring(0, fileName.length() - 6);
try {
scannedClasses.add(Class.forName(className));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
} else {
String subpackageName = packageName + '.' + fileName;
scannedClasses.addAll(scanClasses(subpackageName));
}
}
}
}
private static void scanClassesInJar(String packageName, URL resource, Set<Class<?>> scannedClasses) {
JarURLConnection jarURLConnection;
JarFile jarFile;
try {
jarURLConnection = (JarURLConnection) resource.openConnection();
jarFile = jarURLConnection.getJarFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (!entry.getName().endsWith(".class")
|| entry.getName().endsWith("module-info.class")
|| entry.getName().endsWith("package-info.class")
|| !entry.getName().replace("/", ".").startsWith(packageName)
) {
continue;
}
String className = entry.getName().substring(0, entry.getName().length() - 6).replace("/", ".");
Class<?> clazz;
try {
clazz = Class.forName(className);
if (clazz.getPackageName().startsWith(packageName)) {
scannedClasses.add(clazz);
}
} catch (Throwable e) {
log.warning("扫描到class[%s]时异常,跳过此类。异常信息:%s".formatted(className, e.getMessage()));
}
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)