使用Java写出扫描包的工具
1.前言
通过扫描包的工具,可以使Java在运行时动态加载类。
2.正文
本文主要是带你们如何制作一个简单的扫描包的工具。主要思想分为,第一步通过获取当前线程来获取到当前文件的根目录,第二步根据根目录进行递归扫描,若结尾位.class则为Java类,第三步把该类的全限定名放入容器中,方便后续利用反射加载该类。
2.1.第一步思想
Class.getResource("");是获取该文件的绝对路径,Class.getResource("/");是获取该项目的绝对路径。
private File replacePointToFileString() {
File file;
if (EMPTY_STRING.equals(basePackage)) {
file = new File(
Thread.currentThread().getContextClassLoader().getResource(EMPTY_STRING).getPath(),
SPRIT_STRING
);
} else {
file = new File(
Thread.currentThread().getContextClassLoader().getResource(EMPTY_STRING).getPath(),
basePackage.replace(POINT_CHAR, SPRIT_CHAR)
);
}
return file;
}
2.2.第二步思想
主要是通过简单的递归来获取class文件。在递归之前,先做一些准备工作,如下。
private List<String> findClasses(File file) {
List<String> classPathContainer = new ArrayList<>();
String basePackageLocal;
int index = basePackage.lastIndexOf(POINT_STRING);
if (!EMPTY_STRING.equals(basePackage) && index != -1) {
basePackageLocal = basePackage.substring(0, index);
} else {
basePackageLocal = EMPTY_STRING;
}
findAnyPathJava(file, classPathContainer, basePackageLocal);
return classPathContainer;
}
开始递归,如下所示,需要注意的是最后一个参数就是全限定名的前缀,利用了递归的特性,仔细琢磨琢磨。
private void findAnyPathJava(File file, List<String> container, String name) {
if (file.isDirectory() && file.listFiles() != null) {
for (File childFile : file.listFiles()) {
if (EMPTY_STRING.equals(name)) {
findAnyPathJava(childFile, container, file.getName());
continue;
}
findAnyPathJava(childFile, container, name + POINT_STRING + file.getName());
}
} else if (file.isFile() && file.getName().endsWith(POINT_CLASS_STRING)) {
container.add(name + POINT_STRING +
file.getName().substring(0, file.getName().lastIndexOf(POINT_STRING)));
}
}
2.3.全部代码
package vip.wulang.spring.scanner;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* The class is used for scanning packages.
*
* @author CoolerWu on 2018/12/9.
* @version 1.0
*/
@SuppressWarnings("all")
public class PackageScanner {
private static final String EMPTY_STRING = "";
private static final String POINT_STRING = ".";
private static final char POINT_CHAR = '.';
private static final char SPRIT_CHAR = '/';
private static final String POINT_CLASS_STRING = ".class";
private static final String SPRIT_STRING = "/";
private String basePackage;
public PackageScanner() {
basePackage = EMPTY_STRING;
}
public PackageScanner(String basePackage) {
this.basePackage = basePackage;
}
public List<String> startPackageScan() {
File rootFile = replacePointToFileString();
List<String> container = findClasses(rootFile);
return container;
}
private File replacePointToFileString() {
File file;
if (EMPTY_STRING.equals(basePackage)) {
file = new File(
Thread.currentThread().getContextClassLoader().getResource(EMPTY_STRING).getPath(),
SPRIT_STRING
);
} else {
file = new File(
Thread.currentThread().getContextClassLoader().getResource(EMPTY_STRING).getPath(),
basePackage.replace(POINT_CHAR, SPRIT_CHAR)
);
}
return file;
}
private List<String> findClasses(File file) {
List<String> classPathContainer = new ArrayList<>();
String basePackageLocal;
int index = basePackage.lastIndexOf(POINT_STRING);
if (!EMPTY_STRING.equals(basePackage) && index != -1) {
basePackageLocal = basePackage.substring(0, index);
} else {
basePackageLocal = EMPTY_STRING;
}
findAnyPathJava(file, classPathContainer, basePackageLocal);
return classPathContainer;
}
private void findAnyPathJava(File file, List<String> container, String name) {
if (file.isDirectory() && file.listFiles() != null) {
for (File childFile : file.listFiles()) {
if (EMPTY_STRING.equals(name)) {
findAnyPathJava(childFile, container, file.getName());
continue;
}
findAnyPathJava(childFile, container, name + POINT_STRING + file.getName());
}
} else if (file.isFile() && file.getName().endsWith(POINT_CLASS_STRING)) {
container.add(name + POINT_STRING +
file.getName().substring(0, file.getName().lastIndexOf(POINT_STRING)));
}
}
}
其中类中 startPackageScan() 方法是启动整个流程代码执行的启动器。replacePointToFileString() 方法是把类似于"vip.wulang.base",转换成"F://Dev/Demo/target/classes/vip/wulang/base",这样我们就可以使用 File 类进行递归操作。findAnyPathJava() 就是一直递归,把Java文件的全限定名放进 container 容器中,具体算法稍微读一下就可以知道原理了。
3.运行测试及结果
package vip.wulang.test;
import vip.wulang.spring.scanner.PackageScanner;
/**
* @author CoolerWu on 2018/12/12.
* @version 1.0
*/
public class PackageScannerTest {
public static void main(String[] args) {
System.out.println(new PackageScanner().startPackageScan());
}
}
现在我们有了所有Java类的全限定名,可以用:Class.forName(“vip.wulang.spring.core.User”),会获取到该类的class文件,也就是加载该文件到内存中。
4.结语
学如逆水行舟,不进则退。