2

I have a question about the usage of Java ClassLoader in OSGi.

I wrote two OSGi bundles, namely server bundle and client bundle.

In server bundle, I implemented BundleActivator like:

public class Activator implements BundleActivator {

    public void start(BundleContext context) {
        System.out.println("[Server:Activator.java:26] " + Activator.class.getClassLoader());
        context.registerService(HelloService.class, new HelloService(), null);
    }

    public void stop(BundleContext context) {
        System.out.println("Stopping the bundle");
    }
}

And in client bundle, I implemented BundleActivator like:

public class Activator implements BundleActivator {

    public void start(BundleContext context) {
        ServiceReference<HelloService> ref = context.getServiceReference(HelloService.class);
        HelloService service = context.getService(ref);
        System.out.println("[Client:Activator.java:48] " + HelloService.class.getClassLoader());
        System.out.println("[Client:Activator.java:49] " + Activator.class.getClassLoader());
    }

    public void stop(BundleContext context) {
        System.out.println("Stopping the bundle");
    }
}

And when I started OSGi, the console output:

[Server:Activator.java:26] org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@56b161a[osgi-server:1.0.0(id=54)] [Client:Activator.java:48] org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@56b161a[osgi-server:1.0.0(id=54)] [Client:Activator.java:49] org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader@3a1b72aa[osgi-client:1.0.0(id=55)]

As you can see, the classloader that loads HelloService is always DefaultClassLoader@56b161a no matter it is at server side or client side.

I can not understand this. In my knowledge, when class B is referenced in class A, the classloader of class B is the same as class A's classloader. But in OSGi, it seems not this way.

Can you enlighten me? Is there something I miss about Java ClassLoader? Or is OSGi doing something tricky?

The MANIFEST of server bundle is:

Manifest-Version: 1.0
Bnd-LastModified: 1452582379580
Build-Jdk: 1.7.0_45
Built-By: haoruan
Bundle-Activator: com.cisco.ruan.server.Activator
Bundle-Description: osgi-server OSGi bundle project.
Bundle-ManifestVersion: 2
Bundle-Name: osgi-server Bundle
Bundle-SymbolicName: osgi-server
Bundle-Version: 1.0
Created-By: Apache Maven Bundle Plugin
Export-Package: com.cisco.ruan.server;version="1.0";uses:="org.osgi.fram
 ework"
Import-Package: org.osgi.framework;version="[1.7,2)"
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.5))"
Tool: Bnd-3.0.0.201509101326

The MANIFEST of client bundle is:

Manifest-Version: 1.0
Bnd-LastModified: 1452582396099
Build-Jdk: 1.7.0_45
Built-By: haoruan
Bundle-Activator: com.cisco.ruan.client.Activator
Bundle-Description: osgi-client OSGi bundle project.
Bundle-ManifestVersion: 2
Bundle-Name: osgi-client Bundle
Bundle-SymbolicName: osgi-client
Bundle-Version: 1.0
Created-By: Apache Maven Bundle Plugin
Export-Package: com.cisco.ruan.client;version="1.0";uses:="com.cisco.rua
 n.server,org.osgi.framework"
Import-Package: com.cisco.ruan.server;version="[1.0,2)",org.osgi.framewo
 rk;version="[1.7,2)",org.slf4j;version="[1.7,2)"
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.5))"
Tool: Bnd-3.0.0.201509101326

======================================================

Hi Neil, this is the experiment I just did:

I have ClassA and ClassB, and class Wrapper refers to these 2 classes.

public class Wrapper {

    public Wrapper() {
        showInfo();
    }

    public void showInfo() {
        System.out.println("[Wrapper.java:5] " + ClassA.class.getClassLoader());
        System.out.println("[Wrapper.java:8] " + ClassB.class.getClassLoader());
    }
}

And I wrote my own customized classloader MyClassLoader:

class MyClassLoader extends ClassLoader {
    private ClassLoader haocl;
    private ClassLoader ruancl;

