使用反射框架org.reflections遇到的坑:Scanner SubTypesScanner was not configured

5,677 阅读3分钟

背景

先上package结构图,如下

需求是获取anno包下的AnnotationA和AnnotationB,而不需要获取AnnotationC.这三个类都被注解@ServiceMode标记.这里使用比较流行的org.reflections框架.

现象

public void testAnnotationType() {
        Reflections reflections = new Reflections(
                new ConfigurationBuilder()
                        .setScanners(
                                new TypeAnnotationsScanner(), // 设置Annotation的Scanner.
                                new SubTypesScanner() // 设置扫描子类型的scanner.
                        )
                        .setUrls(ClasspathHelper.forPackage("com.reflections.anno")) // 设置需要扫描的包,虽然指定了包路径,但是其实还是扫描整个root路径.
                        .filterInputsBy(new FilterBuilder().includePackage("com.reflections.anno")) // 因为上面的原因,所以这里加上了inputs过滤器
        );
        Set<Class<?>> types = reflections.getTypesAnnotatedWith(ServiceMode.class);  // 获取被@ServiceMode标记的类.
        for (Class<?> type : types) {
            System.out.println(type.getName());
        }
    }

执行上面的代码,就会出现异常,如下图所示:

分析

通过异常信息和异常栈来看,应该是SubTypesScanner没有配置,但是我们代码里面是在ConfigurationBuilder里面配置了SubTypeScanner的.因此只能通过查看源码来进行解析了.

这里先看Reflections类的469行.

通过代码可以发现在获取被@ServiceMode标记的类时,也会通过SubTypesScanner获取其子类.
然后我们查看Store的39行.

通过上图可以发现,该异常产生的原因并不是SubTypeScanner没有配置,而是在storeMap中没有查到相应的数据.(这里的入参Index就是SubTypesScanner的simpleName,也就是字符串SubTypesScanner).
所以我们需要查找的问题就变成了为什么storeMap.get("SubTypesScanner")查不到相应的数据.

我们从源头,也就是Reflections类的源码开始.
采用Configuration作为参数的构造函数如下.主要方法是scan();

public Reflections(final Configuration configuration) {
        this.configuration = configuration;
        store = new Store();

        if (configuration.getScanners() != null && !configuration.getScanners().isEmpty()) {
            //inject to scanners
            for (Scanner scanner : configuration.getScanners()) {
                scanner.setConfiguration(configuration);
            }

            scan();

            if (configuration.shouldExpandSuperTypes()) {
                expandSuperTypes();
            }
        }
    }

由于代码调用链较长,这里直接查看SubTypeScanner的scan(final Object cls, Store store)方法

// 这里的store是Reflections中store,就是一个存储扫描结果的容器.
@SuppressWarnings({"unchecked"})
public void scan(final Object cls, Store store) {
    String className = getMetadataAdapter().getClassName(cls);
    String superclass = getMetadataAdapter().getSuperclassName(cls);

    if (acceptResult(superclass)) { // debug发现这里返回false.
        put(store, superclass, className); // *将相应的信息放入store*
    }

    for (String anInterface : (List<String>) getMetadataAdapter().getInterfacesNames(cls)) {
        if (acceptResult(anInterface)) {
            put(store, anInterface, className);
        }
    }
}

acceptResult方法代码如下

public boolean acceptResult(final String fqn) {
	return fqn != null && resultFilter.test(fqn); // 这里的fqn为java.lang.Object
}

debug到这里的时候,发现resultFilter.test(fqn)为false.于是查看了resultFilter,发现在AbstractScanner抽象类中是默认返回true的,也就是说有地方对resultFilter重新进行了赋值才导致校验不通过,所以store中没有SubTypesScanner相关的数据.

仔细检查之后发现,如下图.

是因为SubTypesScanner的默认构造函数中excludeObjectClass为true.这就导致了父类是Object的类都不会被加入到store,因此store中根本就没有SubTypesScanner的信息.
此时store中的数据应该是如下图:

可以看到,storeMap中只有TypeAnnotationsScanner相关的数据,而没有SubTypesScanner相关的数据.

原因梳理:首先是因为我设置了InputsFilter,因此会被扫描的类就只有AnnotationA和AnnotationB,而又因为AnnotationA和AnnotationB的父类都是Object,因此这两个类就都不会被SubTypesScanner写入store中,只会被TypeAnnotationsScanner写入store中.而调用reflections.getTypesAnnotatedWith(ServiceMode.class)时,又会通过SubTypesScanner去查询,此时storeMap.get("SubTypesScanner")是null,因此才会抛出异常

解决办法

通过上面分析之后,可以发现,要想不报错,只需要保证storeMap.get("SubTypesScanner")不为null就可以.因此解决办法如下图.

调用SubTypesScanner的带参数的构造函数,并将参数设置为false.
此时storeMap的数据如下.