Skip to content

Non-helpful NoSuchBeanDefinitionException rather than BeanNotOfRequiredTypeException due to creation order [SPR-14504] #19073

Closed
@spring-projects-issues

Description

@spring-projects-issues

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:

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions