2

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.

3
  • Is there only one <bean> declaration for that class in all of your 4 context files? Commented Oct 16, 2013 at 18:45
  • @SotiriosDelimanolis Yes, it is only defined in one of the context files. Commented Oct 16, 2013 at 18:47
  • I am not able to reproduce your problem. Can you post any other information? Maybe your contexts or other @Test cases that might be relevant. Do you have @DirtiesContext anywhere? Commented Oct 16, 2013 at 19:01

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

12 Comments

That was exactly the problem. Thank you!
@CraigOtis There's something you haven't shown us because there is no reason Spring would proxy your instances as your example stands.
@SotiriosDelimanolis you're wrong. Spring DI is based on proxying the instances, and it has two basic mechanisms for that: implementing an interface, which in the code provided wasn't possible, and subclassing. That's how Spring works.
@SotiriosDelimanolis ok, if you don't believe me, maybe you could take a look at the documentation. Third bullet: "The constructor of your proxied object will be called twice. This is a natural consequence of the CGLIB proxy model whereby a subclass is generated for each proxied object.For each proxied instance, two objects are created:the actual proxied object and an instance of the subclass that implements the advice. Usually calling the constructor of the proxied type twice is not an issue[..]"
If we're here arguing about why the proxy was created then I think that's beside the point - the root cause is the cglib proxy, like I answered, and by implementing an interface Spring doesn't have to resort to subclassed proxies and the problem goes away.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.