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?
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.