Skip to content

CastClass exception when wiring Map of beans (NullBean instead of 'null' in the map) [SPR-16033] #20582

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
spring-projects-issues opened this issue Oct 2, 2017 · 13 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Oct 2, 2017

Francisco Lozano opened SPR-16033 and commented

Offending code has been working since 4.1 at least, up until 5.0.0.RC3. It just started failing in GA.

java.lang.IllegalStateException: Failed to load ApplicationContext
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125)
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:107)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:242)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.mycompany.platform.server.healthcheck.HealthcheckConfiguration': Unsatisfied dependency expressed through field 'healthcheckConfigSets'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'eventListenerAdapterHealthcheckConfigSet' defined in class path resource [com/mycompany/platform/event/provider/EventBusHealthcheckConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.mycompany.common.healthcheck.service.HealthcheckConfigSet]: Factory method 'eventListenerAdapterHealthcheckConfigSet' threw exception; nested exception is java.lang.ClassCastException: org.springframework.beans.factory.support.NullBean cannot be cast to com.mycompany.platform.event.bus.EventListenerAdapter
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:581)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:91)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:367)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1340)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:502)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:312)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:310)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:756)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:868)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:128)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:109)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:246)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
	... 26 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'eventListenerAdapterHealthcheckConfigSet' defined in class path resource [com/mycompany/platform/event/provider/EventBusHealthcheckConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.mycompany.common.healthcheck.service.HealthcheckConfigSet]: Factory method 'eventListenerAdapterHealthcheckConfigSet' threw exception; nested exception is java.lang.ClassCastException: org.springframework.beans.factory.support.NullBean cannot be cast to com.mycompany.platform.event.bus.EventListenerAdapter
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:583)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1249)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1098)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:502)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:312)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:310)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:251)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1320)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1286)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeans(DefaultListableBeanFactory.java:1188)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1091)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1060)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:578)
	... 44 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.mycompany.common.healthcheck.service.HealthcheckConfigSet]: Factory method 'eventListenerAdapterHealthcheckConfigSet' threw exception; nested exception is java.lang.ClassCastException: org.springframework.beans.factory.support.NullBean cannot be cast to com.mycompany.platform.event.bus.EventListenerAdapter
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:186)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:575)
	... 59 more
Caused by: java.lang.ClassCastException: org.springframework.beans.factory.support.NullBean cannot be cast to com.mycompany.platform.event.bus.EventListenerAdapter
	at com.mycompany.platform.event.provider.EventBusHealthcheckConfiguration.eventListenerAdapterHealthcheckConfigSet(EventBusHealthcheckConfiguration.java:49)
	at com.mycompany.platform.event.provider.EventBusHealthcheckConfiguration$$EnhancerBySpringCGLIB$$a46517a5.CGLIB$eventListenerAdapterHealthcheckConfigSet$1(<generated>)
	at com.mycompany.platform.event.provider.EventBusHealthcheckConfiguration$$EnhancerBySpringCGLIB$$a46517a5$$FastClassBySpringCGLIB$$e3074686.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:361)
	at com.mycompany.platform.event.provider.EventBusHealthcheckConfiguration$$EnhancerBySpringCGLIB$$a46517a5.eventListenerAdapterHealthcheckConfigSet(<generated>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:155)
	... 60 more

Offending bean is defined as:

	@Bean
	@Profile("BATCH")
	HealthcheckConfigSet eventListenerAdapterHealthcheckConfigSet(
			@Autowired(required = false) @Lazy Map<String, EventListenerAdapter> eventListenerAdapters) {
		if (eventListenerAdapters == null || eventListenerAdapters.isEmpty()) {
			return null;
		}

		HealthcheckConfigSet set = new HealthcheckConfigSet(HealthcheckGroup.HEALTH);
		for (Entry<String, EventListenerAdapter> a : eventListenerAdapters.entrySet()) {
			if (a.getValue() == null) {
				LOGGER.info("Cannot configure healthcheck for EventListenerAdapter {}: the adapter is null",
						a.getKey());
				continue;
			} else {
				LOGGER.info("Configuring healthcheck for EventListenerAdapter {}", a.getKey());
				Healthcheck hc = new EventListenerAdapterHealthcheck(a.getKey(), a.getValue());
				set.add("event-listener-adapter-" + a.getKey(), hc);
			}

		}
		return set;
	}

From the code, I understand that at some point the Map contained null values, and now it seems the map contains NullBean instances.


Affects: 5.0 GA

Issue Links:

Referenced from: commits c9d3c26

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Oct 2, 2017

Francisco Lozano commented

