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.

Community
  • 1
  • 1
Craig Otis
  • 31,257
  • 32
  • 136
  • 234

1 Answers1

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.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
eis
  • 51,991
  • 13
  • 150
  • 199
  • That was exactly the problem. Thank you! – Craig Otis Oct 16 '13 at 19:05
  • @CraigOtis There's something you haven't shown us because there is no reason Spring would proxy your instances as your example stands. – Sotirios Delimanolis Oct 16 '13 at 19:12
  • @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. – eis Oct 16 '13 at 19:14
  • Don't tell me how it works. Test the SSCCE that OP posted. There are no proxies and no reason for a proxy to be created. The proxying (as shown in logs) must happen for some other reason that OP hasn't shown us. – Sotirios Delimanolis Oct 16 '13 at 19:16
  • You're very hard-headed, but also completely wrong. Spring will only wrap an instance in a proxy if it needs to add additional behavior, like scope, transactional, etc. For a basic bean like `` as OP has shown, there is no reason to make a proxy. Spring will simply use reflection to generate an instance using that no-arg constructor. – Sotirios Delimanolis Oct 16 '13 at 19:22
  • Even worse, the fact that it is generating a proxy has nothing to do with the exception. The exception comes from the fact that Spring is invoking the no-arg constructor twice because of some configuration not shown here. Your answer is completely wrong. – Sotirios Delimanolis Oct 16 '13 at 19:22
  • It's true that Spring will wrap an instance in a proxy only if it needs to, but there are a lots of cases where it needs to. If you look at the stack trace in the post you can clearly see there's cglib proxy creation code being activated. Apparently you agree with me, since you say "the fact that it is generating a proxy"... I would be keen to know what configuration you expect to be there? – eis Oct 16 '13 at 19:40
  • The configuration can be anything, ex. a matching AOP pointcut. Even if there was a proxy, Spring will create an actual instance of `SingletonClass` and set that as the `target` field of the proxy. So the constructor is still invoked. The problem is it's invoked twice and nothing in OP's question shows why. – Sotirios Delimanolis Oct 16 '13 at 19:45
  • 1
    @SotiriosDelimanolis ok, if you don't believe me, maybe you could take a look [at the documentation](http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-proxying). 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[..]" – eis Oct 16 '13 at 19:55
  • 1
    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. – eis Oct 16 '13 at 19:58
  • Thank you for explaining yourself. I **am** arguing about why it happens. In OP's example, which you can run for yourself, there is no reason for a proxy to be created and therefore the Exception should not be thrown. The solution of using JDK proxies instead might not work in all situations either. – Sotirios Delimanolis Oct 16 '13 at 20:02
  • For example if you need to autowire the proxied bean by its actual type, it will fail. The JKD proxies extend from `Proxy`, not the actual bean class. – Sotirios Delimanolis Oct 16 '13 at 20:06