Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring component-scan broken in osgi container

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. :(

like image 632
Ben Thurley Avatar asked May 28 '13 14:05

Ben Thurley


People also ask

What is the difference between @component and ComponentScan?

@Component and @ComponentScan are for different purposes. @Component indicates that a class might be a candidate for creating a bean. It's like putting a hand up. @ComponentScan is searching packages for Components.

How do I enable component scanning in Spring?

Using @ComponentScan in a Spring Application. With Spring, we use the @ComponentScan annotation along with the @Configuration annotation to specify the packages that we want to be scanned. @ComponentScan without arguments tells Spring to scan the current package and all of its sub-packages.

Why ComponentScan is required?

@ComponentScan tells Spring in which packages you have annotated classes which should be managed by Spring. Spring needs to know which packages contain spring beans, otherwise you would have to register each bean individually in(xml file). This is the use of @ComponentScan.

When should I use ComponentScan?

The @ComponentScan annotation is used with the @Configuration annotation to tell Spring the packages to scan for annotated components. @ComponentScan also used to specify base packages and base package classes using thebasePackageClasses or basePackages attributes of @ComponentScan.


2 Answers

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:

<context:component-scan base-package="com.company.application.services" />

Do this instead:

<bean id="service1" class="com.company.application.services.impl.Service1" />
<bean id="service2" class="com.company.application.services.impl.Service2" />

You can at least still do this:

<context:annotation-config />

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

like image 199
Ben Thurley Avatar answered Sep 19 '22 10:09

Ben Thurley


For your web application to run with spring you need spring dm, and therefore you need to have something like the following:

    <context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.osgi.web.context.support.OsgiBundleXmlWebApplicationContext</param-value>
</context-param>

Or for a complete sample take a look at the spring-osgi sample at pax-web: Sample web.xml

like image 32
Achim Nierbeck Avatar answered Sep 19 '22 10:09

Achim Nierbeck