Guava ImmutableBiMap 变为 LinkedHashMap 并导致 Spring 自动装配错误

Guava ImmutableBiMap becomes LinkedHashMap and cause Spring autowiring mistake

我有 ImmutableBiMap 填充了 2 个简单的 Spring bean。

OS:曼扎罗 Linux JDK版本:1.8.0.102甲骨文 Spring 版本:4.3.4.RELEASE 来自

<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>Athens-SR1</version>

创建上下文抛出:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: 
    Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [...]: Illegal arguments for constructor; nested exception is java.lang.IllegalArgumentException: argument type mismatch
    Caused by: java.lang.IllegalArgumentException: argument type mismatch
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)

如下图所示,当Spring的BeanUtil参数抛出异常时,参数是LinkedHashMap而不是BiMap。

最小、完整且可验证的示例:

@Component
@Slf4j
public class TestControl {
    private final BiMap<String, Integer> automatons;

    @Autowired
    public TestControl(BiMap<String, Integer> automatons) {
        this.automatons = automatons;
        log.info("automatons={}", automatons.keySet());
    }
}

@Configuration
public class TextContext {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(
                TextContext.class,
                TestControl.class
        );
        BiMap bean = context.getBean(BiMap.class);
    }

    @Bean
    BiMap<String, Integer> automatons() {
        return ImmutableBiMap.of(
                "Cellular Automaton", cellularAutomaton(),
                "Monte Carlo Automaton", monteCarloAutomaton());
    }

    @Bean
    Integer cellularAutomaton() {
       return 6;
    }

    @Bean
    Integer monteCarloAutomaton() {
       return 5;
    }
}

除非 Spring 中存在巨大错误(我对此表示怀疑),否则这一定是 human/editor 错误。

我重新创建了一个稍微简单一些的示例,与我刚刚使用的字符串、整数、长整型和布尔值相同的基础知识,因为我没有您的类型 - 这个简单的示例有效。

LinkedHashMap 不是 BiMap,所以如果它被选为自动装配候选者,那将是一个错误。听起来好像源代码和编译代码不同步,您是否尝试过删除构建文件夹并重建?

如果重建没有帮助,解决这个问题的唯一方法就是老式调试。

  1. 在LinkedHashMaps的构造函数里面打个断点,看看是在哪里构造的,跟你的bean有关系吗?
  2. 在org.springframework.beans.factory.support.ConstructorResolver#autowireConstructor 中设置一个条件断点(所以你只在beanName.equals("automatonTypeSettingsControl")时停止,然后逐步执行该方法,这样你就可以看到spring 找到自动装配候选人;
  3. 做一个失败的最简单的独立例子,把它放在Github和post上link,然后其他人可能会帮你调试。

观察:上个月看了很多Whosebugpost,看来一般的开发者不太擅长调试第三方代码。你实际上可以从调试别人的代码中学到很多东西,尤其是 spring 框架代码,考虑到它正在解决的问题,我觉得它很容易阅读。

Edit 这原来是 Spring 中的限制,如另一个答案中所述。也就是说,我最终重现了错误并阅读了 Spring 代码以在大约 1 小时内找到此行为的确切代码。我觉得许多开发人员忽视了调试作为一门软件学科。对我来说,这是最重要的学科之一,因为您可能大部分时间都在处理不是您自己编写的代码。

这是 how Spring handles some container types 的副作用。

Even typed Maps can be autowired as long as the expected key type is String. The Map values will contain all beans of the expected type, and the keys will contain the corresponding bean names: [...]

一个BiMap是一个Map

Spring 并没有尝试将您的 automatons bean 注入 TestControl。相反,它试图找到所有 Integer 类型的 beans 作为值,将它们收集到 MapLinkedHashMap 作为选择的实现),并将它们与它们的 bean 名称相关联作为键.

在这种情况下,它失败了,因为构造函数需要 BiMap

一种解决方案是按名称注入。

@Autowired()
public TestControl(@Qualifier(value = "automatons") BiMap<String, Integer> automatons) {
    this.automatons = automatons;
}

通过指定带有名称的限定符,Spring 将改为尝试查找名为 automatons 的 bean(具有适当的类型)。

如果你不太依赖 final 实例字段,你也可以用 @Resource

注入该字段
@Resource(name = "automatons") // if you don't specify the name element, Spring will try to use the field name
private BiMap<String, Integer> automatons;

出于原因,这仅适用于 4.3+。

For beans that are themselves defined as a collection/map or array type, @Resource is a fine solution, referring to the specific collection or array bean by unique name. That said, as of 4.3, collection/map and array types can be matched through Spring’s @Autowired type matching algorithm as well, as long as the element type information is preserved in @Bean return type signatures or collection inheritance hierarchies. In this case, qualifier values can be used to select among same-typed collections, as outlined in the previous paragraph.

我可以接受您在 4.3 之前的版本中看到的行为,但这对于 Map 来说确实像是一个错误。 (正确的行为发生在 List 和数组类型。)

我已经打开SPR-15117跟踪了,现在已经解决了(2天成交量,哇!)。