Spring – applicationContext not finding Controllers for Servlet context

springspring-annotationsspring-mvc

I have a Spring web app with an applicationContext.xml and a dispatcher-servlet.xml configuration. I've defined the <context:component-scan /> in applicationContext.xml, but when I run my app the Controllers are not found unless I also add <context:component-scan /> to the dispatcher-servlet.xml. I'm using the same base-package in both, so that's not the issue.

I am confused, because I thought that the applicationContext.xml was a parent of dispatcher-servlet.xml. Wouldn't putting <context:component-scan /> in applicationContext.xml suffice?

web.xml

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


<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

</web-app>

EDIT: I am also using mvc:annotation-driven in the dispatcher-servlet.xml, which is supposed to pick up Controllers (I thought?).

EDIT 2: Here are the config files. I removed a bunch of Spring Security and OAuth settings from applicationContext.xml (for security reasons and being they probably aren't relevant anyway).

applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
xmlns:context="http://www.springframework.org/schema/context" xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
      http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
      http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

<context:component-scan base-package="bar.foo"/>
<context:property-placeholder location="classpath:my.properties" />
<bean class="bar.foo.ServicesConfig" />

</beans>

dispatcher-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
      http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

<context:component-scan base-package="bar.foo.controller" />
<mvc:annotation-driven/>
<mvc:default-servlet-handler />

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/" />
    <property name="suffix" value=".jsp" />
    <property name="order" value="2" />
</bean>

<bean id="contentViewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
    <property name="mediaTypes">
        <map>
            <entry key="json" value="application/json" />
        </map>
    </property>
    <property name="defaultViews">
        <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />
    </property>
    <property name="order" value="1" />
</bean>

</beans>

EDIT 3: Okay, this is interesting. My services and dao classes are in a different project (JAR) that I reference from the web project. I am using java-based config and referencing it from the applicationContext.xml:

<bean class="bar.foo.config.ServicesConfig" />

So, this means there are only Controller annotations in my web project (where applicationContext.xml is located). In retrospect, removing context:component-scan from my applicationContext.xml should not have any affect, since there are no annotations except for @Controller ones (FIX to EDIT: there are some @Autowired annotations). But, when I remove the context:component-scan from applicationContext.xml, it says that the Controllers (found from dispatcher servlet scan) cannot find my Service classes. Shouldn't the reference to the ServicesConfig be enough? Here is the ServicesConfig class for references – it has its own component scan for the Services, which are a different package from what the applicationContext.xml was scanning.

@Configuration
@ComponentScan({ "some.other.package", "another.package" })
@ImportResource({ "classpath:commonBeans.xml" })
@PropertySource({ "classpath:services.properties",
"classpath:misc.properties" })
public class ServicesConfig {
  // Bean definitions //
}

SOLUTION:

When I removed context:component-scan from my root context, the Controllers were not picking up the autowired services beans. This was because the root context references my services java-based config Bean, but I did not have the root context setup to scan for Components. Hence, when I add component scanning to the root context (applicationContext.xml) everything works. Here is what I have now:

applicationContext.xml:

<bean class="bar.foo.config.ServicesConfig" />
<context:component-scan base-package="bar.foo.config" />

dispatcher-servlet.xml:

<context:component-scan base-package="bar.foo.controller" />

I have the web context setup to pickup Controller, Autowired, and any other annotations in the controller package – I'm not sure if this is best practice or not.

Best Answer

You are right - there are two different application contexts, the root application context loaded up by ContextLoaderListener (at the point the ServletContext gets initialized), and the Web Context (loaded up by DispatcherServlet), the root application context is the parent of the Web context.

Now, since these are two different application contexts, they get acted on differently - if you define component-scan for your services in application context, then all the beans for the services get created here.

When your Dispatcher servlet loads up it will start creating the Web Context, at some point(driven by <mvc:annotation-driven/> it will create a mapping for your uri's to handler methods, it will get the list of beans in the application context(which will be the web application context, not the Root application Context) and since you have not defined a component-scan here the controller related beans will not be found and the mappings will not get created, that is the reason why you have to define a component-scan in the dispatcher servlets context also.

A good practice is to exclude the Controller related beans in the Root Application Context:

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

and only controller related one's in Web Application Context:

<context:component-scan base-package="package" use-default-filters="false">
    <context:include-filter expression="org.springframework.stereotype.Controller" type="annotation" />
</context:component-scan>
Related Topic