Loading classes using Spring inside an eclipse OSGi container does not seem to work. Fix?

2.6k Views Asked by At

I am somewhat puzzled about Spring's classloading behavior inside an eclipse OSGi container (which is the basis for the TIBCO ActiveMatrix runtime I am trying to get this running on) and boiled this down to the below simple example.

In it (the TestComponent object is created and called inside an OSGi bundle's Activator class, but I don't think that this is important here) I first create an object direcly, just to double-check, that its class exists and can be instantiated. This of course works as one would expect... Second, I want to create a second object of that very same class using Spring (as I originally intended), but this fails with a ClassNotFoundException.

Spring claims, it can't find that class (even though the package and class name definitely match), so I even added a .setClassLoader(...) call to pass it the very same classloader that just successfully loaded that very same class, but Spring still fails to locate that class. Any clue of why that is so? I have ran out of ideas. What am I missing?

Later edit: I just had to realize, that it is NOT the ...getBean(...)-method call that crashes, but rather already the ClassPathXmlApplicationContext()-constructor. I.e. the object is already created in that constructor and not only later in the subsequent getBean(...)-method call. Thus my attempt to pass-in the classloader is futile as it already comes to late. So the question thus rather is: how can I pass-in the context's class loader to that constructor (or to the factory or whatever Spring uses internally to create the ClassPathXmlApplicationContext object)?

My example:

I first defined an interface for the class to be created via Spring:

package com.example.some_package_0;    
public interface SomeInterface 
{
    public String getSomeString();
}

... and a class, implementing this interface:

package com.example.some_package_1;

import com.example.some_package_0.SomeInterface;

public class SomeClassA implements SomeInterface
{
    private String someProperty;

    public void setSomeProperty(String someProperty) {
        this.someProperty = someProperty;
    }
    public String getSomeString() {
        return this.someProperty;
    }
}

My Test-program reads

public class TestComponent 
{
    import com.example.some_package_0.SomeInterface;
    import com.example.some_package_1.SomeClassA;

    public void test() {
        SomeClassA obj1 = new SomeClassA();
        obj1.setSomeProperty("SomeClassA-object (directly created)");
        System.out.println("@@ message=\"" + obj1.getSomeString() + "\"");          

        ClassPathXmlApplicationContext applicationContext;          
        applicationContext = new ClassPathXmlApplicationContext("/META-INF/package1_beans.xml");
        applicationContext.setClassLoader(Thread.currentThread().getContextClassLoader());
        SomeInterface obj2 = (SomeInterface) applicationContext.getBean("bean1");
        System.out.println("@@ message=\"" + obj2.getSomeString() + "\"");
    }
}

The /META-INF/package1_beans.xml being used reads:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans classpath:/org/springframework/beans/factory/xml/spring-beans-2.5.xsd">

    <bean id="bean1" class="com.example.some_package_1.SomeClassA">
        <property name="someProperty"><value>SomeClassA-object (created via Spring)</value></property>
    </bean>
</beans>

The exception reads:

org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [com.example.some_package_1.SomeClassA] for bean with name 'bean1' defined in class path resource [META-INF/package1_beans.xml]; nested exception is java.lang.ClassNotFoundException: com.example.some_package_1.SomeClassA
    at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1141)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:524)
    at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1177)
    at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:758)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:422)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:728)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:380)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
    at com.example.test_spring_example.TestComponent.testOperation(TestComponent.java:71)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.tibco.amf.platform.runtime.componentframework.internal.proxies.operation.OperationHandler.invokeMethodWithThreadContext(OperationHandler.java:667)
    at com.tibco.amf.platform.runtime.componentframework.internal.proxies.operation.AsyncToSyncOperationHandler.invoke(AsyncToSyncOperationHandler.java:98)
    at com.tibco.amf.platform.runtime.componentframework.internal.proxies.ProxyInvocationHandlerRegistry$ProxyInvocationContext.invoke(ProxyInvocationHandlerRegistry.java:411)
    at $Proxy67.invoke(Unknown Source)
    at com.tibco.amf.binding.soap.runtime.transport.http.SoapHttpInboundEndpoint.processHttpPost(SoapHttpInboundEndpoint.java:565)
    at com.tibco.amf.binding.soap.runtime.transport.http.SoapHttpServer.doPost(SoapHttpServer.java:195)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
    at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502)
    at org.mortbay.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1213)
    at com.tibco.governance.pa.amxcomponent.pep.http.HttpPepFilter.doFilter(HttpPepFilter.java:126)
    at org.mortbay.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1205)
    at com.tibco.amf.implementation.common.httpfilter.GenericComponentFilter.doFilter(GenericComponentFilter.java:65)
    at org.mortbay.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1205)
    at com.tibco.amf.hpa.tibcohost.jetty.internal.ConnectorFilter.doFilter(ConnectorFilter.java:49)
    at org.mortbay.jetty.servlet.ServletHandler$Chain.doFilter(ServletHandler.java:1205)
    at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
    at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
    at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
    at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230)
    at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114)
    at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    at org.mortbay.jetty.Server.handle(Server.java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:536)
    at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:928)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:747)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:405)
    at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:662)
Caused by: java.lang.ClassNotFoundException: com.example.some_package_1.SomeClassA
    at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:513)
    at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:429)
    at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:417)
    at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(DefaultClassLoader.java:107)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    at org.springframework.util.ClassUtils.forName(ClassUtils.java:211)
    at org.springframework.beans.factory.support.AbstractBeanDefinition.resolveBeanClass(AbstractBeanDefinition.java:385)
    at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1138)
    ... 46 more
2

There are 2 best solutions below

0
On

Actually, the solution turned out to be almost trivial! As I already stated in my comment to @Robin apparently in OSGi the current thread's classloader and the current context or class' classloader are not the same!

So, all I had to do in the end was to set the current thread's context-classloader to the calling object's classloader and that's it! I.e. one doesn't even need to dig into OSGi to obtain the bundle's class loader or fiddle with manifests or POMs or anything like that - just tell Spring to use "my" own class' classloader and off you go!

     ...
        // need to set the context class loader to "my" class loader to make Spring work:
        Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());         
        ClassPathXmlApplicationContext applicationContext;          
        applicationContext =
            new ClassPathXmlApplicationContext("/META-INF/package1_beans.xml");
        SomeInterface obj2 = (SomeInterface) applicationContext.getBean("bean1");
        System.out.println("@@ message=\"" + obj2.getSomeString() + "\"");
     ...

And - yes - one probably should set the thread's class loader back to the original value after that, just in case - so another good used for a try { ... } finally { ... } clause. :-)

Thanks all for responding! M.

0
On

I have one other idea.

Instead of initializing your Spring context manually. Try adding this to your pom.xml (if you are using Maven):

<configuration>
    <instructions>
        <Spring-Context>spring/*.xml</Spring-Context>

Make it point to the location of your applContext. This seems a neater way to hook it up as well. If you are not using Maven youll need to add manually to the Manifest, Im not sure exactly the right syntax for that