I'm trying to integrate spring DI into an existing jaxws project. I've got this working locally on tomcat but when I deploy to the remote container it doesn't appear to do the classpath scanning. I can see in the logs that on tomcat it registers my @Components as beans but on the remote server they're not mentioned at all.
What I do see is the stacktrace below. It looks like it's down to the quirky web container I have to use. It's the "integrated application server for IBM i". http://www-03.ibm.com/systems/i/software/ias/ I believe this is built on the eclipse architecture and when you install a war file it converts each app into a bundle. That's great but it's breaking my classpath scanning. :(
Does anyone have a solution for this? Thanks
673 [Thread-6] WARN org.springframework.core.io.support.PathMatchingResourcePatternResolver - Cannot search for matching files underneath URL [bundleresource://32/com/company/application/] because it does not correspond to a directory in the file system
java.io.FileNotFoundException: URL [bundleresource://32/com/company/application/] cannot be resolved to absolute file path because it does not reside in the file system: bundleresource://32/com/company/application/
at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:205)
at org.springframework.core.io.AbstractFileResolvingResource.getFile(AbstractFileResolvingResource.java:52)
at org.springframework.core.io.UrlResource.getFile(UrlResource.java:169)
at org.springframework.core.io.support.PathMatchingResourcePatternResolver.doFindPathMatchingFileResources(PathMatchingResourcePatternResolver.java:526)
at org.springframework.web.context.support.ServletContextResourcePatternResolver.doFindPathMatchingFileResources(ServletContextResourcePatternResolver.java:92)
at org.springframework.core.io.support.PathMatchingResourcePatternResolver.findPathMatchingResources(PathMatchingResourcePatternResolver.java:347)
at org.springframework.core.io.support.PathMatchingResourcePatternResolver.getResources(PathMatchingResourcePatternResolver.java:266)
at org.springframework.context.support.AbstractApplicationContext.getResources(AbstractApplicationContext.java:1269)
at org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider.findCandidateComponents(ClassPathScanningCandidateComponentProvider.java:248)
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:242)
at org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(ComponentScanBeanDefinitionParser.java:84)
at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:73)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1438)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1428)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:185)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:139)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:108)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:493)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:334)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:174)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:209)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:180)
at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:125)
at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:94)
at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:130)
at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:537)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:451)
at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:389)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:294)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:112)
at com.ibm.ws.webcontainer.webapp.WebApp.notifyServletContextCreated(WebApp.java:1678)
at com.ibm.ws.webcontainer.webapp.WebApp.commonInitializationFinish(WebApp.java:371)
at com.ibm.ws.webcontainer.webapp.WebApp.initialize(WebApp.java:347)
at com.ibm.ws.webcontainer.webapp.WebGroup.addWebApplication(WebGroup.java:134)
at com.ibm.ws.webcontainer.VirtualHost.addWebApplication(VirtualHost.java:145)
at com.ibm.ws.webcontainer.WebContainer.addWebApp(WebContainer.java:542)
at com.ibm.ws.webcontainer.WebContainer.addWebApplication(WebContainer.java:513)
at com.ibm.pvc.internal.webcontainer.trackers.WebApplicationServiceTracker.addingService(WebApplicationServiceTracker.java:94)
at org.osgi.util.tracker.ServiceTracker$Tracked.trackAdding(ServiceTracker.java:1064)
at org.osgi.util.tracker.ServiceTracker$Tracked.trackInitialServices(ServiceTracker.java:926)
at org.osgi.util.tracker.ServiceTracker.open(ServiceTracker.java:330)
at org.osgi.util.tracker.ServiceTracker.open(ServiceTracker.java:274)
at com.ibm.pvc.internal.webcontainer.trackers.XMLParserServiceTracker.initializeTrackers(XMLParserServiceTracker.java:520)
at com.ibm.pvc.internal.webcontainer.trackers.XMLParserServiceTracker.startWebcontainer(XMLParserServiceTracker.java:235)
at com.ibm.pvc.internal.webcontainer.trackers.XMLParserServiceTracker.addingService(XMLParserServiceTracker.java:140)
at org.osgi.util.tracker.ServiceTracker$Tracked.trackAdding(ServiceTracker.java:1064)
at org.osgi.util.tracker.ServiceTracker$Tracked.trackInitialServices(ServiceTracker.java:926)
at org.osgi.util.tracker.ServiceTracker.open(ServiceTracker.java:330)
at org.osgi.util.tracker.ServiceTracker.open(ServiceTracker.java:274)
at com.ibm.pvc.internal.webcontainer.WebContainerActivator.start(WebContainerActivator.java:45)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl$2.run(BundleContextImpl.java:1009)
at java.security.AccessController.doPrivileged(AccessController.java:251)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.startActivator(BundleContextImpl.java:1003)
at org.eclipse.osgi.framework.internal.core.BundleContextImpl.start(BundleContextImpl.java:984)
at org.eclipse.osgi.framework.internal.core.BundleHost.startWorker(BundleHost.java:350)
at org.eclipse.osgi.framework.internal.core.AbstractBundle.start(AbstractBundle.java:279)
at com.ibm.rcp.internal.util.BundleManager.start(BundleManager.java:74)
at com.ibm.rcp.internal.util.BundleManager.start(BundleManager.java:179)
at com.ibm.rcp.lifecycle.internal.application.BundleControlImpl.start(BundleControlImpl.java:125)
at com.ibm.rcp.lifecycle.internal.application.BundleControlImpl.start(BundleControlImpl.java:106)
at com.ibm.rcp.lifecycle.application.BundleControl.start(BundleControl.java:89)
at com.ibm.lwi.application.LWIApplication.run(LWIApplication.java:149)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:48)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:600)
at org.eclipse.equinox.internal.app.EclipseAppContainer.callMethodWithException(EclipseAppContainer.java:574)
at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:195)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:386)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:179)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:48)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:600)
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:561)
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:501)
at org.eclipse.equinox.launcher.Main.run(Main.java:1239)
at org.eclipse.equinox.launcher.Main.main(Main.java:1215)
at org.eclipse.core.launcher.Main.main(Main.java:30)
at com.ibm.lwi.LaunchLWI$1.run(LaunchLWI.java:731)
Looking at the code that throws the exception, it's checking if the protocol is file://.
if (!URL_PROTOCOL_FILE.equals(resourceUrl.getProtocol())) {
throw new FileNotFoundException(
description + " cannot be resolved to absolute file path " +
"because it does not reside in the file system: " + resourceUrl);
I can see that the app has been exploded onto the filesystem. I wonder if there is a way to override this loader in spring to not care about the protocol and obviously still work? I don't care for the osgi features of the container. I just want my app to deploy.
The fallback seems to be abandon classpath scanning in favour of defining beans in xml but that;d be a real step back imo. :(
I hate the idea of answering my own question but it seemed the best way to report back what I did. I've upvoted the answers so far because they were helpful for me to make a decision.
Through reading the answers I've learned a bit about why classpath scanning is bad. Nonetheless it's a pretty common part of the spring framework these days. My solution was to go "old skool" and manually define my beans.
Can't do this:
Do this instead:
You can at least still do this:
Annotation config means spring will still wire your beans together, it's just the process of discovering the beans that is being done manually. That's the compromise.
Ideally I would have preferred a means to deploy the project with component scanning but from what I can gather this requires changing the way the project is built specifically for an osgi container. My answer means it'll work both in an osgi and a normal container so it's less specialized.
If someone finds a way to deploy a war file with spring component scanning then I'll gladly re-consider the accepted answer.
Thanks
I tried commenting your initial post, however I can't do that due to my ranking in stackoverflow unfortunately. Don't get me wrong, I am not saying I have a solution here, but I would like share my workaround in newer version of Spring framework here.
Firstly, I have a big configuration class, which servers as the entry in this
AnnotationConfigApplicationContext(PseudoSpringBootApplication.class)
statement.Basically, as @ComponentScan doesn't work with OSGI plugin, (as mentioned in you answer post, this is mainly due to the fact that Spring framework try to scan classes assuming the artifacts are in file system, however, in this case, OSGI (e.g., Apache Felix) has transformed the FILE:// prefixed url to its internal bundle:// prefixed.
java.io.FileNotFoundException: URL [bundle://21.0:1/com/***] cannot be resolved to absolute file path because it does not reside in the file system: bundle://21.0:1/com/***
Nevertheless, @Autowired is still working. I've shown the example code below, where DummySpringBean2 has a dependency of DummySpringBean1 and is @Autowired annotated.For DummySpringBean2, it has dependency on DummySpringBean1.
So I moved all bean declaration to the central configuration class above.
I did this little script for linux so I unpack TrackServer.jar and execute the main class without loosing simplicity from Spring (newb to spring here so not so pleased to maneuver and add extra code there)
For your web application to run with spring you need spring dm, and therefore you need to have something like the following:
Or for a complete sample take a look at the spring-osgi sample at pax-web: Sample web.xml
Classpath scanning is a terrible idea and it will break in many runtimes, not just in OSGi. The OSGi service registry is a much more effective approach to the decoupling problem.
You can use the OSGi Service Registry outside of OSGi itself, take a look at PojoSR.
As for how to register and consume OSGi services... since you are using Spring it would be best to use Blueprint, which is an evolution of an older project called Spring Dynamic Modules.