004 关于Java如何扫描指定package下所有的类

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.结果

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值