Injecting beans from auto-configuration by their actual type fails
See original GitHub issueLets assume following auto-configuration class:
@AutoConfiguration
public class PersonAutoConfiguration {
@Bean
PersonService personService() {
return new PersonServiceImpl();
}
}
Injecting personService
bean by PersonServiceImpl
type:
@Component
public class MyComponent {
private final PersonServiceImpl personService;
MyComponent(PersonServiceImpl personService) {
this.personService = personService;
}
}
fails with:
No qualifying bean of type '...PersonServiceImpl' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Injecting through an interface type works without problems. Also, injecting by actual type (PersonServiceImpl
) works for test classes and applicationContext.getBean(PersonServiceImpl.class)
returns an actual bean.
As far as I can see only beans created through auto-configurations are affected. Beans created through regular configurations are injectable through their interfaces and actual classes.
This behaviour has not been observed in Spring Boot 2.7.4 but is reproducible from Spring Boot 3.0.0-M3 (I am not able to run build with older milestones due to missing dependencies).
~Sample project that reproduces this issue coming soon.~
Sample project that reproduces this issue: https://github.com/maciej-scratches/spring-boot-gh-32763 (./mvnw verify
to see the failing test).
Issue Analytics
- State:
- Created a year ago
- Reactions:2
- Comments:5 (5 by maintainers)
As far as I know, it has always been possible for this to happen.
Your
@Bean
method is hiding the type of thepersonService
bean from the bean factory. Up until the point that the bean is created, the bean factory will only know that it’s aPersonService
and injection of aPersonServiceImpl
will fail. Once thepersonService
bean has been created, the bean factory learns that it is, in fact, aPersonServiceImpl
and injection of aPersonServiceImpl
will then succeed. In other words if thepersonService
bean is created beforeMyComponent
, it will work. IfMyComponent
is created first it will fail.It’s possible that a change somewhere (I suspect Spring Framework to be the most likely place) has made it more likely that
MyComponent
is created first and for the failure to occur. However, you shouldn’t rely on a particular ordering and should instead make sure that the signature of your@Bean
method uses the most-specific type possible for its return type. There is a tip about this in Boot’s reference documentation:It’s particularly important for auto-configuration and bean conditions as the conditions are evaluated before beans are created. That means that the only type information that’s available is from the signature of the
@Bean
method. This is really a separate problem to the dependency injection failure that you had.In terms of dependency injection, the problem can occur in any Spring app. It depends entirely on the order in which the beans are created. Without explicit or implicit dependencies between beans, the ordering isn’t really guaranteed and the problem can occur if the beans are created in the “wrong” order.
Thanks for spotting the problem with
KafkaAutoConfiguration
. I’ve opened https://github.com/spring-projects/spring-boot/issues/32766.Here’s an example from the Framework docs:
This isn’t necessarily wrong. It depends on whether or not you want to be able to inject
MyServiceImpl
or only ever injectMyService
. That said, usingMyServiceImpl
as the return type won’t do any harm and, generally speaking, it’s what I would recommend. There are a few rare edge cases, such as ifMyServiceImpl
isprivate
and you’re using AOT/Native.From the Framework docs again:
Arguably, this is wrong. In the XML case, the bean factory will know that the bean’s type is
com.acme.services.MyServiceImpl
from the outset. I’ve opened https://github.com/spring-projects/spring-framework/issues/29338.