Reputation: 32104
I have a simple JUnit test, based on the AbstractJUnit4SpringContextTests
class:
@ContextConfiguration(locations = {
"/test-spring-config.xml", "/test-databaseApplicationContext.xml",
"/test-sharedApplicationContext.xml", "/test-dispatcher-servlet.xml"
})
public class TestTest extends AbstractJUnit4SpringContextTests {
@Test
public void testOneThing() {
}
}
And a bean that is loaded as part of the application context:
<bean id="mySingleton" class="com.company.SingletonClass" />
This Singleton, as it is used in other projects/locations, has a constructor that ensures there is only a single instance of the class at a given time:
public class SingletonClass {
private static SingletonClass instance = null;
public SingletonClass() {
if (instance != null) {
throw new IllegalStateException("Highlander rules in effect.");
}
instance = this;
}
}
However, when I run my JUnit test, this exception is hit. I understood that from this answer: Reuse spring application context across junit test classes
The application context was reused as long as the locations
are the same. However, this doesn't seem to be the case. These are the two locations in which the bean is instantiated:
First:
Thread [main] (Suspended (breakpoint at line 47 in SingletonClass))
SingletonClass.<init>() line: 47
NativeConstructorAccessorImpl.newInstance0(Constructor, Object[]) line: not available [native method]
NativeConstructorAccessorImpl.newInstance(Object[]) line: 57
DelegatingConstructorAccessorImpl.newInstance(Object[]) line: 45
Constructor<T>.newInstance(Object...) line: 525
BeanUtils.instantiateClass(Constructor<T>, Object...) line: 147
CglibSubclassingInstantiationStrategy(SimpleInstantiationStrategy).instantiate(RootBeanDefinition, String, BeanFactory) line: 76
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).instantiateBean(String, RootBeanDefinition) line: 990
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBeanInstance(String, RootBeanDefinition, Object[]) line: 943
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).doCreateBean(String, RootBeanDefinition, Object[]) line: 485
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBean(String, RootBeanDefinition, Object[]) line: 456
AbstractBeanFactory$1.getObject() line: 294
DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String, ObjectFactory) line: 225
DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class<T>, Object[], boolean) line: 291
DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 193
DefaultListableBeanFactory.preInstantiateSingletons() line: 585
GenericApplicationContext(AbstractApplicationContext).finishBeanFactoryInitialization(ConfigurableListableBeanFactory) line: 913
GenericApplicationContext(AbstractApplicationContext).refresh() line: 464
GenericXmlContextLoader(AbstractGenericContextLoader).loadContext(MergedContextConfiguration) line: 103
GenericXmlContextLoader(AbstractGenericContextLoader).loadContext(MergedContextConfiguration) line: 1
DelegatingSmartContextLoader.loadContext(MergedContextConfiguration) line: 228
TestContext.loadApplicationContext() line: 124
TestContext.getApplicationContext() line: 148
DependencyInjectionTestExecutionListener.injectDependencies(TestContext) line: 109
DependencyInjectionTestExecutionListener.prepareTestInstance(TestContext) line: 75
TestContextManager.prepareTestInstance(Object) line: 321
SpringJUnit4ClassRunner.createTest() line: 211
SpringJUnit4ClassRunner$1.runReflectiveCall() line: 288
SpringJUnit4ClassRunner$1(ReflectiveCallable).run() line: 15
SpringJUnit4ClassRunner.methodBlock(FrameworkMethod) line: 290
SpringJUnit4ClassRunner.runChild(FrameworkMethod, RunNotifier) line: 231
SpringJUnit4ClassRunner(BlockJUnit4ClassRunner).runChild(Object, RunNotifier) line: 44
SpringJUnit4ClassRunner(ParentRunner<T>).runChildren(RunNotifier) line: 180
ParentRunner<T>.access$000(ParentRunner, RunNotifier) line: 41
ParentRunner$1.evaluate() line: 173
RunBefores.evaluate() line: 28
RunBeforeTestClassCallbacks.evaluate() line: 61
RunAfters.evaluate() line: 31
RunAfterTestClassCallbacks.evaluate() line: 71
SpringJUnit4ClassRunner(ParentRunner<T>).run(RunNotifier) line: 220
SpringJUnit4ClassRunner.run(RunNotifier) line: 174
JUnit4TestMethodReference(JUnit4TestReference).run(TestExecution) line: 50
TestExecution.run(ITestReference[]) line: 38
RemoteTestRunner.runTests(String[], String, TestExecution) line: 467
RemoteTestRunner.runTests(TestExecution) line: 683
RemoteTestRunner.run() line: 390
RemoteTestRunner.main(String[]) line: 197
Second:
Thread [main] (Suspended (breakpoint at line 47 in SingletonClass))
SingletonClass$$EnhancerByCGLIB$$e8a4cc48(SingletonClass).<init>() line: 47
SingletonClass$$EnhancerByCGLIB$$e8a4cc48.<init>() line: not available
NativeConstructorAccessorImpl.newInstance0(Constructor, Object[]) line: not available [native method]
NativeConstructorAccessorImpl.newInstance(Object[]) line: 57
DelegatingConstructorAccessorImpl.newInstance(Object[]) line: 45
Constructor<T>.newInstance(Object...) line: 525
ReflectUtils.newInstance(Constructor, Object[]) line: 228
ReflectUtils.newInstance(Class, Class[], Object[]) line: 220
ReflectUtils.newInstance(Class) line: 216
Enhancer.createUsingReflection(Class) line: 643
Enhancer.firstInstance(Class) line: 538
Enhancer(AbstractClassGenerator).create(Object) line: 225
Enhancer.createHelper() line: 377
Enhancer.create() line: 285
Cglib2AopProxy.getProxy(ClassLoader) line: 201
ProxyFactory.getProxy(ClassLoader) line: 112
InfrastructureAdvisorAutoProxyCreator(AbstractAutoProxyCreator).createProxy(Class<?>, String, Object[], TargetSource) line: 476
InfrastructureAdvisorAutoProxyCreator(AbstractAutoProxyCreator).wrapIfNecessary(Object, String, Object) line: 362
InfrastructureAdvisorAutoProxyCreator(AbstractAutoProxyCreator).postProcessAfterInitialization(Object, String) line: 322
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).applyBeanPostProcessorsAfterInitialization(Object, String) line: 407
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).initializeBean(String, Object, RootBeanDefinition) line: 1461
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).doCreateBean(String, RootBeanDefinition, Object[]) line: 519
DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBean(String, RootBeanDefinition, Object[]) line: 456
AbstractBeanFactory$1.getObject() line: 294
DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String, ObjectFactory) line: 225
DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class<T>, Object[], boolean) line: 291
DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 193
DefaultListableBeanFactory.preInstantiateSingletons() line: 585
GenericApplicationContext(AbstractApplicationContext).finishBeanFactoryInitialization(ConfigurableListableBeanFactory) line: 913
GenericApplicationContext(AbstractApplicationContext).refresh() line: 464
GenericXmlContextLoader(AbstractGenericContextLoader).loadContext(MergedContextConfiguration) line: 103
GenericXmlContextLoader(AbstractGenericContextLoader).loadContext(MergedContextConfiguration) line: 1
DelegatingSmartContextLoader.loadContext(MergedContextConfiguration) line: 228
TestContext.loadApplicationContext() line: 124
TestContext.getApplicationContext() line: 148
DependencyInjectionTestExecutionListener.injectDependencies(TestContext) line: 109
DependencyInjectionTestExecutionListener.prepareTestInstance(TestContext) line: 75
TestContextManager.prepareTestInstance(Object) line: 321
SpringJUnit4ClassRunner.createTest() line: 211
SpringJUnit4ClassRunner$1.runReflectiveCall() line: 288
SpringJUnit4ClassRunner$1(ReflectiveCallable).run() line: 15
SpringJUnit4ClassRunner.methodBlock(FrameworkMethod) line: 290
SpringJUnit4ClassRunner.runChild(FrameworkMethod, RunNotifier) line: 231
SpringJUnit4ClassRunner(BlockJUnit4ClassRunner).runChild(Object, RunNotifier) line: 44
SpringJUnit4ClassRunner(ParentRunner<T>).runChildren(RunNotifier) line: 180
ParentRunner<T>.access$000(ParentRunner, RunNotifier) line: 41
ParentRunner$1.evaluate() line: 173
RunBefores.evaluate() line: 28
RunBeforeTestClassCallbacks.evaluate() line: 61
RunAfters.evaluate() line: 31
RunAfterTestClassCallbacks.evaluate() line: 71
SpringJUnit4ClassRunner(ParentRunner<T>).run(RunNotifier) line: 220
SpringJUnit4ClassRunner.run(RunNotifier) line: 174
JUnit4TestMethodReference(JUnit4TestReference).run(TestExecution) line: 50
TestExecution.run(ITestReference[]) line: 38
RemoteTestRunner.runTests(String[], String, TestExecution) line: 467
RemoteTestRunner.runTests(TestExecution) line: 683
RemoteTestRunner.run() line: 390
RemoteTestRunner.main(String[]) line: 197
What is the reason for the constructor being called twice, and how can I prevent this from happening? While these singleton checks do seem to be throwing a wrench into the unit test environment, they have served well in the past as a good "hard" limit when actually in production.
Upvotes: 2
Views: 2113
Reputation: 53563
I would claim the reason being that your singleton class doesn't expose an interface that Spring can use for its application context. Spring bases its autowiring on implementing an interface that acts as a singleton proxy for the class. Since however your singletonclass is an actual class, Spring cannot do that, but instead is forced to subclass your class using cglib (which you see in the stack trace). Any child class has to invoke the super constructor as well, so you see two invocations.
So, in short: I think appcontext is not the issue here. If your class would expose & implement an actual interface that Spring can implement with its proxies, like is recommended, I would suspect you wouldn't get your exception.
Upvotes: 2