Announcement Announcement Module
Collapse
No announcement yet.
Autowiring with factory-methods? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Autowiring with factory-methods?

    Hi,

    we're struggling a bit using the autowiring feature together with factory methods.

    Attached you find a small maven project with a single unit test demonstrating what does not seem to work.

    In short:
    - We have an interface (IFace), where the real implementation is created by a factory (class Factory).
    - The context.xml explicitly defines one bean implementing the interface using the factory.
    - There is a consumer (class Consumer) having the IFace implementation injected.
    - For testing the IFace implementation in the application-context is replaced by a mock, also created by a factory (see test-context.xml).
    - everything else is autowiring magic enabled in the context.xml

    Odd things are:
    - The test fails with "NoSuchBeanDefinitionException: No unique bean of type [test.IFace] ... expected at least 1 matching bean" - but there IS one!
    - Changing the autowiring in the Consumer to classic injection using @Resource makes everything work
    - Autowiring the IFace into the ConsumerTest works

    Are we doing anything wrong or is this a bug?

    Thanks in advance for any hints!

    Andy
    Last edited by abeani; Apr 9th, 2008, 05:11 PM.

  • #2
    Here are the relevant parts (all just for demonstration):

    The IFace:
    public interface IFace {
    }
    The Factory :
    public class Factory {
    public static IFace create() {
    return new IFace() {
    };
    }
    }
    The context.xml:
    <beans ...>
    <context:component-scan base-package="..." />
    <context:annotation-config />
    <context:spring-configured />
    <bean id="iFace" class="test.Factory" factory-method="create" />
    </beans>
    And the Consumer:
    @Component
    public class Comsumer {

    // this does not work:
    @Autowired
    // this works:
    // @Resource(name = "iFace")
    private IFace iFace;

    public boolean checkIFace() {
    return iFace != null;
    }

    }
    The unit test:
    @ContextConfiguration(locations = { "classpath:context.xml", "classpath:test-context.xml"})
    public class ConsumerTest extends AbstractTestNGSpringContextTests {
    @Autowired
    private Comsumer consumer;

    @Test
    public void testConsumer()
    {
    assertNotNull(consumer);
    assertTrue(consumer.checkIFace());
    }
    }
    and finally the test-context.xml:
    <beans ...>
    <bean id="iFace" class="org.easymock.EasyMock" factory-method="createMock" >
    <constructor-arg value="test.IFace" />
    </bean>
    </beans>
    You can see it working (or failing) by downloading the ZIP from my first post and executing "mvn test". This gives me the following stacktrace:

    Caught exception while allowing TestExecutionListener
    [org.springframework.test.context.support.Dependenc yInjectionTestExecutionListener@5d391d]
    to prepare test instance [test.ConsumerTest@1cac6db]
    java.lang.IllegalStateException: Failed to load ApplicationContext
    at org.springframework.test.context.TestContext.getAp plicationContext(TestContext.java:203)
    at org.springframework.test.context.support.Dependenc yInjectionTestExecutionListener.injectDependencies (DependencyInjectionTestExecutionListener.java:109 )
    at org.springframework.test.context.support.Dependenc yInjectionTestExecutionListener.prepareTestInstanc e(DependencyInjectionTestExecutionListener.java:75 )
    at org.springframework.test.context.TestContextManage r.prepareTestInstance(TestContextManager.java:255)
    at org.springframework.test.context.testng.AbstractTe stNGSpringContextTests.springTestContextPrepareTes tInstance(AbstractTestNGSpringContextTests.java:11 7)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Nativ e Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Native MethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(De legatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at org.testng.internal.MethodHelper.invokeMethod(Meth odHelper.java:580)
    at org.testng.internal.Invoker.invokeConfigurationMet hod(Invoker.java:398)
    at org.testng.internal.Invoker.invokeConfigurations(I nvoker.java:145)
    at org.testng.internal.Invoker.invokeConfigurations(I nvoker.java:82)
    at org.testng.internal.TestMethodWorker.invokeBeforeC lassMethods(TestMethodWorker.java:166)
    at org.testng.internal.TestMethodWorker.run(TestMetho dWorker.java:103)
    at org.testng.TestRunner.runWorkers(TestRunner.java:6 89)
    at org.testng.TestRunner.privateRun(TestRunner.java:5 66)
    at org.testng.TestRunner.run(TestRunner.java:466)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:30 1)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner .java:296)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java :276)
    at org.testng.SuiteRunner.run(SuiteRunner.java:191)
    at org.testng.TestNG.createAndRunSuiteRunners(TestNG. java:808)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:776 )
    at org.testng.TestNG.run(TestNG.java:701)
    at org.apache.maven.surefire.testng.TestNGExecutor.ru n(TestNGExecutor.java:62)
    at org.apache.maven.surefire.testng.TestNGDirectoryTe stSuite.execute(TestNGDirectoryTestSuite.java:141)
    at org.apache.maven.surefire.Surefire.run(Surefire.ja va:177)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Nativ e Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Native MethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(De legatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at org.apache.maven.surefire.booter.SurefireBooter.ru nSuitesInProcess(SurefireBooter.java:338)
    at org.apache.maven.surefire.booter.SurefireBooter.ma in(SurefireBooter.java:997)
    Caused by: org.springframework.beans.factory.BeanCreationExce ption: Error creating bean with name 'comsumer':
    Autowiring of fields failed; nested exception is org.springframework.beans.factory.BeanCreationExce ption:
    Could not autowire field: private test.IFace test.Comsumer.iFace;
    nested exception is org.springframework.beans.factory.NoSuchBeanDefini tionException:
    No unique bean of type [test.IFace] is defined: Unsatisfied dependency of type [interface test.IFace]:
    expected at least 1 matching bean
    at org.springframework.beans.factory.annotation.Autow iredAnnotationBeanPostProcessor.postProcessAfterIn stantiation(AutowiredAnnotationBeanPostProcessor.j ava:243)
    at org.springframework.beans.factory.support.Abstract AutowireCapableBeanFactory.populateBean(AbstractAu towireCapableBeanFactory.java:957)
    at org.springframework.beans.factory.support.Abstract AutowireCapableBeanFactory.doCreateBean(AbstractAu towireCapableBeanFactory.java:470)
    at org.springframework.beans.factory.support.Abstract AutowireCapableBeanFactory$1.run(AbstractAutowireC apableBeanFactory.java:409)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.springframework.beans.factory.support.Abstract AutowireCapableBeanFactory.createBean(AbstractAuto wireCapableBeanFactory.java:380)
    at org.springframework.beans.factory.support.Abstract BeanFactory$1.getObject(AbstractBeanFactory.java:2 64)
    at org.springframework.beans.factory.support.DefaultS ingletonBeanRegistry.getSingleton(DefaultSingleton BeanRegistry.java:217)
    at org.springframework.beans.factory.support.Abstract BeanFactory.doGetBean(AbstractBeanFactory.java:261 )
    at org.springframework.beans.factory.support.Abstract BeanFactory.getBean(AbstractBeanFactory.java:185)
    at org.springframework.beans.factory.support.Abstract BeanFactory.getBean(AbstractBeanFactory.java:164)
    at org.springframework.beans.factory.support.DefaultL istableBeanFactory.preInstantiateSingletons(Defaul tListableBeanFactory.java:429)
    at org.springframework.context.support.AbstractApplic ationContext.finishBeanFactoryInitialization(Abstr actApplicationContext.java:729)
    at org.springframework.context.support.AbstractApplic ationContext.refresh(AbstractApplicationContext.ja va:381)
    at org.springframework.test.context.support.AbstractG enericContextLoader.loadContext(AbstractGenericCon textLoader.java:96)
    at org.springframework.test.context.support.AbstractG enericContextLoader.loadContext(AbstractGenericCon textLoader.java:44)
    at org.springframework.test.context.TestContext.loadA pplicationContext(TestContext.java:173)
    at org.springframework.test.context.TestContext.getAp plicationContext(TestContext.java:199)
    ... 33 more
    Caused by: org.springframework.beans.factory.BeanCreationExce ption:
    Could not autowire field: private test.IFace test.Comsumer.iFace;
    nested exception is org.springframework.beans.factory.NoSuchBeanDefini tionException:
    No unique bean of type [test.IFace] is defined: Unsatisfied dependency of type [interface test.IFace]:
    expected at least 1 matching bean
    at org.springframework.beans.factory.annotation.Autow iredAnnotationBeanPostProcessor$AutowiredFieldElem ent.inject(AutowiredAnnotationBeanPostProcessor.ja va:435)
    at org.springframework.beans.factory.annotation.Injec tionMetadata.injectFields(InjectionMetadata.java:1 05)
    at org.springframework.beans.factory.annotation.Autow iredAnnotationBeanPostProcessor.postProcessAfterIn stantiation(AutowiredAnnotationBeanPostProcessor.j ava:240)
    ... 50 more
    Caused by: org.springframework.beans.factory.NoSuchBeanDefini tionException: No unique bean of type [test.IFace] is defined: Unsatisfied dependency of type [interface test.IFace]: expected at least 1 matching bean
    at org.springframework.beans.factory.support.DefaultL istableBeanFactory.resolveDependency(DefaultListab leBeanFactory.java:613)
    at org.springframework.beans.factory.annotation.Autow iredAnnotationBeanPostProcessor$AutowiredFieldElem ent.inject(AutowiredAnnotationBeanPostProcessor.ja va:412)
    ... 52 more
    Any ideas?

    Thanks!

    Andy

    Comment


    • #3
      I have the same issue, using
      • @Autowired
      • @Autowired @Qualifier
      • @Resource @Qualifier

      didn't work. I tried @Resource(name="foo") and it worked fine (under JRE 1.6).

      Steve

      Comment


      • #4
        @Resource doesn't work (and isn't supposed to work) with @Qualifier. @Resource is a common annotation not a Spring based application. It is clearly explained in the reference guide.

        Comment


        • #5
          Well that explains @Resource, but what about using @Autowired with @Qualifier (or even by itself). Shouldn't they pick up the bean definition? (the one that uses a factory).

          Comment


          • #6
            With @Qualifier it should work.

            However in the example above the factory is overriding the factory defined in the context.xml. However spring uses reflection to determine the type the factory-method returns. For the factory in the first case it is a IFace. For the EasyMock stuff it returns Object. Now Object clearly isn't a IFace...

            Comment


            • #7
              Thanks Marten for pointing on the "overwritten" factory-method.

              I may be wrong, but I would expect spring to use reflection on the object returned by the factory-method (and not the factory-method itself)? If so, both objects returned are perfect implementations of the IFace interface and candidates for autowiring?

              Comment


              • #8
                The type is determined by return type of the factory method NOT based on the object returned from the factory-method itself.

                It is done to prevent over eagerly instantiation of beans and to prevent heavy objects from beeing instantiated. (Imagine some object which takes about 10 secs to create and it nog being a singleton, it would be quite a burden for startup).

                Comment


                • #9
                  Thanks again for clarifying.

                  Is there a way to tell spring which class/interfaces objects returned by factory-method implement? This means overwriting the default reflection mechanism? Something like

                  Code:
                  <bean id="iFace" class="org.easymock.EasyMock" factory-method="createMock" 
                    beanClass="test.IFace">
                    <constructor-arg value="test.IFace" />
                  </bean>
                  If not, where can I add it to the wish-list?

                  Comment


                  • #10
                    JIRA for all your wishes and bugs .

                    Comment


                    • #11
                      Done: http://jira.springframework.org/browse/SPR-4699

                      Comment


                      • #12
                        Iv got a reasonable solution

                        One possible solution for using mocks with autowiring is to use the FactoryBean interface that has a getObjectType method (on which spring operates), iv written a post on the subject that can be found on my blog (google javadevelopmentforthemasses, i cant add links yet).

                        Comment


                        • #13
                          Thanks a lot for the post here and your blog. I immediately changed our mocks to your factory and even enhanced our generic DAO factory with our solution and now autowiring works like a charm...

                          I also like your approach with the AutoBeanDeclarer - however it does not seem to be suitable for us: we have several places, where we need real implementations (e. g. for the login, masterdata, etc.) and mocks (e. g. within the functionality to test) together. But as your implementation takes care of each and every autowired field, your cannot mix things here... (?)

                          We thought about an easy way to overwrite/replace specific beans in the application context with mocks. Our current solution is having a single xml-file for each interface to mock only defining this single mock. The test then has a @ContextConfiguration specifiying the global application context xml file and overwriting/replacing specific beans with mocks using the above simple xml files. The test configuration to replace the systemDAO and the userDAO with mocks would look like this:

                          Code:
                          @ContextConfiguration(locations = { "classpath:config/spring-context.xml",
                                   "classpath:mock/systemDAO.xml", "classpath:mock/userDAO.xml" })
                          But with your idea I've started thinking about a annotation based variation:
                          Create a post processor scanning the test classes for a, say "@Mock" annotation specifiying all the interfaces to be mocked. Something like this:

                          Code:
                          @ContextConfiguration(locations = { "classpath:config/spring-context.xml" })
                          @Mock(interfaces = { SystemDAO.class, UserDAO.class })
                          Any thoughts on this?

                          Comment


                          • #14
                            More ideas

                            Sorry for not replying sooner, as for more idea you can make the AutoBeanDeclarer SpringContextAware and make sure that it wont override existing bean definitions that were already defined in the xml context, not only that but iv created an XML free testing framework that hopefully ill be able to post about in the near future (by using a custom Loader).

                            Comment


                            • #15
                              More ideas

                              Sorry for not replying sooner
                              As for more ideas, you can make AutoBeanDeclarer to be Spring context aware and check that for each mock added there isn't already an existing bean in the context, iv taken it further by using a custom Loader i was able to create an XML free framework that registers mocks for all Spring based tests (i should write an entry about it).

                              Comment

                              Working...
                              X