|
Tutorial
The ProblemSuppose 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:
The interaction between two Objects in the scenario above faces two potential problems:
The SolutionTransloader 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. CloningIf 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
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 InvocationIf 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. |
Sign in to add a comment