eitama
eitama

Reputation: 1507

How do I intercept method calls in order to delay the execution, group all calls together and execute in java?

I've been attempting to resolve my problem for about a day now, but can't seem to get anywhere. The problem:

I have a java class, ExternalClass which has 30 methods in it. I also have an interface ExternalClassFacade.

public class ExternalClass {
  public method1() {...}
  public method2() {...}
  ...
  ...
  public metod30(...) {...}
}

This class is an external library and I cannot modify it's code. The class works well but I have a situation where I need to group up multiple calls on an undefined timespan to all 30 methods, delay the execution, and execute all at once (serial or parallel I don't care) at some moment.

For example, over 10 minutes, methods 1 to 30 will be called randomly 500 times, I want them to do nothing at the moment of being invoked, but after 10 minutes I want to invoke all 500 calls as they were originally called.

most of the methods require parameters which I need to remember for the moment in which i will call the methods.

I'm looking for a way to extend/wrap/composite this class, so that when someone calls any of these methods, or, a special method that will bridge the calls to the original methods so that they will be delayed till the right moment comes.

I was thinking about extending the class and overriding all methods, and managing 30 Struct-Like classes to hold the info about the calls, but that would require :

Lots of code, not very smart.

I'm looking for a better way to do this, I was thinking about catching the calls and keeping the pointer to the original method call, but this is java, so it's not possible.

Upvotes: 2

Views: 2251

Answers (3)

Tomasz Nurkiewicz
Tomasz Nurkiewicz

Reputation: 340733

Very interesting problem indeed. First question: does ExternalClass implement some interface? If it does, it simplifies stuff a lot, however if it doesn't, you can create one:

interface ExternalClassFacade {
    method1();
    method2();
            //...
    method30();
}

Don't worry, you don't have to implement it! Just copy all the method signatures from the ExternalClass. Do you know java.lang.Proxy? Wonderful tool in such problems like yours:

ExternalClass ext = //obtain target ExternalClass somehow
ExternalClassFacade extFacade = (ExternalClassFacade) Proxy.newProxyInstance(
    ExternalClass.class.getClassLoader(), 
    new Class<?>[]{ExternalClassFacade.class},
    new BatchInvocationHandler(ext));
extFacade.method1();

As you can see this magic and obscure code created something that implements ExternalClassFacade and allows you to run the same methods as ExternalClass. Here is the missing puzzle:

public class BatchInvocationHandler implements InvocationHandler {

    private final ExternalClass ext;

    public BatchInvocationHandler(ExternalClass ext) {
        this.ext = ext;
    }

    @Override
    public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
        return MethodUtils.invokeMethod(ext, method.getName(), args);
    }

}

This code itself is not doing anything useful - when you call a method on the ExternalClassFacade it forwards the call to the same named method on ExternalClass with the same arguments. So we haven't achieved anything yet. BTW I am using MethodUtils from Apache Commons Lang to simplify the reflection code a bit. Chances are you already have this library on the CLASSPATH, if not, it is just few lines of extra code.

Now look at this improved version:

private static class BatchInvocationHandler implements InvocationHandler {

    private final ExternalClass ext;

    private Queue<Callable<Object>> delayedInvocations = new ConcurrentLinkedQueue<Callable<Object>>();

    public BatchInvocationHandler(ExternalClass ext) {
        this.ext = ext;
    }

    @Override
    public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
        delayedInvocations.add(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                return MethodUtils.invokeMethod(ext, method.getName(), args);
            }
        });
        return null;
    }

}

Now we are getting somewhere: instead of calling the method we are wrapping the call inside Callable and adding it to a delayedInvocations queue. Of course since we are no longer calling the actual method, the return value is just a placeholder. If ExternalClass methods have return types different than void, you must be very careful.

I think you see the light now. Everything you need is to create a thread that will take all the Callables collected in the queue and run them in batch. You can do it in various ways, but the basic building blocks are there. Also you might choose data structure like map or set rather than a queue. I can for instance imagine grouping methods by name for some reason.


Of course if you can use AspectJ/Spring AOP you will avoid the whole proxy infrastructure code. But the basic idea will be the same only that the API will be more pleasent.

Upvotes: 9

Ryan Stewart
Ryan Stewart

Reputation: 128829

Using AspectJ, you could introduce an interface to the class, then code to the interface. After that, you're free to add whatever behavior behind the interface that you want. Alternately, just use AspectJ to weave in the collecting/executing behavior you're looking for.

Cglib or Javassist would also let you do it more cleanly by letting you basically proxy the class by dynamic subclassing (assuming it's not final).

There are plenty of options. Those are three third-party ones that occurred to me. An advantage of some of these approaches is that they'll give you some representation of a method invocation in object form, which you can easily collect and run at a later time.

Upvotes: 2

atrain
atrain

Reputation: 9255

You could use an aspect to intercept all calls to execute external lib methods and to pause the Thread, writing the Thread's ID to a synchronized Set. That Set is in a singleton being watched by another Thread.

When your business rule to execute is fired, have the singleton watcher iterate the Set and notify each thread to continue processing. The aspect will continue on and execute each originally requested external method.

Upvotes: 0

Related Questions