q前言:
在工作中看到这个知识点,就顺便参考了百度的一些资料,整理一下,希望以后用的到。
一:理论部分
1.使用场景
写一个MVC框架,需要从包中扫描出组件并注册到容器中,而JDK没有提供现成的从方法,只能自己实现
2.需求
给定一个包名,编程得到该包(和其所有子包)下所有的类文件
3.思路
有的web server在部署运行时会解压jar包,因此class文件会在普通的文件目录下。
如果web server不解压jar包,则class文件会直接存在于Jar包中。
对于前者,只需定位到class文件所在目录,然后将class文件名读取出即可;
对于后者,则需先定位到jar包所在目录,然后使用JarInputStream
读取Jar包,得到class类名。
二:程序实现
1.程序目录
2.ClasspathPackageScanner.java
1 package com.scan; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 6 import java.io.File; 7 import java.io.FileInputStream; 8 import java.io.IOException; 9 import java.net.URL; 10 import java.util.ArrayList; 11 import java.util.Arrays; 12 import java.util.List; 13 import java.util.jar.JarEntry; 14 import java.util.jar.JarInputStream; 15 16 public class ClasspathPackageScanner implements PackageScanner{ 17 private Logger logger = LoggerFactory.getLogger(ClasspathPackageScanner.class); 18 private String basePackage; 19 private ClassLoader cl; 20 21 /** 22 * 初始化 23 * @param basePackage 24 */ 25 public ClasspathPackageScanner(String basePackage) { 26 this.basePackage = basePackage; 27 this.cl = getClass().getClassLoader(); 28 } 29 public ClasspathPackageScanner(String basePackage, ClassLoader cl) { 30 this.basePackage = basePackage; 31 this.cl = cl; 32 } 33 /** 34 *获取指定包下的所有字节码文件的全类名 35 */ 36 public List<String> getFullyQualifiedClassNameList() throws IOException { 37 logger.info("开始扫描包{}下的所有类", basePackage); 38 return doScan(basePackage, new ArrayList<String>()); 39 } 40 41 /** 42 *doScan函数 43 * @param basePackage 44 * @param nameList 45 * @return 46 * @throws IOException 47 */ 48 private List<String> doScan(String basePackage, List<String> nameList) throws IOException { 49 String splashPath = StringUtil.dotToSplash(basePackage); 50 URL url = cl.getResource(splashPath); //file:/D:/WorkSpace/java/ScanTest/target/classes/com/scan 51 String filePath = StringUtil.getRootPath(url); 52 List<String> names = null; // contains the name of the class file. e.g., Apple.class will be stored as "Apple" 53 if (isJarFile(filePath)) {// 先判断是否是jar包,如果是jar包,通过JarInputStream产生的JarEntity去递归查询所有类 54 if (logger.isDebugEnabled()) { 55 logger.debug("{} 是一个JAR包", filePath); 56 } 57 names = readFromJarFile(filePath, splashPath); 58 } else { 59 if (logger.isDebugEnabled()) { 60 logger.debug("{} 是一个目录", filePath); 61 } 62 names = readFromDirectory(filePath); 63 } 64 for (String name : names) { 65 if (isClassFile(name)) { 66 nameList.add(toFullyQualifiedName(name, basePackage)); 67 } else { 68 doScan(basePackage + "." + name, nameList); 69 } 70 } 71 if (logger.isDebugEnabled()) { 72 for (String n : nameList) { 73 logger.debug("找到{}", n); 74 } 75 } 76 return nameList; 77 } 78 79 private String toFullyQualifiedName(String shortName, String basePackage) { 80 StringBuilder sb = new StringBuilder(basePackage); 81 sb.append('.'); 82 sb.append(StringUtil.trimExtension(shortName)); 83 //打印出结果 84 System.out.println(sb.toString()); 85 return sb.toString(); 86 } 87 88 private List<String> readFromJarFile(String jarPath, String splashedPackageName) throws IOException { 89 if (logger.isDebugEnabled()) { 90 logger.debug("从JAR包中读取类: {}", jarPath); 91 } 92 JarInputStream jarIn = new JarInputStream(new FileInputStream(jarPath)); 93 JarEntry entry = jarIn.getNextJarEntry(); 94 List<String> nameList = new ArrayList<String>(); 95 while (null != entry) { 96 String name = entry.getName(); 97 if (name.startsWith(splashedPackageName) && isClassFile(name)) { 98 nameList.add(name); 99 } 100 101 entry = jarIn.getNextJarEntry(); 102 } 103 104 return nameList; 105 } 106 107 private List<String> readFromDirectory(String path) { 108 File file = new File(path); 109 String[] names = file.list(); 110 111 if (null == names) { 112 return null; 113 } 114 115 return Arrays.asList(names); 116 } 117 118 private boolean isClassFile(String name) { 119 return name.endsWith(".class"); 120 } 121 122 private boolean isJarFile(String name) { 123 return name.endsWith(".jar"); 124 } 125 126 /** 127 * For test purpose. 128 */ 129 public static void main(String[] args) throws Exception { 130 PackageScanner scan = new ClasspathPackageScanner("com.scan"); 131 scan.getFullyQualifiedClassNameList(); 132 } 133 }
3.PackageScanner接口
1 package com.scan; 2 import java.io.IOException; 3 import java.util.List; 4 public interface PackageScanner { 5 public List<String> getFullyQualifiedClassNameList() throws IOException; 6 }
4.StringUtil.java
1 package com.scan; 2 3 import java.net.URL; 4 5 public class StringUtil { 6 private StringUtil() { 7 8 } 9 /** 10 * "file:/home/whf/cn/fh" -> "/home/whf/cn/fh" 11 * "jar:file:/home/whf/foo.jar!cn/fh" -> "/home/whf/foo.jar" 12 */ 13 public static String getRootPath(URL url) { 14 String fileUrl = url.getFile(); 15 int pos = fileUrl.indexOf('!'); 16 17 if (-1 == pos) { 18 return fileUrl; 19 } 20 21 return fileUrl.substring(5, pos); 22 } 23 24 /** 25 * "cn.fh.lightning" -> "cn/fh/lightning" 26 * @param name 27 * @return 28 */ 29 public static String dotToSplash(String name) { 30 return name.replaceAll("\.", "/"); 31 } 32 33 /** 34 * "Apple.class" -> "Apple" 35 */ 36 public static String trimExtension(String name) { 37 int pos = name.indexOf('.'); 38 if (-1 != pos) { 39 return name.substring(0, pos); 40 } 41 42 return name; 43 } 44 45 /** 46 * /application/home -> /home 47 * @param uri 48 * @return 49 */ 50 public static String trimURI(String uri) { 51 String trimmed = uri.substring(1); 52 int splashIndex = trimmed.indexOf('/'); 53 54 return trimmed.substring(splashIndex); 55 } 56 }
5.结果