Java classloader usage in OSGi

那年仲夏 提交于 2019-12-14 03:54:44

问题


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?


回答1:


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.




回答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.



来源:https://stackoverflow.com/questions/34737624/java-classloader-usage-in-osgi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!