Open kovacsgabor opened 9 years ago
Proposed solution: (1)
Introduce a generic wrapper type to the language that could be used to monitor the life cycle of the contained model object.
Proposed solution: (2)
Use proxies instead of the model objects to monitor their life cycle. Action.create
could return these proxy instances.
This functionality could be switched on and off with a global setting (in ModelExecutor.Settings
) to improve runtime performance when checks are not needed.
As agreed I will check the technology available for creating type-safe dynamic proxies.
Updates: cglib seems promising. The following code subclasses the given class and overrides its default behavior (for any method). In the combination with a deleted flag, could be used to check if an object is deleted.
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InvocationHandler;
class SampleClass {
public String test(String input) {
return "Hello world!";
}
}
public class Test {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getName().equals("test")) {
return "Hello cglib!";
} else {
throw new RuntimeException("Do not know what to do.");
}
}
});
SampleClass proxy = (SampleClass) enhancer.create();
System.out.println(proxy.test(null));
}
}
Long-term performance is within 110% of the uninstrumented version, so enabling checks will not result in a substantial slowdown.
A little example:
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
class BaseClass {
private boolean deleted = false;
public void delete() {
deleted = true;
}
public boolean isDeleted() {
return deleted;
}
}
class SampleClass extends BaseClass {
public int test() {
return 0;
}
}
public class Test {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SampleClass.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
if (!method.getName().equals("isDeleted")
&& ((BaseClass) obj).isDeleted()) {
throw new RuntimeException(
"calling method on a deleted object");
}
return proxy.invokeSuper(obj, args);
}
});
SampleClass proxy = (SampleClass) enhancer.create();
System.out.println(proxy.test());
proxy.delete();
System.out.println(proxy.test());
}
}
@kovacsgabor, is this the kind of thing you imagined?
Yes, it is, thanks.
@devaigergely81, you worried about performance and user-friendliness here. Do you think this could work?
Creation using the proxy could be done inside Action.create
, for example.
There are some inconveniences, for example, methods declared as final could not be overridden. That means that getClass()
, notify()
, notifyAll()
, and wait()
will work as it would in the original class. getClass()
returns something like class SampleClass$$EnhancerByCGLIB$$e04b1a01
that is the direct subclass of SampleClass
Can it use different constructors?
Sure it can:
SampleClass proxy = (SampleClass) enhancer.create(
new Class<?>[] { Integer.TYPE }, new Object[] { 3 });
will call
public SampleClass(int i) {
this.i = i;
}
Another option: we may use Java Instrumentation along with a bytecode manipulation library, like Javassist. I will create some experiments basend on these tools and link them here.
I pass back the ownership to @kovacsgabor, for he is in charge of API changes.
We should somehow check whether any deleted model object is accessed, as in Java, the users will get no error or warning messages if they do so.