轉自我的個人博客:java掃描包下所有類的完整路徑
最近在完善微信公眾號返利機器人訂單輪訓、跟蹤。
業務邏輯是定時任務調用淘客API,按一定條件查詢某時間段內的返利訂單。定時任務框架用的quartz,原先定時任務寫死在代碼中,此次優化將定時任務做成可視化配置,更加靈活、通用。
今天暫不討論、總結quartz定時任務可視化配置,后面單獨寫文章總結一下。
用過quartz的應該知道,所有的定時任務都是直接或間接的繼承Job類,我們把所有的job類放在同一個包下,做成可視化需要解決的就是掃描此包,獲取包下所有的job類。
直接貼上掃描包下所有類代碼
package com.gaoxiaobo.download.component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
/**
* @Auther: gaoxiaobo
* @Date: 2020/8/8 21:17
* @Description:
*/
public class ClasspathPackageScanner {
private static Logger logger = LoggerFactory.getLogger(ClasspathPackageScanner.class);
private static ClassLoader cl = ClasspathPackageScanner.class.getClassLoader();
/**
*獲取指定包下的所有字節碼文件的全類名
*/
public static List<String> getFullyQualifiedClassNameList(String basePackage) throws Exception {
return doScan(basePackage, new ArrayList<>());
}
private static List<String> scanJar(String basePackage)throws Exception{
List<String> clazz = new ArrayList<>();
Enumeration<URL> urlEnumeration = Thread.currentThread().getContextClassLoader().getResources(basePackage.replace(".", "/"));
while (urlEnumeration.hasMoreElements()) {
URL url = urlEnumeration.nextElement();//得到的結果大概是:jar:file:/C:/Users/ibm/.m2/repository/junit/junit/4.12/junit-4.12.jar!/org/junit
String protocol = url.getProtocol();//大概是jar
System.out.println(protocol);
if ("jar".equalsIgnoreCase(protocol)) {
//轉換為JarURLConnection
JarURLConnection connection = (JarURLConnection) url.openConnection();
if (connection != null) {
JarFile jarFile = connection.getJarFile();
if (jarFile != null) {
//得到該jar文件下面的類實體
Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
while (jarEntryEnumeration.hasMoreElements()) {
/*entry的結果大概是這樣:
org/
org/junit/
org/junit/rules/
org/junit/runners/*/
JarEntry entry = jarEntryEnumeration.nextElement();
String jarEntryName = entry.getName();
//這里我們需要過濾不是class文件和不在basePack包名下的類
if (jarEntryName.contains(".class") && jarEntryName.replaceAll("/",".").startsWith(basePackage)) {
String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replace("/", ".");
clazz.add(className);
}
}
}
}
}
}
return clazz;
}
/**
*doScan函數
* @param basePackage
* @param nameList
* @return
* @throws IOException
*/
private static List<String> doScan(String basePackage, List<String> nameList) throws Exception {
String classpath = ClasspathPackageScanner.class.getResource("/").getPath();
if (classpath.startsWith("file")){
return scanJar(basePackage);
}
String splashPath = dotToSplash(basePackage);
URL url = cl.getResource(splashPath); //file:/D:/WorkSpace/java/ScanTest/target/classes/com/scan
String filePath = getRootPath(url);
List<String> names = null; // contains the name of the class file. e.g., Apple.class will be stored as "Apple"
if (isJarFile(filePath)) {// 先判斷是否是jar包,如果是jar包,通過JarInputStream產生的JarEntity去遞歸查詢所有類
if (logger.isDebugEnabled()) {
logger.debug("{} 是一個JAR包", filePath);
}
names = readFromJarFile(filePath, splashPath);
} else {
if (logger.isDebugEnabled()) {
logger.debug("{} 是一個目錄", filePath);
}
names = readFromDirectory(filePath);
}
for (String name : names) {
if (isClassFile(name)) {
nameList.add(toFullyQualifiedName(name, basePackage));
} else {
doScan(basePackage + "." + name, nameList);
}
}
if (logger.isDebugEnabled()) {
for (String n : nameList) {
logger.debug("找到{}", n);
}
}
return nameList;
}
private static String toFullyQualifiedName(String shortName, String basePackage) {
StringBuilder sb = new StringBuilder(basePackage);
sb.append('.');
sb.append(trimExtension(shortName));
//打印出結果
System.out.println(sb.toString());
return sb.toString();
}
private static List<String> readFromJarFile(String jarPath, String splashedPackageName) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("從JAR包中讀取類: {}", jarPath);
}
JarInputStream jarIn = new JarInputStream(new FileInputStream(jarPath));
JarEntry entry = jarIn.getNextJarEntry();
List<String> nameList = new ArrayList<String>();
while (null != entry) {
String name = entry.getName();
if (name.startsWith(splashedPackageName) && isClassFile(name)) {
nameList.add(name);
}
entry = jarIn.getNextJarEntry();
}
return nameList;
}
private static List<String> readFromDirectory(String path) {
File file = new File(path);
String[] names = file.list();
if (null == names) {
return null;
}
return Arrays.asList(names);
}
private static boolean isClassFile(String name) {
return name.endsWith(".class");
}
private static boolean isJarFile(String name) {
return name.endsWith(".jar");
}
private static String getRootPath(URL url) {
String fileUrl = url.getFile();
int pos = fileUrl.indexOf('!');
if (-1 == pos) {
return fileUrl;
}
return fileUrl.substring(5, pos);
}
/**
* "cn.fh.lightning" -> "cn/fh/lightning"
* @param name
* @return
*/
private static String dotToSplash(String name) {
return name.replaceAll("\\.", "/");
}
/**
* "Apple.class" -> "Apple"
*/
private static String trimExtension(String name) {
int pos = name.indexOf('.');
if (-1 != pos) {
return name.substring(0, pos);
}
return name;
}
/**
* /application/home -> /home
* @param uri
* @return
*/
private static String trimURI(String uri) {
String trimmed = uri.substring(1);
int splashIndex = trimmed.indexOf('/');
return trimmed.substring(splashIndex);
}
}
