2

I'm trying to get a better hang of the way Spring handles dependency injection, but I have come to face a (at least for me) quite weird situation.

I have a layered application which can be visually described something like this: web - business - domainmodel - dal ... every layer in separate modules.

I'm using annotations to inject whats needed in my classes, and to make things more complicated I'm using cucumber in the business layer, which means I need to explicitly inject a test-bean when testing and the real bean when not testing.

Everything compiles just fine, and even runs and injects (@Inject) everything just fine... until I add this line to the context.xml in the web layer:

<context:component-scan base-package="com.foo.bar.baz.bleep.bll" />

... this is where my problems start. When that line is added, compile completes successfully, but when I run it in Tomcat I get this error:

Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sampleAppServiceImpl': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.foo.bar.baz.bleep.dal.SampleAppDao com.foo.bar.baz.bleep.bll.SampleAppServiceImpl.sampleAppDao; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [com.foo.bar.baz.bleep.dal.SampleAppDao] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@javax.inject.Inject()}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:287)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1106)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:585)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:913)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:464)
    at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:385)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:284)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:111)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4797)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5291)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
    at java.util.concurrent.FutureTask.run(FutureTask.java:138)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
    at java.lang.Thread.run(Thread.java:662)

All of a sudden the dao is missing from the container (?).

I want to use the business services from the business layer in the web layer, but as soon as I'm trying to add the scan on the bll to the web layer, the business layer breaks. What gives?

my web-context.xml in the web layer

<context:component-scan base-package="com.foo.bar.baz.bleep.bll" />
<context:component-scan base-package="com.foo.bar.baz.bleep.domainmodel" />
<context:component-scan base-package="com.foo.bar.baz.bleep.web" />

my cucumber.xml in the business layer

<context:component-scan base-package="com.foo.bar.baz.bleep.bll" />
<context:component-scan base-package="com.foo.bar.baz.bleep.domainmodel" />
<import resource="classpath:business-context.xml" />

my two business-context.xml

TEST business-context.xml

<bean id="sampleAppDao" class="com.foo.bar.baz.bleep.dal.inMemorySampleAppDaoImpl"></bean>

REAL business-context.xml

<bean id="sampleAppDao" class="com.foo.bar.baz.bleep.dal.sampleAppDaoImpl"></bean>

My implementations are all annotated with the appropriate component annotation (i.e. @Controller on the web-servlet, @Service on the business service and @Repository on the dao) All modules have the necessary dependencies to other modules in the pom.xml.

I have two questions:

  1. What am I doing wrong? Obviously =)

  2. Have I gotten this auto-wiring business all wrong? I was thinking I'd only scan the packages that each module specifically needed, not EVERYTHING (context:component-scan base-package="com.foo" />), like I have seen in many tutorials. One thing to note there: I have tried scanning everything before in other projects, but ran into the same problem, that is why I started scanning just what was necessary for each module.

Please advice me in how to make this work!

