Reputation: 883
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
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
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
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