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:
What am I doing wrong? Obviously =)
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" />