How to make the JVM proxy the only one interface method?

517 Views Asked by At
public interface Action {
    void doSomething();
    void dontProxy();
}

For example with this interface, I just want the JVM to proxy the doSomething method.

class DynamicProxy implements InvocationHandler{
    private Action work;
    public DynamicProxy(Action action){
        this.work = action;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName()+" start");
        var tmp = method.invoke(work,args);
        System.out.println(method.getName()+" end");
        return tmp;
    }

Action action = (Action) Proxy.newProxyInstance(handler.getClass().getClassLoader(),work.getClass().getInterfaces(),handler);

The work is an instance of the Action interface implementation class. If I use Proxy.newProxyInstance, it seems to make all the interface methods be handled by the handler.

1

There are 1 best solutions below

0
raner On

Indeed, in a proxy invocation, all methods are handled by the specified InvocationHandler. As folks already pointed out in the comments, you would otherwise end up with unimplemented methods, and the typical implementation of an InvocationHandler usually involves some kind of logic to switch between different implementations for different methods. For methods that should retain the original behavior you would usually have some kind of pass-through handler that just calls the original method (but the methods would still be proxied).

Alternatively, there are several libraries available that make this sort of instrumentation easier. For example, with the Projo library you could use a slightly different approach (DISCLAIMER: I'm the author of that library):

import pro.projo.Projo;
import pro.projo.annotations.Delegate;
import pro.projo.singles.Factory;

public interface ProxiedAction extends Action {

    Factory<ProxiedAction, Action> factory =
        Projo.creates(ProxiedAction.class).with(ProxiedAction::original);

    @Delegate
    Action original();

    @Override
    default void doSomething() {
        System.out.println("doSomething() start");
        original().doSomething();
        System.out.println("doSomething() end");
    }
}

This is especially helpful if you have a large interface with dozens of methods, but you only wish to instrument just a few methods. To test this out, you could write something like this:

Action work = new Action() {

    @Override
    public void doSomething() {
        System.out.println("work.doSomething()");
    }

    @Override
    public void dontProxy() {
        System.out.println("work.dontProxy()");
    }
};
Action myAction = ProxiedAction.factory.create(work);
System.out.println("Calling myAction.doSomething()...");
myAction.doSomething();
System.out.println("Calling myAction.dontProxy()...");
myAction.dontProxy();
System.out.println("Done.");

The resulting output should be something like:

Calling myAction.doSomething()...
doSomething() start
work.doSomething()
doSomething() end
Calling myAction.dontProxy()...
work.dontProxy()
Done.

As you see, only doSomething() is instrumented with the start/end messages, whereas calling dontProxy() just prints the original message.

For this to work you just need to add these two additional dependencies:

   <dependency>
    <groupId>pro.projo</groupId>
    <artifactId>projo</artifactId>
    <version>1.3.1</version>
   </dependency>
   <dependency>
    <groupId>pro.projo</groupId>
    <artifactId>projo-runtime-code-generation</artifactId>
    <version>1.3.1</version>
    <scope>runtime</scope>
   </dependency>

Another advantage of this approach is that it actually does not use proxies (which are very inefficient and slow), but generates some wrapper code at runtime. If you leave out the second dependency (projo-runtime-code-generation) the code will revert to using proxies, which will be very close to what your original code is doing (but also not very efficient; you might also see some warnings about illegal accesses via reflection).

Under the hood, Projo uses Byte Buddy for the runtime code generation, and you can also hand-roll a simple solution for your case just using Byte Buddy directly.

Spring and some other dependency injection frameworks also support efficient code instrumentation using annotation-based approaches.