转自我的个人博客: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); } }