Copy java Object from diffrent classloader

Tutorial

Java环境中,由不同ClassLoader装载的类是不能直接赋值的,So... 

Featured Updated Jul 16, 2007 by jeremywa...@gmail.com The Problem Suppose you have one object, built using Classes loaded through one ClassLoader, and another object, built using Classes loaded through a ClassLoader that is independent of the first ClassLoader, and you want the two objects to interact:

This scenario does not occur very often in normal Java™ development. However, it does occur whenever a container you are using implements ClassLoader isolation and yet that isolation needs to be crossed for whatever reason. For example, with OSGi, which provides ClassLoader isolation for each Bundle (a module in the OSGi Framework), valid reasons for crossing the ClassLoader divide include:

You have an object from a Bundle in the OSGi Framework that you want to use outside the OSGi Framework. You need to work around the situation where a particular combination of Bundle constraints makes it impossible for the OSGi Framework to make the “Class space” consistent. The interaction between two Objects in the scenario above faces two potential problems:

ClassLoader1 doesn’t even have access to the same Class definitions as ClassLoader2 Effect: You cannot even compile the Class of ObjectA to reference the Class of ObjectB in the first place. Workaround: You could use Java™’s Reflection API to call methods on ObjectB but this can be difficult and is always ugly. ClassLoader1 has access to the same Class definitions as ClassLoader2 but loads them independently Effect: You can compile the Class of ObjectA to reference the Class of ObjectB but when you bring the objects into contact (e.g. by passing one into the other through a reflective method invocation, or pulling one out of a Collection accessible to the other and casting it) then a ClassCastException will be thrown. This is because although ObjectB appears to extend/implement a type known to ObjectA, it really extends/implements a different copy of that type loaded by a different ClassLoader, not the copy of the type known to ObjectA. Workaround: Again, don’t use ObjectB directly but either invoke its methods reflectively, with all the drawbacks that go with Java™’s Reflection API, or use Java™’s Serialization API to serialize ObjectB and deserialize it using ClassLoader1. The latter workaround has the advantage of making a copy of ObjectB that will work in Object#equals(Object) and so forth but is slow and only works for object graphs that are completely Serializable. The Solution Transloader aims to provide superior alternatives to the workarounds above.

The central interface is com.googlecode.transloader.Transloader and for convenience it provides static access to the default implementation via the Transloader.DEFAULT constant (but you can use whatever implementation you want, which you'll usually want to inject following IOC). The Transloader interface is used to wrap objects referencing Classes from potentially foreign ClassLoaders like so:

Object someObject = someService.getObjectFromAnotherClassLoader(); Transloader transloader = Transloader.DEFAULT; ObjectWrapper someObjectWrapped = transloader.wrap(someObject); ClassWrapper someClassWrapped = transloader.wrap(someObject.getClass()); The resulting wrappers can then be used to work with the wrapped object despite the wrapped object referencing different Classes to those loaded by the ClassLoader of the calling code.

The ClassWrapper is fairly self-explanatory; of more interest is the ObjectWrapper. ObjectWrapper basically provides the ability to clone the wrapped object in a way far superior to serialization or to invoke methods on the wrapped object without resorting directly to Java™’s Reflection API. It also provides the ability to find out about the wrapped object before doing either:

if (someObjectWrapped.isNull()) … will let you handle the null case while

if (someObjectWrapped.isInstanceOf(“com.somepackage.SomeType”)) … will let you handle particular types of object, analogous to

if (someObject instanceOf SomeType)) … except that the isInstanceOf method takes a String, not a Class, to indicate that it is only matching the wrapped object against a type name which could actually be loaded by any ClassLoader (whereas a Class refers to a specific ClassLoader) and in fact a definition of that type need not even be accessible to the ClassLoader of the calling object.

Cloning If you want a copy of the wrapped object that will behave exactly as if it were originally constructed using Classes from the current ClassLoader, the cloneWith method is your friend:

