要扫描包下的所有类,分类路径下扫描和jar包扫描两种,其中jar包扫描又分项目中引入的三方jar包,同级maven的多个子项目jar相互引用,还有jdk jar包(这里不考虑,一般没哪个项目会扫描jdk jar包里的类).
我先声明一个接口,用于应对不同类型的class扫描:
public interface Scan {
String CLASS_SUFFIX = ".class";
Set> search(String packageName, Predicate> predicate);
default Set> search(String packageName){
return search(packageName,null);
}
}
其中 Predicate 是jdk8新增的Lambda表达式相关内容,可以筛选要扫描的类。
从类路径扫描指定包下的类:
public class FileScanner implements Scan {
private String defaultClassPath = FileScanner.class.getResource("/").getPath();
public String getDefaultClassPath() {
return defaultClassPath;
}
public void setDefaultClassPath(String defaultClassPath) {
this.defaultClassPath = defaultClassPath;
}
public FileScanner(String defaultClassPath) {
this.defaultClassPath = defaultClassPath;
}
public FileScanner(){}
private static class ClassSearcher{
private Set> classPaths = new HashSet<>();
private Set> doPath(File file,String packageName, Predicate> predicate,boolean flag) {
if (file.isDirectory()) {
//文件夹我们就递归
File[] files = file.listFiles();
if(!flag){
packageName = packageName+"."+file.getName();
}
for (File f1 : files) {
doPath(f1,packageName,predicate,false);
}
} else {//标准文件
//标准文件我们就判断是否是class文件
if (file.getName().endsWith(CLASS_SUFFIX)) {
//如果是class文件我们就放入我们的集合中。
try {
Class> clazz = Class.forName(packageName + "."+ file.getName().substring(0,file.getName().lastIndexOf(".")));
if(predicate==null||predicate.test(clazz)){
classPaths.add(clazz);
}
} catch (ClassNotFoundException e) {
throw new ScannerClassException(e.getMessage(),e);
}
}
}
return classPaths;
}
}
@Override
public Set> search(String packageName, Predicate> predicate) {
//先把包名转换为路径,首先得到项目的classpath
String classpath = defaultClassPath;
//然后把我们的包名basPack转换为路径名
String basePackPath = packageName.replace(".", File.separator);
String searchPath = classpath + basePackPath;
return new ClassSearcher().doPath(new File(searchPath),packageName, predicate,true);
}
}
从jar中扫描相关类:
public class JarScanner implements Scan {
@Override
public Set> search(String packageName, Predicate> predicate) {
Set> classes = new HashSet<>();
try {
//通过当前线程得到类加载器从而得到URL的枚举
Enumeration urlEnumeration = Thread.currentThread().getContextClassLoader().getResources(packageName.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
if ("jar".equalsIgnoreCase(protocol)) {
//转换为JarURLConnection
JarURLConnection connection = (JarURLConnection) url.openConnection();
if (connection != null) {
JarFile jarFile = connection.getJarFile();
if (jarFile != null) {
//得到该jar文件下面的类实体
Enumeration 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(packageName)) {
String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replace("/", ".");
Class cls = Class.forName(className);
if(predicate == null || predicate.test(cls)){
classes.add(cls);
}
}
}
}
}
}else if("file".equalsIgnoreCase(protocol)){
//从maven子项目中扫描
FileScanner fileScanner = new FileScanner();
fileScanner.setDefaultClassPath(url.getPath().replace(packageName.replace(".", "/"),""));
classes.addAll(fileScanner.search(packageName,predicate));
}
}
}catch (ClassNotFoundException | IOException e){
throw new ScannerClassException(e.getMessage(),e);
}
return classes;
}
}
之后,我们可以使用一个委派模式对以上两个结果进行合并,得出扫描结果:
public class ScanExecutor implements Scan {
private volatile static ScanExecutor instance;
@Override
public Set> search(String packageName, Predicate> predicate) {
Scan fileSc = new FileScanner();
Set> fileSearch = fileSc.search(packageName, predicate);
Scan jarScanner = new JarScanner();
Set> jarSearch = jarScanner.search(packageName,predicate);
fileSearch.addAll(jarSearch);
return fileSearch;
}
private ScanExecutor(){}
public static ScanExecutor getInstance(){
if(instance == null){
synchronized (ScanExecutor.class){
if(instance == null){
instance = new ScanExecutor();
}
}
}
return instance;
}
}
封装一个工具类,方便调用:
public class ClassScannerUtils {
public static Set> searchClasses(String packageName){
return searchClasses(packageName,null);
}
public static Set> searchClasses(String packageName, Predicate predicate){
return ScanExecutor.getInstance().search(packageName,predicate);
}
}
源码地址:
https://gitee.com/luhui123/luhui_library/tree/master/commons_utils/src/main/java/com/luhui/commons/scanclass