2
votes

I am migrating away from XML config for Spring context configuration. Instead when I try to use the functionally equivalent @EnableTransactionManagement on a Spring 4.0.3.RELEASE Java Configuration, my Spring context fails to instantiate with the following exception:

java.lang.IllegalStateException: Failed to load ApplicationContext
        at org.springframework.test.context.CacheAwareContextLoaderDelegate.loadContext(CacheAwareContextLoaderDelegate.java:99)
        at org.springframework.test.context.DefaultTestContext.getApplicationContext(DefaultTestContext.java:101)
        at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
        at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
        at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:319)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:212)
        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:232)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
        at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
        at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:175)
        at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:236)
        at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:134)
        at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:113)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
        at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
        at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:103)
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:74)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'baseMySQLTest.TestConfig': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.config.internalTransactionAdvisor' defined in class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.transaction.intercep...skipping...
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1558)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
        ... 45 more
Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration.transactionAdvisor()] threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionInterceptor' defined in class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Property 'transactionManager' is required
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:188)
        at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:586)
        ... 62 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionInterceptor' defined in class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Property 'transactionManager' is required
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1553)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
        at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:324)
        at org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$83a12634.transactionInterceptor()
        at org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration.transactionAdvisor(ProxyTransactionManagementConfiguration.java:45)
        at org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$83a12634.CGLIB$transactionAdvisor$0()
        at org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$83a12634$$FastClassBySpringCGLIB$$cc829ae.invoke()
        at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
        at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:312)
        at org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$83a12634.transactionAdvisor()
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:166)
        ... 63 more
Caused by: java.lang.IllegalArgumentException: Property 'transactionManager' is required
        at org.springframework.transaction.interceptor.TransactionAspectSupport.afterPropertiesSet(TransactionAspectSupport.java:195)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1612)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1549)
        ... 82 more

This happens to obtain in a unit test, but when it works here, I can use it in production code.

Here is my unit test base class where the Spring wiring occurs:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {PropertyPlaceholderConfigurer.class, BaseMySQLTest.TestConfig.class})
public class BaseMySQLTest extends AbstractTransactionalJUnit4SpringContextTests {

    @Configuration
    @Import(DaoConfig.class)
    @PropertySource("classpath:/jdbc.properties")
    @EnableTransactionManagement
    public static class TestConfig {
        @Bean
        public PlatformTransactionManager providesTransactionManager(ListableBeanFactory beanFactory) {
            return new DataSourceTransactionManager(beanFactory.getBean(DataSource.class));
        }
    }
}

and here is a subclass that uses this base class and config:

public class UserDaoImplTest extends BaseMySQLTest {

    @Autowired
    private UserDao userDao;

    @Test
    public void testById() throws Exception {
        jdbcTemplate.execute("insert into users (email) values ('[email protected]')");
        ...
    }

}

As one can see, my Spring TestConfig has a transaction manager bean defined, which according to the Javadoc

http://docs.spring.io/spring/docs/3.1.x/javadoc-api/org/springframework/transaction/annotation/EnableTransactionManagement.html

means I do not have to name the bean (though I have named it in an attempt to get this to work). In fact, the Spring context is behaving as if configured with XML config in the face of no bean explicitly named "transactionManager".

What is my Java Config missing such that the Spring context cannot use this transaction manager bean to satisfy its requirements at instantiation time?

Thank you for any helpful observations.

EDIT:

(I'm not sure where this edit should go, so I try here. ae6rt)

Here is the new test class, which results in the same error as the original work:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {BaseMySQLTest.TestConfig.class})
public class BaseMySQLTest extends AbstractTransactionalJUnit4SpringContextTests {

    protected int lastInsertId() {
        return jdbcTemplate.queryForInt("select LAST_INSERT_ID()");
    }

    @Autowired
    private UserDao userDao;

    @Test
    public void testById() throws Exception {
        jdbcTemplate.execute("insert into mgdb.users (email) values ('[email protected]')");
        int userId = lastInsertId();
        Optional xoomUserOptional = userDao.byId(userId);
        assertThat(xoomUserOptional.isPresent(), equalTo(true));
        XoomUser user = xoomUserOptional.get();
        assertThat(user.getEmailAddress(), equalTo("[email protected]"));
    }

    @Configuration
    @Import(DaoConfig.class)
    @PropertySource("classpath:/jdbc.properties")
    @EnableTransactionManagement
    public static class TestConfig {
        @Bean
        public PlatformTransactionManager providesTransactionManager(ListableBeanFactory beanFactory) {
            return new DataSourceTransactionManager(beanFactory.getBean(DataSource.class));
        }
    }

}

I didn't actualy need the property config here

@ContextConfiguration(classes = {BaseMySQLTest.TestConfig.class})

so I removed it. Hope this meets the spirit of this round.

1

1 Answers

0
votes

Looks like the only change should be to name your transaction manager bean name to "transactionManager":

public static class TestConfig {
    @Autowired
    private DataSource datasource;

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(datasource);
    }
}

EDIT :

Can you try these additional things:

.1. Remove PropertyPlaceHolderConfigurer from here.. @ContextConfiguration(classes = {PropertyPlaceholderConfigurer.class, BaseMySQLTest.TestConfig.class}), that is not how PropertyPlaceholderConfigurer is used, you have to do it this way:

@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
    PropertySourcesPlaceholderConfigurer placeholderConfigurer = new PropertySourcesPlaceholderConfigurer();
    ClassPathResource resource = new ClassPathResource("/META-INF/spring/database.properties");
    placeholderConfigurer.setLocation(resource);
    return placeholderConfigurer;
}

Also, just for test, can you move your actual test to the base class also and see if one of these configuration works out for you. I tested in my own machine and it seems to work nicely with Spring 4.0.2+.