if (someObjectWrapped.isInstanceOf(“com.somepackage.SomeType”)) { SomeType someObject = (SomeType) someObjectWrapped.cloneWith(getClass().getClassLoader()); if (someOtherObject.equals(someObject) … } This is helpful when you are interested in the values of the object from a foreign ClassLoader, not in that precise Object itself. It is perfectly compatible, for example, with being passed into the Object#equals(Object) method because it is built using local Classes. Of course, this can only solve Problem 2 above, where the relevant Classes are accessible to both ClassLoaders, not Problem 1 where the current ClassLoader cannot even access definitions of the Classes in the foreign ClassLoader.

The algorithm used to create the clone is supplied by the CloningStrategy injected into the ObjectWrapper at construction, which in turn can be supplied by the particular implementation of Transloader that you choose to use. You can change the CloningStrategy by replacing the implementation of Transloader that you are using:

Transloader transloader = new DefaultTransloader(CloningStrategy.MINIMAL); Out of the box Transloader supplies two major implementations of CloningStrategy

One based on Java™ Serialization, which is comparatively slow and limited only to Serializables, but has the advantage that Java™ Serialization has at least been well understood for a long time. One based on Java™ Reflection, which is comparatively fast and much more flexible. The latter is divided into two flavours by different implementations of CloningDecisionStrategy. MinimalCloningDecisionStrategy actually only clones an object if it instantiates a Class that happens to be different when loaded through the ClassLoader supplied to cloneWith(ClassLoader); everything else is left untouched. MaximalCloningDecisionStrategy clones everything. MinimalCloningDecisionStrategy is usually the best choice because it is so fast and less likely to hit problems, because it is attempting to do the least. However, it can provide surprising results if you do not understand how it works:

In the scenario above, CloningStrategy.MINIMAL will determine that ObjectC extends a Class that is the same whether loaded through ClassLoader1 or ClassLoader2 and therefore does not clone it. In this scenario, CloningStrategy.MINIMAL is not too surprising as it’s just doing a shallow clone instead of the deep clone that SerializationCloningStrategy or CloningStrategy.MAXIMAL would do. However, imagine the scenario where ObjectC is the top level object that references ObjectA:

In this scenario, ObjectC is again not cloned and, again, ObjectA is. However, because ObjectC is now the top level object that is wrapped by ObjectWrapper, the cloneWith method now actually returns the wrapped object itself (because it wasn’t cloned) while the wrapped object itself has been altered to now refer to the clone of ObjectA, which is ObjectB, instead of referring to ObjectA. So rather than being a completely new, clean copy, CloningStrategy.MINIMAL will, in this scenario, alter the wrapped object just enough to make it compatible with Classes from the given ClassLoader. If this is not what you want, then use CloningStrategy.MAXIMAL, which happens to be the default in Transloader.DEFAULT.

Method Invocation If you need to invoke on the wrapped object a method that is not on an interface you have access to in the current ClassLoader, here’s what to do:

someObjectWrapped.invoke(new InvocationDescription(“getValue”)) There is only one invoke method but InvocationDescription has many convenient constructors for specifying invocations with zero, one or many parameters. These parameters are cloned on the way through to ensure no ClassCastExceptions as a result of the different ClassLoaders involved. The CloningStrategy employed will be the one injected into the ObjectWrapper at construction.

If you are fortunate enough that the method you want to invoke happens to exist on an interface accessible to your current ClassLoader, then you’ll prefer:

SomeInterface someObject = (SomeInterface) someObjectWrapped.makeCastableTo(SomeInterface.class); someObject.doSomethingWith(parameterObject); The makeCastableTo method just returns a proxy that implements the given interface (which would be loaded through the current ClassLoader) and delegates to the ObjectWrapper#invoke method under the covers.

In this way the object from the foreign ClassLoader can be employed to do work using all its current references to other objects, which is particularly handy if, for example, you want to invoke an OSGi Service from outside the OSGi Framework.

Leave Comment