Search code examples
javaconcurrencyenumsword-wrap

How to wrap more than 1 method in Java? Enums aren't the right way?


I have 1 Interface called A that needs to be implemented by a class I load dynamically after the program start. Lets call it B.

This interface provides x (more than 1) Methods. Lets call em from a() to z(). Now I have to wrap this class for some time measuring and control issues and run it in its own thread to be able to kill it if it takes too long.

Therefore I have invented class C that wraps B because Bdoesn't implement runnable on its own.

The next part ist the Class the original Program should call. New Class D. D implements interface A as well to hide the whole controlling part from the model.

Now I have to wrap the methods of the interface in D and send them over and Callable to C who unwraps them and executes them on Object B.

Here some example code who I imagined it can be:

public class D implements A {

private C ai;

public D(String aiName) {
    ai = new C("trivialKi");
}

private void call(parameters, ORIGIN_METHOD origin) {
    AiTaskExecutor task = new AiTaskExecutor(parameters, origin, ai);
    FutureTask<Long> tsk = new FutureTask<Long>(task);

    Thread thread = new Thread(tsk);
    thread.start();
    if (abort) {
            tsk.cancel(true);
    }
}

@Override
public void a(g g, f f, t t) {
    call(g, f, t, ORIGIN_METHOD.a);
}

@Override
public void b(g g, t t, h h) {
    call(g, t, h, ORIGIN_METHOD.b);
}

@Override
public void c(g g, t t, f f) {
    call(g, t, f, ORIGIN_METHOD.c);
}
}

And in class C the obvious switch case with that enum to pass the parameters to the right method on the class B that is held in class C als private field.

Dou you have a better solution in mind? I personally don't like the enum thing and if the parameters are too different this does not work very well.

Is there a "standard" solution for things like that?


Solution

  • The standard solution for this is: Use a "Dynamic Proxy" (java.lang.reflect.Proxy) for A. This saves you almost all your boilerplate code.

    This site and Google contains enough usage examples for Proxy.

    Also: I propose not to use a new thread for each call - this is extremely expensive if the called methods are short. You can use the Callable interface instead Runnable and a threadpool Executor instead. This also allows you to have return values in your interface :-)

    EDIT

    Just for fun I coded the dynamic proxy and the executor thing.

    Given the following interface A and a sample implementation B:

    interface A {
        int a(int g, int f, int h);
        int b(int x);
    }
    
    class B implements A {
        @Override
        public int a(int g, int f, int t) {
            System.out.println("called a in thread "+Thread.currentThread().getName());
            return 42;
        }
        @Override
        public int  b(int x) {
            System.out.println("called b in thread "+Thread.currentThread().getName());
            return 21;
        }
    }
    

    A proper Callable using reflection to call any java.lang.reflect.Method looks like this:

    class ReflectiveMethodCallable implements Callable<Object> {
        private Object target;
        private Method method;
        private Object[] args;
    
        public ReflectiveMethodCallable(Object target, Method method, Object[] args) {
            this.target = target;
            this.method = method;
            this.args = args;
        }
    
        @Override
        public Object call() throws Exception {
            return method.invoke(target, args);
        }
    }
    

    The part where such a ReflectiveMethodCallable is created and given to an ExecutorService is the InvocationHandler of java.lang.reflect.Proxy:

    class MyInvocationHandler implements InvocationHandler {
        private Object target;
        private ExecutorService executorService;
    
        public MyInvocationHandler(Object target, ExecutorService executorService) {
            this.target = target;
            this.executorService = executorService;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                Callable<Object> task = new ReflectiveMethodCallable(target, method, args);
                Future<Object> future = executorService.submit(task);
                return future.get();
            }
            catch(ExecutionException e1){
                try {
                    throw e1.getCause();
                } catch(InvocationTargetException e2){
                    throw e2.getCause();
                }
            }
        }
    }
    

    The InvocationHandler is used when creating a new Proxy in createProxyFor. The rest of the Main class is used for a SSCCE example:

    public class Main {
        public static void main(String[] args) throws InterruptedException {
            ExecutorService executorService = Executors.newCachedThreadPool();
    
            // get B somehow
            A a = new B();
    
            // get proxy for B 
            A proxy = createProxyFor(a, executorService);
    
            // call proxy
            System.out.println("current thread: "+Thread.currentThread().getName());
    
            int resultA = proxy.a(1,2,3);
            System.out.println("calling a returned "+resultA);
    
            int resultB = proxy.b(1);
            System.out.println("calling b returned "+resultB);
    
        }
    
        static A createProxyFor(A a, ExecutorService executorService){
            InvocationHandler h = new MyInvocationHandler(a, executorService);
            A proxy = (A)Proxy.newProxyInstance(A.class.getClassLoader(), new Class[]{A.class}, h);
            return proxy;
        }
    }
    

    The output:

    current thread: main
    called a in thread pool-1-thread-1
    calling a returned 42
    called b in thread pool-1-thread-1
    calling b returned 21
    

    To finish up:

    • Each method in A will be called in another thread.
    • Threads are reused by a thread pool.
    • Additional logic (e.g. time measurement, timeout control) can be done either in invoke or in call.
    • No need to code anything for each method.