Page 1 of 2 12 LastLast
Results 1 to 10 of 19

Thread: Autowiring with factory-methods?

  1. #1

    Question 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
    Attached Files Attached Files
    Last edited by abeani; Apr 9th, 2008 at 04:11 PM.

  2. #2

    Default

    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

  3. #3

    Default

    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

  4. #4
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,624

    Default

    @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.
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

  5. #5

    Default

    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).

  6. #6
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,624

    Default

    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...
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

  7. #7

    Default

    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?

  8. #8
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,624

    Default

    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).
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

  9. #9

    Default

    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?

  10. #10
    Join Date
    Jun 2006
    Location
    The Netherlands
    Posts
    13,624

    Default

    JIRA for all your wishes and bugs .
    Marten Deinum
    Java Consultant / Pragmatist / Open Source Enthousiast / Author


    Pro Spring MVC: With Web Flow
    Conspect

    Have you read the reference guide.
    Use the [ code ] tags, young padawan

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •