Paulius K.
Paulius K.

Reputation: 883

Organizing beans in ServletContext and ContextLoaderListener

I have a spring-webmvc+spring-security application and I encountered a problem with bean injection. First of all, org.springframework.web.servlet.DispatcherServlet, org.springframework.web.filter.DelegatingFilterProxy and org.springframework.web.context.ContextLoaderListener are all defined in web.xml, servlet context is called servlet-context.xml and application context -- application-context.xml.

Now, the problem arises when I create my service, which uses org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping. If I create this bean in application-context.xml, no bean is found of RequestMappingHandlerMapping type. If, however, I put it in servlet-context.xml, RequestMappingHandlerMapping is found, I can use my service in Controllers, but I cannot use it in other services, created in application-context.xml.

My question is how to organize bean creation in such application configuration. I feel like I'm missing something simple here.

Edit: when application is started with a specific profile, a filter is added to the filter chain, which allows you to mock API calls. This filter is defined in application context, because that's where spring security is defined. It also needs to access a service, which can provide a list of all request mappings and our custom permissions associated with them (and no, they don't translate to spring roles that well). However, this service needs to be defined in servlet context, because it needs to access RequestMappingHandlerMapping.

Edit #2: I produced a MWE (probably shoud be Minimal Not Working Example?) here: https://github.com/guilty/separate-spring-contexts.

Now, there's an ExampleController and CoreService. They are both created in separate contexts and need to access MappedUrlsService, which in turn expects RequestMappingHandlerMapping bean to be accessible. Depending on where you actually create the MappedUrlsService bean, you either get that RequestMappingHandlerMapping bean is not found, or MappedUrlsService bean is not found. Here's a stack trace of when RequestMappingHandlerMapping is not found:

SEVERE: Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.github.guilty.spring.service.CoreServiceImpl#0': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.github.guilty.spring.service.MappedUrlsService com.github.guilty.spring.service.CoreServiceImpl.mappedUrlsService; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.github.guilty.spring.service.MappedUrlsService] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:292)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1185)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:703)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:760)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
        at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:403)
        at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306)
        at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:106)
        at org.eclipse.jetty.server.handler.ContextHandler.callContextInitialized(ContextHandler.java:800)
        at org.eclipse.jetty.servlet.ServletContextHandler.callContextInitialized(ServletContextHandler.java:446)
        at org.eclipse.jetty.server.handler.ContextHandler.startContext(ContextHandler.java:792)
        at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:296)
        at org.eclipse.jetty.webapp.WebAppContext.startWebapp(WebAppContext.java:1341)
        at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1334)
        at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:744)
        at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:497)
        at org.eclipse.jetty.maven.plugin.JettyWebAppContext.doStart(JettyWebAppContext.java:281)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:132)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:114)
        at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:60)
        at org.eclipse.jetty.server.handler.ContextHandlerCollection.doStart(ContextHandlerCollection.java:154)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:132)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:114)
        at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:60)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:132)
        at org.eclipse.jetty.server.Server.start(Server.java:357)
        at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:114)
        at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:60)
        at org.eclipse.jetty.server.Server.doStart(Server.java:324)
        at org.eclipse.jetty.maven.plugin.JettyServer.doStart(JettyServer.java:68)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
        at org.eclipse.jetty.maven.plugin.AbstractJettyMojo.startJetty(AbstractJettyMojo.java:564)
        at org.eclipse.jetty.maven.plugin.AbstractJettyMojo.execute(AbstractJettyMojo.java:360)
        at org.eclipse.jetty.maven.plugin.JettyRunMojo.execute(JettyRunMojo.java:168)
        at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo(DefaultBuildPluginManager.java:133)
        at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:208)
        at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153)
        at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:145)
        at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:108)
        at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject(LifecycleModuleBuilder.java:76)
        at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build(SingleThreadedBuilder.java:51)
        at org.apache.maven.lifecycle.internal.LifecycleStarter.execute(LifecycleStarter.java:116)
        at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:361)
        at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:155)
        at org.apache.maven.cli.MavenCli.execute(MavenCli.java:584)
        at org.apache.maven.cli.MavenCli.doMain(MavenCli.java:213)
        at org.apache.maven.cli.MavenCli.main(MavenCli.java:157)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced(Launcher.java:289)
        at org.codehaus.plexus.classworlds.launcher.Launcher.launch(Launcher.java:229)
        at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode(Launcher.java:415)
        at org.codehaus.plexus.classworlds.launcher.Launcher.main(Launcher.java:356)
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.github.guilty.spring.service.MappedUrlsService com.github.guilty.spring.service.CoreServiceImpl.mappedUrlsService; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.github.guilty.spring.service.MappedUrlsService] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:508)
        at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:289)
        ... 63 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.github.guilty.spring.service.MappedUrlsService] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1103)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:963)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:858)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:480)
        ... 65 more

Run the example with mvn jetty:run. Note, that Java 7 and Maven are required to run it.

Upvotes: 0

Views: 607

Answers (3)

Serge Ballesta
Serge Ballesta

Reputation: 149125

Well I won't discuss the why of your bean organization, but only the how.

You can inject beans from root context in beans from servlet context but not the opposite, as servlet context is created as root context as its parent.

IMHO it is really bad design to have a bean in service layer depending on a bean in controller layer, and in real world you should try to avoid it. but let's keep on.

I suggest you to create a relay bean in controller package and servlet contex and leave MappedUrlsService in service package and root context. Then you inject the service in relay, but in reality write a pointer to relay in the service :

public class MappedUrlsRelayImpl {

    @SuppressWarnings("SpringJavaAutowiringInspection")
    @Autowired private RequestMappingHandlerMapping requestMappingHandlerMapping;

    // all the stuff that is presently in MappedUrlsServiceImpl
    // ...

    @Autowired
    private void setMappedUrlsServiceImpl (MappedUrlsServiceImpl serviceImpl) {
        serviceImpl.setMappedUrlsRelayImpl(this);
    }
}

The your service bean is simply :

public class MappedUrlsServiceImpl implements MappedUrlsService {

    @SuppressWarnings("SpringJavaAutowiringInspection")
    private MappedUrlsRelayImpl relay;

    @Override
    public Set<String> getMappedUrls() {
        return relay.getMappedUrls();
    }

    public void setMappedUrlsRelayImpl(MappedUrlsRelayImpl relay) {
        this.relay = relay;
    }
}

It is pretty ugly because a service depends on a controller layer class and I reverse a Spring injection of dependancy, but it works.

To be complete the beans in servlet context

<bean class="com.github.guilty.spring.controller.ExampleController" />
<bean class="com.github.guilty.spring.controller.MappedUrlsRelayImpl" />

and in root context

<bean class="com.github.guilty.spring.service.CoreServiceImpl" />
<bean class="com.github.guilty.spring.service.MappedUrlsServiceImpl" />

Upvotes: 0

Bart
Bart

Reputation: 17361

A RequestMappingHandlerMapping is created in the context of DispatcherServlet which can access beans defined in the root context (application-context.xml) but not the other way around. You could however define a RequestMappingHandlerMapping bean in the root context. Spring MVC should pick that up with no problem. But it really does not make sense outside the servlet context.

Upvotes: 0

dectarin
dectarin

Reputation: 1006

RequestMappingHandlerMapping is designed to be used in the controller layer only (typically with <mvc:annotation-driven/>) so should only be in your servlet-context.xml

Is there a reason you need this in your service layer?

Upvotes: 1

Related Questions