Description
Andy Wilkinson opened SPR-14504 and commented
With reference to #19047, I am trying to add a FailureAnalyzer
to Spring Boot that outputs some advice when refresh fails due to a BeanNotOfRequiredTypeException
. In doing so I have observed an unpleasant side-effect of bean creation ordering not being deterministic. Sometimes refresh will fail with a BeanNotOfRequiredTypeException
as the root cause and other times the root cause will be a NoSuchBeanDefinitionException
. When it's the latter there is no information to help you figure out why there was no such bean.
Here's a small class that demonstrates the problem caused by the non-deterministic ordering:
public class NonDeterministicBeanCreationOrdering {
public static void main(String[] args) {
try {
new AnnotationConfigApplicationContext(JdkProxyConfiguration.class).close();
}
catch (Exception ex) {
Throwable rootCause = getRootCause(ex);
if (!(rootCause instanceof BeanNotOfRequiredTypeException)) {
throw new IllegalStateException("Unexpected root cause", rootCause);
}
}
}
private static Throwable getRootCause(Throwable candidate) {
while (candidate.getCause() != null) {
candidate = candidate.getCause();
}
return candidate;
}
@Configuration
@EnableAsync
static class JdkProxyConfiguration {
@Bean
public AsyncBean asyncBean() {
return new AsyncBean();
}
@Bean
public AsyncBeanUser user(AsyncBean bean) {
return new AsyncBeanUser(bean);
}
}
static class AsyncBean implements SomeInterface {
@Async
public void foo() {
}
@Override
public void bar() {
}
}
static interface SomeInterface {
void bar();
}
static class AsyncBeanUser {
AsyncBeanUser(AsyncBean asyncBean) {
}
}
}
If you run it a few times you should see the two different root causes. The key thing is the order in which asyncBean
and user
are created.
If asyncBean
is created first its proxy is stored in the bean factory and, subsequently, the attempt to find an AsyncBean
instance for injection into the user
bean method fails as there's no matching bean.
If user
is created first then its creation triggers the creation of asyncBean
. I think this means that asyncBean
is directly available to be passed into the user
bean method rather than requiring a bean factory lookup. When this fails as the types don't actually match a BeanNotOfRequiredTypeException
is thrown with details of the actual type.
Ideally, the ordering would be deterministic, but I realise that's almost certainly not possible in 4.3 and perhaps at all. Failing that, some information in the NoSuchBeanDefinitionException
that points towards the proxied asyncBean
would be very useful.
Affects: 4.3.1
Reference URL: spring-projects/spring-boot#6434
Issue Links:
- Mis-proxying of Mockito mock and poor diagnostics for type mismatch on proxy injection [SPR-14478] #19047 Mis-proxying of Mockito mock and poor diagnostics for type mismatch on proxy injection
- Deterministic and JVM-independent @Bean registration order within Class-reflected configuration classes [SPR-14505] #19074 Deterministic and JVM-independent
@Bean
registration order within Class-reflected configuration classes - Consistent autowiring behavior for specifically typed injection points against loosely typed @Bean methods [SPR-14960] #19527 Consistent autowiring behavior for specifically typed injection points against loosely typed
@Bean
methods