|
From: Chris N. <ch...@si...> - 2003-11-28 21:05:44
|
Rod Johnson wrote:
> Unfortunately I think there's a fatal flaw in my solution using CGLIB 1.0.
> There are now two objects, even if the enhanced one starts off with the
> same state. "Optimized" invocations go to the original target, advised
> ones to the enhanced class and never to the original object. This is fine
> if there's no conversational state but the results can, ahem, be rather
> interesting if there is.
I can imagine.
> I can't see a way round this with CGLIB 1.0.
>
> It seems that CGLIB 2.0 solves this problem, if the LazyLoaderCallback
> works as I understand. If it does solve this problem, could you please
> send me the sample code?
There are two ways to go about this. One is to use the proxy object as a
"shell" which dispatches all of its methods, one way or another, to the
original instance. The proxy, since it extends the original class, will
have a bunch of uninitialized fields, but they will go unused.
Here is some code using CGLIB 2 that takes a bean and creates a proxy that
will delegate to the bean. Some methods use the LazyLoader callback, and
others use a MethodInterceptor. You will have to plug in the algorithm to
choose which are which. FYI this is a bit different from normal use of
LazyLoader because you don't really care about the laziness, but there is
no "LoadRightNow" callback and it would end up working the same anyway.
public Object createProxy(final Object bean) {
Enhancer e = new Enhancer();
e.setSuperclass(bean.getClass());
e.setCallbackFilter(new CallbackFilter() {
public int accept(Method method) {
// Return the index into the callback array. We will put
// the MethodInterceptor into index 0, and the LazyLoader
// into index 1.
return methodShouldBeAdvised(method) ? 0 : 1;
}
});
e.setCallbacks(new Callback[]{
// index 0: MethodInterceptor
new MethodInterceptor() {
public Object intercept(Object obj,
Method method,
Object[] args,
MethodProxy proxy) throws Throwable {
// Use either of these methods to direct method to the
// original bean. We are ignoring the "obj" argument--it is
// the proxy instance. Somehow your "advice" plugs in here.
// method A: reflection (slow)
// return method.invoke(bean, args);
// method B: generated MethodProxy (faster)
return proxy.invoke(bean, args);
}
},
// index 1: LazyLoader
new LazyLoader() {
public Object loadObject() {
return bean;
}
}
});
return e.create();
}
The *other* possibility is to use something similar to your current
solution, which copies the fields from the original instance. As you point
out, having the two objects in play causes problems, so you really need to
throw out the original instance if you want this to work. This means that
all intercepted methods cannot be redirected to the original instance, but
instead must eventually call the "super" method of the proxy itself. There
is no way to do this using the java.lang.reflect.Method object passed to
the interceptor, since (understandably) invoking it on the proxy will
result in an infinite loop.
The solution is to use the MethodProxy invokeSuper method. This calls a
special synthetic method in the generated class which then calls the proper
method in the base class.
Here is the same code but using invokeSuper within the interceptor, and
using the NoOp callback for non-intercepted methods:
public Object createProxy(Object bean) {
Enhancer e = new Enhancer();
e.setSuperclass(bean.getClass());
e.setCallbackFilter(new CallbackFilter() {
public int accept(Method method) {
return methodShouldBeAdvised(method) ? 0 : 1;
}
});
e.setCallbacks(new Callback[]{
// index 0: MethodInterceptor
new MethodInterceptor() {
public Object intercept(Object obj,
Method method,
Object[] args,
MethodProxy proxy) throws Throwable {
// Invoke method in superclass (avoiding interception).
// Of course you still need to plug in your advice code
// here somehow.
return proxy.invokeSuper(obj, args);
}
},
// index 1: NoOp
NoOp.INSTANCE
});
Object proxy = e.create();
copyFieldsFromBeanToProxy(bean, proxy);
return proxy;
}
If you can get the field copying to work and plug invokeSuper into your
system this option is preferrable, since you only have one object and there
is *zero* overhead for non-intercepted methods.
> Also, since that would be a killer reason to go to CGLIB 2.0, I guess it
> brings the whole version thing up again. CGLIB 2.0 isn't backward
> compatible, is it, so it will break old versions of Hibernate?
You cannot drop in 2.0 for 1.0. However, they can be used simultaneously
except that 1.0 uses ASM 1.3 *or* BCEL, and 2.0 uses *only* ASM 1.4. If you
use CGLIB 1.0 w/o the ASM classes, and make sure that bcel.jar is in your
classpath, you can use CGLIB 1.0 and 2.0 at the same time. Otherwise you
will have to choose to support either Hibernate 2.0 or 2.1, and choose your
CGLIB version accordingly (I would strongly recommend Hibernate 2.1 if you
take this route).
Chris
|