Affects: spring 5.3 (specifically; spring boot 2.6.6)
Sorry, I realize this is a report for an old version, but maybe this is still relevant in newer versions because google wasn't of much help to me
After changing CI server to build on linux with docker, spring boot app started to undeterministically fail with NoSuchBeanDefinitionException. Ie, it works half the time (and it used to work all the time when the jar was being built on windows). I tracked it down and I think the cause was probably this:
@Service
public class Foo {
@Autowired public void setBar(BarImpl bar) { ... }
}
@Configuration
public class Config {
@Bean
public Bar makeBar() {
return new BarImpl();
}
}
ie., a bean is registered through interface type, and autowired through implementation type. I think this is the cause, because I stepped through wiring internals with debugger, and I found container rejecting makeBar bean due to barImplType.isAssignableFrom(barType)
yielding false (where type is ResolvedType instance).
This is bad code, but regardless if it should or shouldn't work, it should behave more deterministically.
Comment From: snicoll
it should or shouldn't work, it should behave more deterministically.
The order in which the Service
and the Configuration
classes are processed may not be deterministic, which would be the reason for the failure. Unfortunately, this is impossible for us to know without more details. If you want support from us, please share a small sample we can run ourselves that replicate the problem you've described.
Comment From: arvyy
Reproduced it on spring 6.0.8
https://github.com/arvyy/SpringIssue30359
Comment From: snicoll
@arvyy thanks for the reproducer. I am assuming you must be on Windows or something as I can easily reproduce it with MacOS without the loop and docker, see https://github.com/snicoll-scratches/SpringIssue30359
In general, you should not expose an interface type if the bean is to be injected by a specific subtype. Regardless of the outcome of this issue, I highly recommend fixing that.
What's happening is that the bean initialization is not deterministic. If svc1
is not yet initialized when ServiceUser1
is initialized, the container only knows about a bean of type Service1
and there's no bean of type ServiceImpl1
yet. We'll investigate how we can make the initialization order more deterministic so that it isn't OS-specific.
Comment From: jhoeller
The root concern are the two separate @Autowired
methods on ServiceUser1
: If setAnotherService
gets resolved first, it works since AnotherService1
resolves a Service1
which implicitly makes the latter's actual bean class available. If setSvc
gets resolved first, it fails since the ServiceImpl1
bean class has not been resolved yet.
To illustrate the case: a single setServices(AnotherService1 svc1, ServiceImpl1 svc2)
method will always work, whereas a single setServices(ServiceImpl1 svc1, AnotherService1 svc2)
method will always fail.
The root cause for this is the JVM using arbitrary method ordering on reflection, returning methods in a different order between runs. We address this with ASM-based method declaration sorting for multiple @Bean
methods on the same configuration class already, maybe we should do the same for multiple @Autowired
methods on a single class.