I have ImmutableBiMap filled with 2 simple Spring beans.
OS: Manjaro Linux JDK version: 1.8.0.102 Oracle Spring version: 4.3.4.RELEASE from
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>Athens-SR1</version>
Creating context throws:
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)
As following screen show, when exception is throw by Spring's BeanUtil argument is a LinkedHashMap instead of BiMap.
Minimal, Complete, and Verifiable example:
@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;
}
}
This is a side effect of how Spring handles some container types.
Even typed
Map
s can be autowired as long as the expected key type isString
. TheMap
values will contain all beans of the expected type, and the keys will contain the corresponding bean names: [...]
A BiMap
is a Map
.
Spring isn't trying to inject your automatons
bean into the TestControl
. Instead, it's trying to find all beans of type Integer
as the values, collecting them into a Map
(LinkedHashMap
as implementation of choice), and associating them with their bean name as the key.
In this case, it fails because the constructor expects a BiMap
.
One solution is to inject by name.
@Autowired()
public TestControl(@Qualifier(value = "automatons") BiMap<String, Integer> automatons) {
this.automatons = automatons;
}
By specifying a qualifier with a name, Spring will instead try to find a bean (with the appropriate type) that's named automatons
.
If you're not too attached to the final
instance field, you could also inject the field with @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;
For reasons, this will only work 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.
I would be OK with the behavior you're seeing in pre-4.3, but this does seem like a bug for Map
. (The correct behavior occurs for List
and array types.)
I've opened SPR-15117 to track it, which has now been resolved (2 day turnover, wow!).