We had uses cases, especially with OSGI where we had to use TCCL in deserialization of Object InputStream.
Sample method
public static Object getObject(byte[] bArr){
try{
ByteArrayInputStream bais = new ByteArrayInputStream(bArr);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
}catch(Exception err){
err.printStackTrace();
}
return null;
}
return ois.readObject() - fails because ObjectInputStream doesnt take into consideration TCCL when deserializing.
In OSGi we need to deserialize this using TCCL because objects being deserialized are in another bundle and framework bundle canot see these classes.
Java has provided a way to solve this . We need to extend ObjectInputStream and overide the
public Class resolveClass(ObjectStreamClass desc) method.
[Since resolveClass class is protected method in ObjectInputStream ]
Code
public class MyObjectInputStream extends ObjectInputStream {
@Override
public Class resolveClass(ObjectStreamClass desc) throws IOException,
ClassNotFoundException {
ClassLoader currentTccl = null;
try {
currentTccl = Thread.currentThread().getContextClassLoader();
return currentTccl.loadClass(desc.getName());
} catch (Exception e) {
}
}
return super.resolveClass(desc);
}
public MyObjectInputStream(InputStream in) throws IOException {
super(in);
}
}
Now we can use MyObjectInputStream which ensures that TCCL will have visibility to deserialize the class.
MyObjectInputStream ois = new MyObjectInputStream(bais);
return ois.readObject();
Sunday, May 9, 2010
Thread Context Classloader in Deserialization - ObjectInputStream
Tuesday, November 17, 2009
TCCL in OSGi(Spring DM)
In order to better understand how TCCL works in OSGi i have done a sample POC.
I created 3 bundles.
Bundle A - calls Bundle B (imports Bundle B)
Bundle B -calls Bundle C (imports Bundle A)
Bundle C - instantiates class in Bundle A (No imports)
In normal cases Bundle C will fail to instantiate class in Bundle A, but this can be achieved using TCCL as follows.
ITest test=(ITest)Thread.currentThread().getContextClassLoader().loadClass("com.bundleA.external.ActionClass").newInstance();
Conclusion: Spring Dm sets TCCL to the bundle classloader where the thread initiated.
Now what if we want to instantiate class of bundle B in Bundle C , but the thread flow doesnt change. ?
This can be easily done by resetting the TCCL in bundle B's method as follows
Thread.currentThread().setContextClassLoader(Bundle2.class.getClassLoader());
Saturday, August 8, 2009
Thread Context ClassLoader / Buddy ClassLoading in OSGi.
Consider a case where there are more than one class loaders .Lets take a simple case in which the struts/spring jar is kept under tomcat common lib and your application classes are under the web-inf lib. These two are jars now are loaded by two different classloaders.
Tomcat common lib class loader being the parent of tomcat web-inf/lib class loader.Also classloader of struts jar cannot see application classes in web-inf lib.
Now how can struts/spring see application classes ?
To solve such problems java Java came up with a solution called Thread Context Class Loader(TCCL). Thread context class loader was introduced in Java 2 to enable frameworks running in application servers to access the "right" application class loader for loading application classes.
This is accessed using Thread.getCurentThread.getContextClassloader().Its as simple as, It is the classloader associated with each thread.There are setter methods which can be called to set this TCCL.
By the definition in tomcat the TCCL is set to the context class loader from where the thread begins.So TCCL has the classloader visibity of web-inf lib,solving our problem.
Now since the thread started from that application ,these third party jars can use TCCL to instantiate application classes. The control starts from our application bundle and then flow moves to some third party bundle like struts or hibernate,which internally in their
source code use TCCL to instantiate our classes.
TCCL becomes slightly more complicated in the case of OSGI.
I have a small POC for the Reflection based Class loading using TCCL.
Source Code : http://www.4shared.com/file/123739699/3b6dd30e/TCCLosgi.html
Bundle 3(Core bundle) – has reflection code to instantiate application classes. Uses TCCL to instantiate classes.
Bundle 2 (Service) –Using the core bundle to instantiate its classes (imports bundle 3 classes directly)
Bundle 1 (Application) – uses the OSGi service provided by bundle 2.
Control Flow:
Bundle 1 makes a OSGi service call to bundle 2.
Bundle 2 now uses core to instantiate its(bundle 2) classes.
Result: ClassNotFoundException
Reason:
Bundle 2 is able to instantiate classes only in Bundle 3 and not in Bundle.
This is because TCCL has visibility only to class loader from where the thread started.
Solution:
1. PAR(Spring DM solution)
Spring Dm comes with a concept of PAR,which is grouping of bundles.All bundles inside the PAR will contribute together to form a synthetic context.And hence bundles inside it have visibility to all others exisiting inside it.
http://blog.springsource.com/2008/05/02/running-spring-applications-on-osgi-with-the-springsource-application-platform/
2.Buddy Class Loading (this is equinox specific solution and not part of OSGi spec)
http://wiki.eclipse.org/index.php/Context_Class_Loader_Enhancements#Buddy_Class_Loading
Now bundle 3, that is the core bundle can be a buddy to bundle 2 (service bundle).
Syntax
In bundle 3 manifest we need to mention Buddy-Policy :registered
In bundle 2 manifest we mention Eclipse-RegisterBuddy : bundle symbolic name
Now at run time we can add new slots(Bundle 4) which registers itself as a buddy to bundle 2(core) ,without affecting any other bundles.
3. We can set the TCCL in bundle 2 to bundle class loader when the deligation goes through it.I guess TCCL may not be the right approach(not good to tamper the class loader prior to making calls across bundles).
We can choose a solution based on the requirement.
.jpg)