spring-context-indexer导致的坑

故事背景

前段在与公司的另一个团队对接一些功能,自然而然的需要使用别人提供的一些SDK,故事就这样发生了,在我引入对方的sdk后,写了一天的代码,然后发现项目无法正常启动了,原因是很多基础的类都没有被注入,感觉依赖注入失效了,于是就开始了问题的排查之旅。

排查过程

首先查看错误的位置

因为异常很多所以找了一个眼熟的入手。

/**
 * 省略了一些代码
 */
public class ShiroAutoConfiguration extends AbstractShiroConfiguration {
    @Bean
    @ConditionalOnMissingBean(Realm.class)
    protected Realm missingRealm() {
        throw new NoRealmBeanConfiguredException();
    }
}

经过查看启动日志,发现时shiro的一个校验程序发生了报错,可以看到这个自动配置类会查找Realm的实现,Realm是shiro里面的一个核心类,提供了权限以及身份的验证,算是比较核心的。 代码会执行到这里就说明了一个问题,那就是Realm的实现没有被扫描到,讲道理我是一脸懵逼到,然后脑袋开始疯狂运转,因为今天确实是提交了不少的代码,然后肯定是先瞅瞅自己的提交,经过一番检查发现没啥问题,然后我想到更新了SDK,然后我就尝试将SDK移除后,发现项目可以正常启用了,自此问题定位到了是这个SDK的问题,因为这个SDK是别的团队的产品,要想掰扯掰扯也得看看是为什么,于是乎开始了第二步的排查。未待完续~~

debug

我反编译了对方的代码,看了一圈并没有发现什么异常,这里思路就断掉了,那遇事不决就只能debug了。在debug扫描时发现了一个有意思的现象,如下代码:

    //     org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
    // A分支
    if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
        return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
    }
    else {
        // B分支
        return scanCandidateComponents(basePackage);
    }
}

这里断点发现竟然走的是A分支,因为正常的逻辑应该走的是B分支,于是乎我点进了这段代码进行阅读。我首先点开了componentsIndex这个属性。发现这个属性是解析以下的这个

META-INF/spring.components

看到这里我第一时间去查看了SDK,确实是存在这个文件。于是我开始了查阅资料,首先自然是进入了官网,官网的介绍如下:

The spring-context-indexer artifact generates a META-INF/spring.components file that is included in the jar file.

When working with this mode in your IDE, the spring-context-indexer must be registered as an annotation processor to make sure the index is up-to-date when candidate components are updated.
The index is enabled automatically when a META-INF/spring.components file is found on the classpath. If an index is partially available for some libraries (or use cases) but could not be built for the whole application, you can fall back to a regular classpath arrangement (as though no index were present at all) by setting spring.index.ignore to true, either as a JVM system property or via the SpringProperties mechanism.

意思大概就是说这个是Spring的一个索引文件,原来Spring 5.0的时候引入一个模块叫做 spring-context-indexer,这个组件就是为了降低Spring的启动时间的,原理大概就是实现生成这个spring.components文件,那么也就意味可以直接读取这个文件,而无需进行遍历。这个时候我们会到A分支的代码

public Set<String> getCandidateTypes(String basePackage, String stereotype) {
    List<Entry> candidates = this.index.get(stereotype);
    if (candidates != null) {
        return candidates.parallelStream()
                .filter(t -> t.match(basePackage))
                .map(t -> t.type)
                .collect(Collectors.toSet());
    }
    return Collections.emptySet();

从代码很容易看出,这个索引文件会和我们定义的扫描路径进行取交集,至此所有的困惑解决了,接下来就是如何解决了。

方案

经过阅读源码,果然发现了一个配置项:spring.index.ignore,将这个配置项设置为true即可,不过这里还是有一个坑,这个配置是指定读取spring.properties和系统变量这两种方式的,其他的配置文件是读取不到的,至此问题解决。

总结

关于Spring的这个机制还是蛮有意思,就是可能兼容性差了些,在这个节奏的年代,并不会有那么多时间去沉淀一些事!加油吧大家!未待完续~~

猜你喜欢

转载自juejin.im/post/7123224887837065230