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.