fix ClassScanner bug

This commit is contained in:
Looly 2021-07-12 18:53:24 +08:00
parent bee3056844
commit 3160742be1
3 changed files with 91 additions and 13 deletions

View File

@ -9,6 +9,7 @@
### 🐞Bug修复 ### 🐞Bug修复
* 【core 】 修复FileUtil.normalize处理上级路径的问题issue#I3YPEH@Gitee * 【core 】 修复FileUtil.normalize处理上级路径的问题issue#I3YPEH@Gitee
* 【core 】 修复ClassScanner扫描空包遗漏问题
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------

View File

@ -33,7 +33,8 @@ public class ClassScanner implements Serializable {
*/ */
private final String packageName; private final String packageName;
/** /**
* 包名最后跟一个点表示包名避免在检查前缀时的歧义 * 包名最后跟一个点表示包名避免在检查前缀时的歧义<br>
* 如果包名指定为空不跟点
*/ */
private final String packageNameWithDot; private final String packageNameWithDot;
/** /**
@ -66,29 +67,63 @@ public class ClassScanner implements Serializable {
private final Set<Class<?>> classes = new HashSet<>(); private final Set<Class<?>> classes = new HashSet<>();
/** /**
* 扫描指定包路径下所有包含指定注解的类 * 扫描指定包路径下所有包含指定注解的类包括其他加载的jar或者类
* *
* @param packageName 包路径 * @param packageName 包路径
* @param annotationClass 注解类 * @param annotationClass 注解类
* @return 类集合 * @return 类集合
*/ */
public static Set<Class<?>> scanPackageByAnnotation(String packageName, final Class<? extends Annotation> annotationClass) { public static Set<Class<?>> scanAllPackageByAnnotation(String packageName, Class<? extends Annotation> annotationClass) {
return scanAllPackage(packageName, clazz -> clazz.isAnnotationPresent(annotationClass));
}
/**
* 扫描指定包路径下所有包含指定注解的类<br>
* 如果classpath下已经有类不再扫描其他加载的jar或者类
*
* @param packageName 包路径
* @param annotationClass 注解类
* @return 类集合
*/
public static Set<Class<?>> scanPackageByAnnotation(String packageName, Class<? extends Annotation> annotationClass) {
return scanPackage(packageName, clazz -> clazz.isAnnotationPresent(annotationClass)); return scanPackage(packageName, clazz -> clazz.isAnnotationPresent(annotationClass));
} }
/** /**
* 扫描指定包路径下所有指定类或接口的子类或实现类不包括指定父类本身 * 扫描指定包路径下所有指定类或接口的子类或实现类不包括指定父类本身包括其他加载的jar或者类
* *
* @param packageName 包路径 * @param packageName 包路径
* @param superClass 父类或接口不包括 * @param superClass 父类或接口不包括
* @return 类集合 * @return 类集合
*/ */
public static Set<Class<?>> scanPackageBySuper(String packageName, final Class<?> superClass) { public static Set<Class<?>> scanAllPackageBySuper(String packageName, Class<?> superClass) {
return scanAllPackage(packageName, clazz -> superClass.isAssignableFrom(clazz) && !superClass.equals(clazz));
}
/**
* 扫描指定包路径下所有指定类或接口的子类或实现类不包括指定父类本身<br>
* 如果classpath下已经有类不再扫描其他加载的jar或者类
*
* @param packageName 包路径
* @param superClass 父类或接口不包括
* @return 类集合
*/
public static Set<Class<?>> scanPackageBySuper(String packageName, Class<?> superClass) {
return scanPackage(packageName, clazz -> superClass.isAssignableFrom(clazz) && !superClass.equals(clazz)); return scanPackage(packageName, clazz -> superClass.isAssignableFrom(clazz) && !superClass.equals(clazz));
} }
/** /**
* 扫描该包路径下所有class文件 * 扫描该包路径下所有class文件包括其他加载的jar或者类
*
* @return 类集合
* @since 5.7.5
*/
public static Set<Class<?>> scanAllPackage() {
return scanAllPackage(StrUtil.EMPTY, null);
}
/**
* 扫描classpath下所有class文件如果classpath下已经有类不再扫描其他加载的jar或者类
* *
* @return 类集合 * @return 类集合
*/ */
@ -106,6 +141,20 @@ public class ClassScanner implements Serializable {
return scanPackage(packageName, null); return scanPackage(packageName, null);
} }
/**
* 扫描包路径下和所有在classpath中加载的类满足class过滤器条件的所有class文件<br>
* 如果包路径为 com.abs + A.class 但是输入 abs会产生classNotFoundException<br>
* 因为className 应该为 com.abs.A 现在却成为abs.A,此工具类对该异常进行忽略处理<br>
*
* @param packageName 包路径 com | com. | com.abs | com.abs.
* @param classFilter class过滤器过滤掉不需要的class
* @return 类集合
* @since 5.7.5
*/
public static Set<Class<?>> scanAllPackage(String packageName, Filter<Class<?>> classFilter) {
return new ClassScanner(packageName, classFilter).scan(true);
}
/** /**
* 扫描包路径下满足class过滤器条件的所有class文件<br> * 扫描包路径下满足class过滤器条件的所有class文件<br>
* 如果包路径为 com.abs + A.class 但是输入 abs会产生classNotFoundException<br> * 如果包路径为 com.abs + A.class 但是输入 abs会产生classNotFoundException<br>
@ -163,11 +212,23 @@ public class ClassScanner implements Serializable {
} }
/** /**
* 扫描包路径下满足class过滤器条件的所有class文件 * 扫描包路径下满足class过滤器条件的所有class文件<br>
* 此方法首先扫描指定包名下的资源目录如果未扫描到则扫描整个classpath中所有加载的类
* *
* @return 类集合 * @return 类集合
*/ */
public Set<Class<?>> scan() { public Set<Class<?>> scan() {
return scan(false);
}
/**
* 扫描包路径下满足class过滤器条件的所有class文件
*
* @param forceScanJavaClassPaths 是否强制扫描其他位于classpath关联jar中的类
* @return 类集合
* @since 5.7.5
*/
public Set<Class<?>> scan(boolean forceScanJavaClassPaths) {
for (URL url : ResourceUtil.getResourceIter(this.packagePath)) { for (URL url : ResourceUtil.getResourceIter(this.packagePath)) {
switch (url.getProtocol()) { switch (url.getProtocol()) {
case "file": case "file":
@ -179,7 +240,8 @@ public class ClassScanner implements Serializable {
} }
} }
if (CollUtil.isEmpty(this.classes)) { // classpath下未找到则扫描其他jar包下的类
if (forceScanJavaClassPaths || CollUtil.isEmpty(this.classes)) {
scanJavaClassPaths(); scanJavaClassPaths();
} }
@ -245,7 +307,7 @@ public class ClassScanner implements Serializable {
} }
} else if (file.isDirectory()) { } else if (file.isDirectory()) {
final File[] files = file.listFiles(); final File[] files = file.listFiles();
if(null != files){ if (null != files) {
for (File subFile : files) { for (File subFile : files) {
scanFile(subFile, (null == rootDir) ? subPathBeforePackage(file) : rootDir); scanFile(subFile, (null == rootDir) ? subPathBeforePackage(file) : rootDir);
} }
@ -262,7 +324,7 @@ public class ClassScanner implements Serializable {
String name; String name;
for (JarEntry entry : new EnumerationIter<>(jar.entries())) { for (JarEntry entry : new EnumerationIter<>(jar.entries())) {
name = StrUtil.removePrefix(entry.getName(), StrUtil.SLASH); name = StrUtil.removePrefix(entry.getName(), StrUtil.SLASH);
if (name.startsWith(this.packagePath)) { if (StrUtil.isEmpty(packagePath) || name.startsWith(this.packagePath)) {
if (name.endsWith(FileUtil.CLASS_EXT) && false == entry.isDirectory()) { if (name.endsWith(FileUtil.CLASS_EXT) && false == entry.isDirectory()) {
final String className = name// final String className = name//
.substring(0, name.length() - 6)// .substring(0, name.length() - 6)//
@ -289,13 +351,12 @@ public class ClassScanner implements Serializable {
Class<?> clazz = null; Class<?> clazz = null;
try { try {
clazz = Class.forName(className, this.initialize, loader); clazz = Class.forName(className, this.initialize, loader);
} catch (NoClassDefFoundError e) { } catch (NoClassDefFoundError | ClassNotFoundException e) {
// 由于依赖库导致的类无法加载直接跳过此类 // 由于依赖库导致的类无法加载直接跳过此类
} catch (UnsupportedClassVersionError e) { } catch (UnsupportedClassVersionError e) {
// 版本导致的不兼容的类跳过 // 版本导致的不兼容的类跳过
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
// Console.error(e);
} }
return clazz; return clazz;
} }
@ -318,7 +379,7 @@ public class ClassScanner implements Serializable {
} }
} else if (classLen > packageLen) { } else if (classLen > packageLen) {
//检查类名是否以指定包名为前缀包名后加.避免类似于cn.hutool.A和cn.hutool.ATest这类类名引起的歧义 //检查类名是否以指定包名为前缀包名后加.避免类似于cn.hutool.A和cn.hutool.ATest这类类名引起的歧义
if (className.startsWith(this.packageNameWithDot)) { if (".".equals(this.packageNameWithDot) || className.startsWith(this.packageNameWithDot)) {
addIfAccept(loadClass(className)); addIfAccept(loadClass(className));
} }
} }

View File

@ -16,4 +16,20 @@ public class ClassScanerTest {
Console.log(clazz.getName()); Console.log(clazz.getName());
} }
} }
@Test
@Ignore
public void scanPackageBySuperTest(){
// 扫描包如果在classpath下找到就不扫描JDK的jar了
final Set<Class<?>> classes = ClassScanner.scanPackageBySuper(null, Iterable.class);
Console.log(classes.size());
}
@Test
@Ignore
public void scanAllPackageBySuperTest(){
// 扫描包如果在classpath下找到就不扫描JDK的jar了
final Set<Class<?>> classes = ClassScanner.scanAllPackageBySuper(null, Iterable.class);
Console.log(classes.size());
}
} }