1

I have read Injecting Jaxb2Marshaller in Spring MVC controller and Difference between applicationContext.xml and spring-servlet.xml in Spring Framework etc but feeling confused on why my case is not working.

Here is what I have in my app contexts and web.xml (trimmed down to show only related parts):

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app .... >

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/app-context.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    ....
    <servlet>
        <servlet-name>spring-rest</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring-rest</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>
    ...
</web-app>

spring-rest-servlet.xml

<beans ....>

    <!-- Only scan for Controllers in Servlet context -->
    <context:component-scan base-package="com.fil.ims" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>

    <mvc:annotation-driven />

    <context:annotation-config />

    <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
         ....
    </bean>
</beans>

I have a controller something like this:

@Controller
public class FooControllerImpl implements FooController {
    @Inject
    @Named("jaxb2Marshaller")
    Jaxb2Marshaller jaxb2Marshaller;

    public void setJaxb2Marshaller(Jaxb2Marshaller jaxb2Marshaller) {
        this.jaxb2Marshaller = jaxb2Marshaller;
    }
    //.....
}

I found that I cannot inject the jaxb2Marshaller in the servlet context (NoSuchBeanDefinitionException is thrown). However if I have the jaxb2Marshaller put in the main app-context (app-context.xml), it can be injected normally.

I am confused why it happens. From my understanding, if I have ContextLoaderListener in web.xml, the provided context config (app-context.xml) will be used to create a top-level app context, and then the dispatcher servlet context (spring-rest-servlet.xml in my example) will be created as a child context under the top-level app context.

What I don't understand is, both the controller and the bean to be injected (jaxb2Marshaller) is all created under the dispatcher servlet context, why I cannot inject jaxb2Marshaller into my FooController? (I understand it will not work if the bean to be injected is located in child app context, but in this case it seems not.)

Can someone explain why?


Update:

I have done another experiment (which makes me even more confusing), by implementing ApplicationContextAware in my controller, and from the controller, I do applicationContext.getBean("jaxb2Marshaller", Jaxb2Marshaller.class). A valid bean is returned (which is aligned with my understanding). It seems only injection through annotation is not working. Any clues?

Community
  • 1
  • 1
Adrian Shum
  • 38,812
  • 10
  • 83
  • 131

1 Answers1

0

(This post was once deleted because I found the solution myself. However I think it isgood to post my answer here for reference, as seems that I am not the only one faced similar problem)

My understandings in the relationship between app contexts and beans, are in fact correct:

  1. There is a root context
  2. Dispatcher servlet context is a child context under the root context
  3. Controllers and Jaxb2Marshaller are created in the servlet context.

If Controllers and Jaxb2Marshaller are both in the same context, why injection is failing?

The answer is: the injection of controller with Jaxb2Marshaller in the servlet context does not fail. The failing message comes from another controller in the root context.

Root problem is I have used the default component-scan in root context, for which it will scan for classes annotated with @Service, @Repository etc, including @Controller. That means my controllers are scanned and created in root context too. Once I put @Inject/@Autowired in my controller to inject a bean which supposed to be created in servlet context, it will cause an error because that "unexpected copy" of controller in root context cannot see the bean in child servlet context.

Solution is straight-forward: exclude scanning of Controller in root context, and scan only Controller in servlet context:

root context xml:

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

dispatcher context xml:

<context:component-scan base-package="com.foo" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
Adrian Shum
  • 38,812
  • 10
  • 83
  • 131