UPDATE 1

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    id="WebApp_ID" version="3.0">

    <display-name>Display Name Here</display-name>

    <!-- Spring -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/web-context.xml</param-value>
    </context-param>

    <!-- Vaadin servlet -->
    <servlet>
        <servlet-name>Coconut Scooter</servlet-name>
        <servlet-class>ru.xpoft.vaadin.SpringVaadinServlet</servlet-class>
        <init-param>
            <description>Vaadin UI to display</description>
            <param-name>beanName</param-name>
            <param-value>mainUI</param-value>
        </init-param>
        <init-param>
            <description>Application widgetset</description>
            <param-name>widgetset</param-name>
            <param-value>com.foo.bar.baz.bleep.common.widgetset.AppWidgetSet</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>Coconut Scooter</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>Coconut Scooter</servlet-name>
        <url-pattern>/VAADIN/*</url-pattern>
    </servlet-mapping>

    <context-param>
        <description>Vaadin production mode</description>
        <param-name>productionMode</param-name>
        <param-value>false</param-value>
    </context-param>

</web-app>

web-context.xml

<context:component-scan base-package="com.foo.bar.baz.bleep"
    use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

cucumber.xml

<context:component-scan base-package="com.foo.bar.baz.bleep">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>

<import resource="classpath:business-context.xml" />

UPDATE 2

As in the comments of M. Deinum's answer, when neither using Spring MVC nor dispatcherservlet you can safely load all of your context-files in the ContextLoaderListener in order for your beans to be loaded into a single ApplicationContext. This can be done by simply using component-scan that scans all your packages. See updated context-files below.

web-context.xml

<context:component-scan base-package="com.foo.bar.baz.bleep" />

cucumber.xml

<context:component-scan base-package="com.foo.bar.baz.bleep" />

<import resource="classpath:business-context.xml" />
Roger
  • 2,684
  • 4
  • 36
  • 51
  • How did you declared the Dao in the Controller or where you used it? – araknoid Aug 30 '13 at 07:42
  • @araknoid I have not declared the dal-package (daos) in the Controller, because the controller doesn't need to know about it. However, I have declared the daos in the business-context.xmls, which is resources to the cucumber.xml. – Roger Aug 30 '13 at 07:59
  • As a tip... You should group up the component scans as this: `` instead of having multiple xml elements. – araknoid Aug 30 '13 at 08:21
  • Yup, got it. Thanks for the advice. Unfortunately this won't solve my problem. – Roger Aug 30 '13 at 08:32
  • Have you tried importing your `business-context.xml` resource also in the `context.xml`? – araknoid Aug 30 '13 at 08:56
  • I'm not sure why the web layer would need to have anything to do with the dal. Please elaborate. I tried this and received the same error, I'm afraid. – Roger Aug 30 '13 at 09:09
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/36521/discussion-between-araknoid-and-roger) – araknoid Aug 30 '13 at 09:20
  • Your configuration is wrong... You are duplicating your bean instancs by loading the same configuration twice!!! Your web layer should only scan for web related components NOT everything else, the rest will be looked up from the root applicationcontext (which should contain your services, daos etc.). – M. Deinum Aug 30 '13 at 10:07

1 Answers1

1

Your configuration is wrong, your web layer should only scan for web related beans, you are also scanning for other types of beans which you are also scanning for in your other application context. Basically you are creating 2 instances of yuor service, the second is failing because it cannot satisfy its dependencies.

So basicallyy your web should do this

<context:component-scan base-package="com.foo.bar.baz.bleep.web" />

Nothing more, nothing less. The other can remain as is.

See my related answer here

Community
  • 1
  • 1
M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • Also, for better control of what beans get created , XML configurations could be preffered. – Gyan Aug 30 '13 at 10:18
  • How would my web layer be able to know about my services if I don't scan the bll layer? In my web layer - Do I not have to @Inject my sampleAppService in order to use someRandomService(), (hence scan of the bll-package)? – Roger Aug 30 '13 at 10:23
  • See my related answer. In general you have a `ContextLoaderListener` which bootstraps a spring `ApplicationContext` (generally called to root context). This root context functions as a parent for the `ApplicationContext` loaded by the `DispatcherServlet`. Now when your bean needs a dependency which it cannot find in its own context it will look at its parent. – M. Deinum Aug 30 '13 at 10:25
  • Ok, I hope I understood your related answer correctly. So what I did was using context:component-scan + filters to component-scan for ONLY the Controller annotation in the web layer and component-scan for everything BUT the Controller annotation in the business layer. This resulted in a successful compile and a successful startup of Tomcat, but when I'm trying to @Inject sampleAppService it says I have no matching bean of that type. – Roger Aug 30 '13 at 12:33
  • post your new configuration including your web.xml... And the stacktrace you get. – M. Deinum Aug 30 '13 at 12:44
  • I guess I'm using my cucumber.xml in my bll as "root-context" as this is where I scan for "everything else". Maybe I'm missing a reference in my web-context to that context? Though I can't find it in your answer. There is just the two component-scans... – Roger Aug 30 '13 at 12:45
  • As asked, post the new stacktrace, configuration including the web.xml... Else we are just guessing. – M. Deinum Aug 30 '13 at 12:52
  • My answer assumed you had a `ContextLoaderListener` **AND** `DispatcherServlet` and that you where using Spring MVC. However you aren't... Simply load both the web-context.xml and cucumber.xml in the `ContextLoaderListener`. Also having multiple ` elements is also pretty useless now as everything is loaded in a single `ApplicationContext`. Simply create a `` that scans for everything. – M. Deinum Aug 30 '13 at 13:08
  • After a chat with araknoid I got it working by just adding to my original web-context.xml. However, after seeing your answer I'm not so sure that solution is a good practice. – Roger Aug 30 '13 at 13:09
  • @M.Deinum Thanks for your help! Sorry for letting you fall into the DispatcherServlet-trap there. I didn't know I wasn't using it. I have a better understanding of how the bean wiring works now, but I'm still confused on how beans are not duplicated when scanning the way I do in my second update (see original post) - which is, by the way, working! Feel free to make another answer or edit/delete this one so I can tick it as accepted, now when you got all the information you needed! – Roger Aug 30 '13 at 13:39
  • 1
    Everything is loaded in a single ApplicationContext (the meta data consists of multiple xml files but they are all loaded in a single ApplicationContext). All `` are merged in a single one. You only end up with duplicatie beans if there is another ApplicationContext doing the same `` or if you explicitly define another bean of the same type (which is not overriding the other definition(s) ). – M. Deinum Aug 31 '13 at 18:11