Fixing temporarily as:

	@Bean
	@Profile("BATCH")
	HealthcheckConfigSet eventListenerAdapterHealthcheckConfigSet(
			@Autowired(required = false) @Lazy Map<String, EventListenerAdapter> eventListenerAdapters) {
		if (eventListenerAdapters == null || eventListenerAdapters.isEmpty()) {
			return null;
		}

		HealthcheckConfigSet set = new HealthcheckConfigSet(HealthcheckGroup.HEALTH);
		for (Entry<String, EventListenerAdapter> a : eventListenerAdapters.entrySet()) {
			String key = a.getKey();
			EventListenerAdapter value;
			// TODO remove this when #20582 is solved
			try {
				value = a.getValue();
			} catch (ClassCastException e) {
				LOGGER.info("Cannot configure healthcheck for EventListenerAdapter " + key, e);
				continue;
			}
			if (value == null) {
				LOGGER.info("Cannot configure healthcheck for EventListenerAdapter {}: the adapter is null", key);
				continue;
			} else {
				LOGGER.info("Configuring healthcheck for EventListenerAdapter {}", key);
				Healthcheck hc = new EventListenerAdapterHealthcheck(key, value);
				set.add("event-listener-adapter-" + key, hc);
			}

		}
		return set;
	}

@spring-projects-issues
Copy link
Collaborator Author

Francisco Lozano commented

Also reproduced somewhere else (in test code) by doing:

Map<String, EventListenerAdapter> beanMap = ctx.getBeansOfType(EventListenerAdapter.class);
com.mycompany.platform.product.server.services.BatchProfileTest > activated FAILED
    org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'mongoDBIndexerEventListenerAdapter' is expected to be of type 'com.mycompany.platform.event.bus.EventListenerAdapter' but was actually of type 'org.springframework.beans.factory.support.NullBean'
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:384)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:515)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:504)
        at org.springframework.context.support.AbstractApplicationContext.getBeansOfType(AbstractApplicationContext.java:1192)
        at com.mycompany.platform.product.server.services.BatchProfileTest.activated(BatchProfileTest.java:44)

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

This is indeed a consequence of a recent change where we're never exposing null values as a bean instance internally. For external consumption, we're still exposing those as null, so it seems we missed the Map injection case there...

@spring-projects-issues
Copy link
Collaborator Author

Francisco Lozano commented

One question: if I change the Map<String,TypeOfBean> to Map<String,Optional<TypeOfBean>>, will I get it right? it could mitigate my case in a cleaner way.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

We don't support nested Optional declarations that way, I'm afraid. The proper fix is on our end, simply resolving null values accordingly, which I have fixed for 5.0.1 now. Please give the upcoming 5.0.1.BUILD-SNAPSHOT a try if you have a chance.

That said, on your end, I avoid null values in your context setup to begin with. I'd rather expose a dummy listener instance (or whatever it is) instead of a null value from your factory method or FactoryBean.getObject() implementation.

@spring-projects-issues
Copy link
Collaborator Author

Francisco Lozano commented

I'm still getting this:

org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'mongoDBIndexerEventListenerAdapter' is expected to be of type 'com.kii.platform.ufp.event.bus.EventListenerAdapter' but was actually of type 'org.springframework.beans.factory.support.NullBean'
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:384)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:515)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:504)
	at org.springframework.context.support.AbstractApplicationContext.getBeansOfType(AbstractApplicationContext.java:1192)
	at com.mycompany.platform.ufp.ufe.server.services.BatchProfileTest.activated(BatchProfileTest.java:44)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:539)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:761)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:461)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:207)

Line 44 is:

		Map<String, EventListenerAdapter> beanMap = ctx.getBeansOfType(EventListenerAdapter.class);

@spring-projects-issues
Copy link
Collaborator Author

Francisco Lozano commented

(Using Spring 5.0.1.RELEASE).

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Francisco Lozano, could you please create a separate follow-up issue for this specific case? Indeed, getBeansOfType still doesn't leniently handle null instances yet since that 5.0.1 fix just applied to @Autowired resolution... Let's fix this for getBeansOfType as well but ideally under a separate JIRA ticket, then marked for 5.0.2.

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Nov 6, 2017

Francisco Lozano commented

I CANNOT reproduce with very simple code, however:

@Configuration
public class ConfigClass {
	@Bean
	public Dummy bean1() {
		return new Dummy();
	}

	@Bean
	public Dummy bean2() {
		return new Dummy();
	}

	@Bean
	public Dummy bean3() {
		return new Dummy();
	}

	@Bean
	public Object bean4() {
		return null;
	}
}
		try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext()) {
			ctx.register(ConfigClass.class);
			ctx.refresh();
			Map<String, Dummy> beans = ctx.getBeansOfType(Dummy.class);
			assertNotNull(beans.get("bean1"));
			assertNotNull(beans.get("bean2"));
			assertNotNull(beans.get("bean3"));
			assertNull(beans.get("bean4"));

		}

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

I've managed to reproduce it next to my @Autowired test case already, and it's also straightforward to fix. It'd be great to have a separate JIRA issue for tracking it though :-)

@spring-projects-issues
Copy link
Collaborator Author

Francisco Lozano commented

spring-attic/spring-framework-issues#173 managed to reproduce too. I'll open a new issue.

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Yes please. For better or for worse, @Autowired collection resolution and getBeansOfType are separate code paths. So let's keep this issue for the @Autowired fix (5.0.1) and the new issue for the getBeansOfType fix (5.0.2).

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Nov 6, 2017

Francisco Lozano commented

#20711 here it is. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: bug A general bug
Projects
None yet
Development

No branches or pull requests

2 participants