- 获取Class类的集合
- 单元测试
获取某个包下的类集合 代码如下 . 主要是三步
- 获取类加载器: 通过
Thread.currentThread().getContextClassLoader()
来获取 - 通过类加载器获取到加载的资源信息 , 是通过
classLoader.getResource
获取, 传入包路径, 注意要把. , 转化为"/". - 获取类的集合: 主要是 过滤出文件类型, 目前只处理file类型的协议, 再获取包下的实际绝对路径, 最后是通过绝对路径, 去递归获取类的集合 .
完整代码如下
/**
* 获取某个包下的类集合 :
* 1. 获取到类加载器:
* 目的: 获取项目发布的实际路径 . 不同的系统表示路径的方法不同. 如果是war或jar包就找不到jar包.
* 类加载器: ClassLoader
*
* 2. 通过类加载器获取到加载的资源信息
* 3. 根据不同的资源类型, 采取不同的方式获取资源的集合
*
* @param packageName 包名
* @return 类集合
*/
public static Set extractPackageClass(String packageName) {
// 1. 获取到类加载器
ClassLoader classLoader = getClassLoader();
// 2. 通过类加载器获取到加载的资源信息
URL url = classLoader.getResource(packageName.replace(".", "/"));
if (url == null) {
log.error(" 无法获取到资源, 从此包中 : " + packageName);
return null;
}
// 3. 根据不同的资源类型(目前只解析file类型的), 采取不同的方式获取资源的集合
Set classSet = null;
// 过滤出文件类型资源
if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)) {
classSet = new HashSet();
// 获取package的实际路径
File packageDirectory = new File(url.getPath());
// 递归获取目标package 里面的所有class文件(包括子package里的class文件)
extractClassFile(classSet, packageDirectory, packageName);
}
// 返回所有的类实例
return classSet;
}
/**
* 递归获取目标package里面的所有class文件(包括子package里的class文件)
*
* @param emptyClassSet 装载目标类的集合
* @param fileSource 目录或文件
* @param packageName 包名
*/
private static void extractClassFile(Set emptyClassSet, File fileSource, String packageName) {
if (!fileSource.isDirectory()) {
// 如果资源不是文件夹, 那么直接结束
return;
}
// 获取一个文件夹下的所有文件或者文件夹, 不包含子文件夹
File[] files = fileSource.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
if (file.isDirectory()) {
// 筛选出只含有文件夹的资源
return true;
} else {
//获取文件的绝对值路径
String absolutePath = file.getAbsolutePath();
if (absolutePath.endsWith(".class")) {
// 如果是class文件, 加载到内存中
addToClassSet(absolutePath);
}
}
return false;
}
// 根据class文件的绝对值路径, 获取并生成class对象, 并放入classSet中
private void addToClassSet(String absolutePath) {
// 1. 从class文件的绝对值路径里提取出包含了package的类名
// 如absolutePath D:\mycode\spring_study\simpleframework\src\main\java\org\simpleframework\core\annotation\Component.class
// 如 org.simpleframework.core.annotation.Component
// 把\ 换成 .
absolutePath = absolutePath.replace(File.separator, ".");
// 例如传入的包名为org.simpleframework.core 代表从 org.simpleframework.core 开始截取
String className = absolutePath.substring(absolutePath.indexOf(packageName));
// 去掉 末尾的class
className = className.substring(0, className.lastIndexOf("."));
// 2. 通过反射机制, 获取对应的class对象并加入到classSet里
Class targetClass = loadClass(className);
// 把加载的类放入集合中
emptyClassSet.add(targetClass);
}
});
// 迭代遍历之前, 先判断是否为空
if (files != null) {
for (File f : files) {
// 递归调用
extractClassFile(emptyClassSet, f, packageName);
}
}
}
/**
* 获取class 对象
*
* @param className class全名 = package+类名
* @return
*/
public static Class loadClass(String className) {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
// 加载类异常
log.error("load class error", e);
throw new RuntimeException(e);
}
}
/**
* 获取当前的ClassLoader
* 程序是通过线程执行的, 获取当前执行的方法的线程, 便能通过线程属的类加载器获取程序资源信息
*
* @return
*/
public static ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
单元测试
pom中加入如下的依赖
org.junit.jupiter
junit-jupiter-api
5.5.2
test
在test包中, 编写如下的测试类
public class ClassUtilTest {
// 表明测试用例的含义
@DisplayName("extractPackageClassTest 提取目标类方法")
@Test
public void extractPackageClassTest(){
Set classes = ClassUtil.extractPackageClass("demo.annotation");
if (classes != null) {
for (Class aClass : classes) {
System.out.println(aClass);
}
}
Assertions.assertEquals(5, classes.size());
}
}
在我当前的项目中, demo.annotation包下有五个类, 因此在上面的断言中, 我断言了5个. 控制台打印如下 , 测试通过 , 并且成功打印获取了如下的五个类.