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:
Andy Wilkinson opened SPR-14504 and commented
With reference to #19047, I am trying to add a
FailureAnalyzerto Spring Boot that outputs some advice when refresh fails due to aBeanNotOfRequiredTypeException. In doing so I have observed an unpleasant side-effect of bean creation ordering not being deterministic. Sometimes refresh will fail with aBeanNotOfRequiredTypeExceptionas the root cause and other times the root cause will be aNoSuchBeanDefinitionException. 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:
If you run it a few times you should see the two different root causes. The key thing is the order in which
asyncBeananduserare created.If
asyncBeanis created first its proxy is stored in the bean factory and, subsequently, the attempt to find anAsyncBeaninstance for injection into theuserbean method fails as there's no matching bean.If
useris created first then its creation triggers the creation ofasyncBean. I think this means thatasyncBeanis directly available to be passed into theuserbean method rather than requiring a bean factory lookup. When this fails as the types don't actually match aBeanNotOfRequiredTypeExceptionis 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
NoSuchBeanDefinitionExceptionthat points towards the proxiedasyncBeanwould be very useful.Affects: 4.3.1
Reference URL: spring-projects/spring-boot#6434
Issue Links:
@Beanregistration order within Class-reflected configuration classes@Beanmethods