Spring component-scan broken in osgi container
Asked Answered
G

5

6

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

Gloriole answered 28/5, 2013 at 14:54 Comment(6)
You may register your components as osgi services on a per bundle basis. When injecting dependencies you then need to consider registered osgi services.Dagnydago
You should also read Why classpath scanning is bad for youDagnydago
Fair point about classpath scanning but how else can I neatly wire my components? How do I go about registering components as osgi services? This is getting messy as I'm not using an osgi container for local testing. I don't even care that the remote server is osgi, I just want to be able to deploy my app.Gloriole
It would be good if people explained why they down voted. It's a genuine question that I'm stuck on and so far the answers have either not worked or just criticized using classpath scanning. I thought this was a pretty integral part of spring which itself is a mainstream widely used framework. I've had this osgi architecture forced upon me by the container I have to use. I'm currently assessing my options.Gloriole
Also, I am really grateful to those who have responded publicly. They've all been useful in helping me decide what to do.Gloriole
I don't know why you were downvoted either. I have upvoted but it only brings you back to zero, sorry! I will make further comments in the thread attached to my answer below.Erwin
G
11

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

Gloriole answered 30/5, 2013 at 10:54 Comment(2)
Thanks for sharing, this allowed me finally make it work ! It seems three years later it still is a problem.Lease
Funnily enough we just upgraded yesterday from IAS 8.1 to 8.5. The old version was based on the "lotus expeditor" and I was never that impressed with it and this was one of a number of workarounds I had to use. 8.5 appears to be based on WAS and is infinitely better. It's much faster to start/stop, you can set JVM options more easily, you can choose the JRE so no longer fixed on 6 and I'm fairly sure it will now support classpath scanning. I need to test it but if it's built on WAS then it should do. If you're on the IBM i then definitely look into upgrading.Gloriole
A
2

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

Armalda answered 29/5, 2013 at 6:49 Comment(4)
This looks more like what I wanted. I'm going to play with this and see if I can get it working. I don't have much time unfortunately so hope its not too tricky. Cheers.Gloriole
Immediately run into problems. Tried adding spring-osgi-web as a maven dependency but now the build is broken because it can't resolve servlet-api.osgi. The maven page for this returns a 404. :( mvnrepository.com/artifact/org.springframework.osgi/…Gloriole
I'm not sure what your trying to do, but the sample is a sample wab and if you want to test it you need a osgi capable web container, for example Pax-Web. It's not something to add in your application.Armalda
what about if you want to deploy your war in OSGI and in another container (like Tomcat) at the same time?Basement
O
2

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.

/**
 * This class serves as the entry point from OSGI framework to Spring framework
 *
 */
@Configuration
//@ComponentScan
//@SpringBootApplication
public class PseudoSpringBootApplication {

  @Autowired
  ApplicationContext context;

  @Bean
  DummySpringBean1 dummySpringBean1() {
    return new DummySpringBean("This is a dummy message");
  }

  @Bean
  DummySpringBean2 dummySpringBean2() {
    return new DummySpringBean("This is a dummy message");
  }

}

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.

//@Component
class DummySpringBean1 {

  private final String dummyMessage;

  DummySpringBean1(String dummyMessage) {
    this.dummyMessage = dummyMessage;
  }
}

For DummySpringBean2, it has dependency on DummySpringBean1.

//@Component
class DummySpringBean2 {

  private final String dummyMessage;

  @Autowired
  private DummySpringBean1 bean1;

  DummySpringBean(String dummyMessage) {
    this.dummyMessage = dummyMessage;
  }
}

So I moved all bean declaration to the central configuration class above.

Ori answered 10/3, 2017 at 15:29 Comment(3)
Just to let you know my opinion on this: if it is a workaround, it is a kind of solution. Besides, I don't know how you would have fit this into a comment. On the other hand, my knowledge of Spring is low, I cannot say much about the workaround itself. May I encourage you to keep contributing to the site. Cheers.Stereoisomer
@Irnzcig, Thanks for the encouragement. I will do more contributing to the site for sure, and I feel the need to share my developer experiences to more developers, especially junior developer.Ori
This is essentially the same as my solution except you're using the java @Configuration instead of xml config to register the beans. It's a good suggestion though.Gloriole
E
1

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.

Erwin answered 28/5, 2013 at 17:11 Comment(2)
So I keep being told. However, it is quite an integral part of the spring framework. Surely there's a way to get these apps running without re-writing them in the osgi style?Gloriole
I can't really answer your question because I stopped using Spring a long time ago; but I'd be very surprised if classpath scanning was the only way, because the the Spring architects should know better than that. I think if you dig deep into the documentation you might be able to find an API or extension mechanism that lets you control the discovery process for annotated beans. Good luck... if you do find an answer, be sure to write here about here.Erwin
C
0

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)

# Remove any old file before new version
rm -rf tracker;
# Recreate executable folder
mkdir tracker;
# copy main jar to executable folder
cp TrackServer.jar tracker;
# move to executable folder
cd tracker;
# Extract jar on executable folder
jar xf TrackServer.jar;
# Execute main class (at the end of the line) 
# with all the jar dependencies that came 
# in original TrackerServer.jar
java -cp '.;'`ls -dm *.jar | sed -e ':a' -e 'N' -e '$!ba' -e 's/\n/ /g' | sed -e 's/,[ \n\r\t]/:/g'` incodemode.mainApp.Main;
# sed bits are for getting .:dependency1.jar:dependency2.jar etc...
# incodemode.mainApp.Main is the full class path of the Main class
Capping answered 1/3, 2016 at 4:59 Comment(3)
Is this an answer for a different question?Gloriole
Yes, didn't want to do manual lookup, it happened to me with controllers, yet the error was the same, so instead moved bitcode to the file system, later I moved it to /run/shm/tracker instead of /var/www/.../tracker. If you remove the comments you have just a few lines, ah and this project starts its own tomcat. Placed it here since the error was the same at low level and this was the answer that google gave me when looking for the error.Capping
You may have had a similar error but I don't think this is the same problem. My problem was getting spring classpath scanning to work with an OSGi container. We use tomcat on our local machines and classpath scanning works no problem.Gloriole

© 2022 - 2024 — McMap. All rights reserved.