    public MyClassLoader() {
        this.haocl = new HaoClassLoader();
        this.ruancl = new RuanClassLoader();
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {

        if (name.endsWith("com.cisco.ruan.classloader.ClassA")) {
            return haocl.loadClass(name);
        }

        if (name.endsWith("com.cisco.ruan.classloader.ClassB")) {
            return ruancl.loadClass(name);
        }

        if (name.endsWith("Wrapper")) {
            InputStream is = null;
            try {
                is = new FileInputStream("/Users/haoruan/Desktop/Projects/cl-test/target/classes/com/cisco/ruan/classloader/Wrapper.class");
            } catch (Exception e) {
                e.printStackTrace();
            }
            byte[] bytes = null;
            try {
                bytes = ByteStreams.toByteArray(is);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return defineClass(name, bytes, 0, bytes.length);
        }

        return super.loadClass(name);

    }
}

Then I called Class.forName("com.cisco.ruan.classloader.Wrapper", true, mcl).newInstance();, and the console outputs:

[Wrapper.java:5] com.cisco.ruan.classloader.HaoClassLoader@248523a0 [Wrapper.java:8] com.cisco.ruan.classloader.RuanClassLoader@3c635421

So, it can be inferred that ClassA and ClassB is at first loaded by MyClassLoader and then actually loaded by HaoClassLoader and RuanClassLoader. And I think this experiment can be seem as a very simple implementation of OSGi bundle classloader mechanism? Right?

ruanhao
  • 4,663
  • 6
  • 28
  • 43
  • Can you show as you .bnd file or manifest or pom? Maybe you have set wrong bundle activator class. – amkz Jan 12 '16 at 07:21

2 Answers2

12

This is completely normal in OSGi. In OSGi there is one classloader per bundle. This classloader serves all classes that are located in the bundle. For all classes outside the bundle there are Import-Package definitions. At runtime each package import is wired to a bundle that exports the package. When a class from such a package is loaded the loading is delegated to the other bundles classloader.

Lets go through your scenario.

Bundle osgi-server contains the class com.cisco.ruan.server.HelloService it also export the package com.cisco.ruan.server. Bundle osgi-client imports the package com.cisco.ruan.server. When you load the HelloService class in the Activator of osgi-client the classloader of osgi-client is asked to load the class. It finds a delegation for the package and delegates loading to the classloader of osgi-server. This classloader is then user to load the class.

This is the default behaviour in OSGi and if you think it through it makes a lot of sense.

Christian Schneider
  • 19,420
  • 2
  • 39
  • 64
2

You said: "In my knowledge, when class B is referenced in class A, the classloader of class B is the same as class A's classloader. But in OSGi, it seems not this way."

This is not a true statement about Java classloaders... whether or not you are using OSGi.

For example, every class you write extends from java.lang.Object. Your class is loaded by the application classloader, but java.lang.Object is loaded by the boot classloader. This works because of delegation: one classloader can ask another classloader to load a class on its behalf.

In OSGi it's exactly the same thing. Each bundle has a classloader, and when you import a package from another bundle, that other bundle's classloader is used to load them.

Neil Bartlett
  • 23,743
  • 4
  • 44
  • 77
  • I don't think you are right about "every class you write extends from java.lang.Object. Your class is loaded by the application classloader". It is because "My class" is still in the class path and System ClassLoader can find it, therefore the classloader turns out to be app classloader. If "my class" is not under the class path, and only my customized classloader can find it, then the classloader of "my class" should not be app classloader. – ruanhao Jan 13 '16 at 07:06
  • Hey I've been coding Java for 18 years and have written a book on OSGi; I'm right about this. In the case you have a custom class loader, then that class loader still has a parent and it still delegates to the bootstrap class loader. – Neil Bartlett Jan 13 '16 at 07:31
  • Hi, Bartlett, no offense. I don't know if I expressed right. But I just wrote some code about this question, and I found that the classloader can be 'inherited'. Let me post the code, and see if there is something wrong :) – ruanhao Jan 13 '16 at 08:44
  • Okay, it's called "parent delegation" rather than inheritance, but if that helps you to understand it better then that's fine. – Neil Bartlett Jan 13 '16 at 13:57