问题
最近在SpringBoot 2.x项目中使用Reflections
工具的时候(Jar包的版本是org.reflections:reflections:0.10.2
),发现在IntelliJ IDEA中运行是能正常扫描出Class对象,但是部署在测试环境或者本地以Jar报运行时,扫描不出来,所以Debug了Reflections
源码。
原因
因为我创建Reflections
对象写法是这样的
java">Reflections reflections = new Reflections("com.jay.userinterface", Scanners.MethodsAnnotated);
点进去最终会调用这个方法来初始化配置
java">public static ConfigurationBuilder build(Object... params) {
final ConfigurationBuilder builder = new ConfigurationBuilder();
// flatten
List<Object> parameters = new ArrayList<>();
for (Object param : params) {
if (param.getClass().isArray()) { for (Object p : (Object[]) param) parameters.add(p); }
else if (param instanceof Iterable) { for (Object p : (Iterable) param) parameters.add(p); }
else parameters.add(param);
}
ClassLoader[] loaders = Stream.of(params).filter(p -> p instanceof ClassLoader).distinct().toArray(ClassLoader[]::new);
if (loaders.length != 0) { builder.addClassLoaders(loaders); }
FilterBuilder inputsFilter = new FilterBuilder();
builder.filterInputsBy(inputsFilter);
for (Object param : parameters) {
if (param instanceof String && !((String) param).isEmpty()) {
builder.forPackage((String) param, loaders);
inputsFilter.includePackage((String) param);
} else if (param instanceof Class && !Scanner.class.isAssignableFrom((Class) param)) {
builder.addUrls(ClasspathHelper.forClass((Class) param, loaders));
inputsFilter.includePackage(((Class) param).getPackage().getName());
} else if (param instanceof URL) {
builder.addUrls((URL) param);
} else if (param instanceof Scanner) {
builder.addScanners((Scanner) param);
} else if (param instanceof Class && Scanner.class.isAssignableFrom((Class) param)) {
try { builder.addScanners(((Class<Scanner>) param).getDeclaredConstructor().newInstance()); }
catch (Exception e) { throw new RuntimeException(e); }
} else if (param instanceof Predicate) {
builder.filterInputsBy((Predicate<String>) param);
} else throw new ReflectionsException("could not use param '" + param + "'");
}
if (builder.getUrls().isEmpty()) {
// scan all classpath if no urls provided todo avoid
builder.addUrls(ClasspathHelper.forClassLoader(loaders));
}
return builder;
}
在For循环中第一个if,如果是String类型的参数,就会设置package,然后会设置filter,问题就出现在这里。这种简写方式,filter是和包名一样的。在本地IDEA中,所有文件都是在out目录下,文件的目录是正常包名开头,如下格式
java">com/jay/userinterface/authority/model/dto/ProductVO.class
但是如果是以Jar包运行的方式,因为SpringBoot 2.x版本打包时会做一些处理(加一些启动类),导致文件结构会发生变化(读者可以解压一个SpringBoot的Jar包看看实际结构)。这种情况下,获取文件的名称是如下格式
java">BOOT-INF/classes/com/jay/userinterface/authority/model/dto/ProductVO.class
在扫描过程中,这些文件都过不了Filter的校验。
大致过程就是在Reflections
类中scan()
方法,对每个文件都会根据Filter过滤下,这个Filter就是一个正则表达式的匹配,表达式就是com/jay/userinterface/*
,以Jar包方式运行的话,所有文件都会被过滤掉,扫描结果就为空。
解决方案
使用另外一种写法,自己构造Reflections
的 Configuration
,手动设置Filter
java">Reflections reflections = new Reflections(new ConfigurationBuilder()
.forPackages(packageName)
.filterInputsBy(new FilterBuilder().includePackage("BOOT-INF.classes." + packageName))
.setScanners(Scanners.MethodsAnnotated));
我这里面是直接加了前缀,每个文件在Jar包中的实际路径,和Springboot打包后文件格式有关,Springboot 1.x版本应该是不用改的。
也可以通过FilterBuilder
的includePattern()
方法来直接写正则表达式,兼容IDEA运行和Jar包运行的方式。