Wow, today I thought I was doing some good code cleanup, and in my nice, working application, I merged a couple of Spring application context files into one single application context file, and got this horrible-looking Spring "unsatisfied dependency" error message when trying to run my suite of unit tests:
Error creating bean with name 'com.devdaily.ftpfilemover.dao.FTPFileMoverDaoTests': Unsatisfied dependency expressed through bean property 'dataSource': No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [fooDataSource, barDataSource]; nested
This "unsatisfied dependency", "dataSource" error seemed horrible, but what really happened was that when I merged my separate application context files into one larger applicationContext.xml
file, I learned that Spring uses autowiring in the background of some of my database tests. In this application, I had created a number of database tests that basically just validate the syntax of my JDBC SQL database calls (which I really like, especially when the database schema is changing), but I hadn't paid attention to the fact that some of this worked because of the Spring autowire magic.
I guess autowiring can be a good thing -- I haven't used it yet -- but in this case it caused me a ton of problems. In short, this ""unsatisfied dependency"" error message means that I can only have one DataSource defined in a Spring application context file if you plan to use that context file in Spring JDBC tests. (Warning: What I wrote there may be a little ambiguous ... but I don't have all the code with me to look up the exact class definitions at this time.)
I don't have time to get into all of this today, but the solution to my problem was:
- Keep my Spring application context files separate, so that there is just one autowired DataSource (BasicDataSource) defined in each context.
- Create a new "main" application context file that imports these other context files.
- Use the individual context files in my JDBC tests, but have my application use my new "main" context file.
When I was finished I had one "main" context file (applicationContext.xml
) that looked almost exactly like the following code:
<!-- applicationContext.xml --> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <import resource="fooContext.xml"/> <import resource="barContext.xml"/> <!-- this handler depends on beans from both database context files --> <bean id="storEventHandler" class="com.devdaily.ftpfilemover.controller.StorEventHandler"> <property name="fooDao" ref="fooDao"/> <property name="barDao" ref="barDao"/> </bean> </beans>
Spring application context file #1: fooContext.xml
What the heck, let's go all the way. After that main application context file, here's the first context file I include, fooContext.xml
:
<!-- fooContext.xml --> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="ftpFileMoverDao" class="com.devdaily.ftpfilemover.dao.FTPFileMoverDao"> <property name="dataSource" ref="fooDataSource"/> <property name="transactionTemplate" ref="transactionTemplate"/> </bean> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="fooDataSource"/> </bean> <bean id="fooDataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="@db_driver@" /> <property name="url" value="@db_url@" /> <property name="username" value="@db_username@" /> <property name="password" value="@db_password@" /> <property name="initialSize" value="@initial_pool_size@" /> <property name="maxActive" value="@max_pool_size@" /> </bean> </beans>
Spring application context file #2: barContext.xml
And here's the second Spring application context file that references a DataSource, barContext.xml
:
<!-- barContext.xml --> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="barDao" class="com.devdaily.ftpfilemover.dao.BarDao"> <property name="dataSource" ref="barBasicDataSource"/> <property name="transactionTemplate" ref="barTransactionTemplate"/> </bean> <bean id="barTransactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="barTransactionManager"/> </bean> <bean id="barTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="barBasicDataSource"/> </bean> <bean id="barBasicDataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="@bar_db_driver@" /> <property name="url" value="@bar_db_url@" /> <property name="username" value="@bar_db_username@" /> <property name="password" value="@bar_db_password@" /> <property name="initialSize" value="@bar_initial_pool_size@" /> <property name="maxActive" value="@bar_max_pool_size@" /> </bean> </beans>
I don't have the rest of the source code with me at this time, but as mentioned, my JDBC SQL "unit" tests refer to either the fooContext.xml
file, or the barContext.xml
file, while my actual application refers to the mainContext.xml
context file, which glues those others together, while also defining a bean that uses the data sources from both of the other context files.
The full Spring "unsatisfied dependency" error message
If for some reason you'd like to see the full, gory error message from Spring, to make sure we're talking about the same error for instance, here it is:
Testsuite: com.devdaily.ftpfilemover.dao.FTPFileMoverDaoTests Tests run: 1, Failures: 0, Errors: 1, Time elapsed: 0.344 sec ------------- Standard Output --------------- INFO | 2008-09-08 15:20:02,323 | AbstractSingleSpringContextTests.java | 187 | loadContextLocations | Loading context for locations: ffmContext.xml INFO | 2008-09-08 15:20:02,401 | XmlBeanDefinitionReader.java | 323 | loadBeanDefinitions | Loading XML bean definitions from class path resource [ffmContext.xml] INFO | 2008-09-08 15:20:02,495 | AbstractApplicationContext.java | 412 | prepareRefresh | Refreshing org.springframework.context.support.GenericApplicationContext@4f1d0d: display name [org.springframework.context.support.GenericApplicationContext@4f1d0d]; startup date [Mon Sep 08 15:20:02 EDT 2008]; root of context hierarchy INFO | 2008-09-08 15:20:02,495 | AbstractApplicationContext.java | 427 | obtainFreshBeanFactory | Bean factory for application context [org.springframework.context.support.GenericApplicationContext@4f1d0d]: org.springframework.beans.factory.support.DefaultListableBeanFactory@192b996 INFO | 2008-09-08 15:20:02,526 | DefaultListableBeanFactory.java | 414 | preInstantiateSingletons | Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@192b996: defining beans [ftpEventFinder,ftpFileMoverDao,transactionTemplate,transactionManager,fooDataSource,barDao,barTransactionTemplate,barTransactionManager,barBasicDataSource,ftpFileMoverDaoTests,storEventHandler,deleteEventHandler,renameEventHandler]; root of factory hierarchy ------------- ---------------- --------------- Testcase: testGetApplicationId took 0.312 sec Caused an ERROR Error creating bean with name 'com.devdaily.ftpfilemover.dao.FTPFileMoverDaoTests': Unsatisfied dependency expressed through bean property 'dataSource': : No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [fooDataSource, barBasicDataSource]; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [fooDataSource, barBasicDataSource] org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.devdaily.ftpfilemover.dao.FTPFileMoverDaoTests': Unsatisfied dependency expressed through bean property 'dataSource': : No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [fooDataSource, barBasicDataSource]; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [fooDataSource, barBasicDataSource] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1091) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:982) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:329) at org.springframework.test.AbstractDependencyInjectionSpringContextTests.injectDependencies(AbstractDependencyInjectionSpringContextTests.java:205) at org.springframework.test.AbstractDependencyInjectionSpringContextTests.prepareTestInstance(AbstractDependencyInjectionSpringContextTests.java:180) at org.springframework.test.AbstractSingleSpringContextTests.setUp(AbstractSingleSpringContextTests.java:100) at org.springframework.test.ConditionalTestCase.runBare(ConditionalTestCase.java:76) Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.sql.DataSource] is defined: expected single matching bean but found 2: [fooDataSource, barBasicDataSource] at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:621) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireByType(AbstractAutowireCapableBeanFactory.java:1076)