In class today, we were talking about reflection in Java programming. A part of the lesson today was about using InvocationHandlers in Java, rather than just implementing an interface. When I asked the teacher what the advantages were of using an invocation handler, there wasn't a clear answer. So let's say we have an interface Plugin
public interface Plugin {
void calculate(double a, double b);
String getCommand();
}
you can easily implement this interface in a class Multiply
public class Multiply implements Plugin {
@Override
public void calculate(double a, double b){
return a * b;
}
@Override
public String getCommand(){
return "*";
}
}
Then why would I prefer another implementation using an InvocationHandler?
public class MyMock {
public static Object createMock(Class myClass) {
InvocationHandler handler = new MyInvocationHandler();
Object result = Proxy.newProxyInstance(myClass.getClassLoader(), new Class[]{myClass}, handler);
return result;
}
}
Thanks in advance :)
Proxy is a dynamic proxy, allowing you to alter the behaviour of objects at runtime instead of having to decide it at compile-time.
For example, let's say we want to return only nulls during the night. If you were to implement it statically, you would need to write the logic into all the classes with something like
if(isNight())
return null;
return normalValue;
This requires that you can actually change the class, and you would need to change all the classes.
However with a Proxy
, you can write the above logic into the InvocationHandler
and the normal classes won't even know that their values aren't used during the night. Instead of the original class, your code is now using the dynamic proxy, but it won't know the difference.
This also allows you to have multiple InvocationHandlers
, so you could run your code with parameters to decide if you want to log calls, prevent calls for security reasons, or any other such thing, which would be quite impossible to do with static implementations.
You're unlikely to use those classes directly though, as they're quite low level. However AOP uses either dynamic proxies or bytecode manipulation to achieve its task. If you've ever used Spring, you've most likely used an InvocationHandler
without knowing it. When you put @Transactional
on a method, an InvocationHandler
is what will intercept the method call and start (and end) the transaction for you.
As per a more specific or real-world example, you may run into these kind of reflection usages more using a third-party or open-source API. A very popular example of this would be minecraft, specifically Bukkit/Spigot.
This api is used to write plugins, which the main server then loads and runs. This means you're not 100% in control of some of the code that exists in that codebase, inviting solutions using reflection. Specifically, when you want to intercept calls being made in the API (or even another plugin's API, e.g. Vault for those familiar), you may look to use a Proxy
.
We'll stick with the minecraft example, but we're parting from bukkit's api here (and pretending it's not accepting PRs). Say there's a part of the API that just doesn't quite work the way you need.
public interface Player {
//This method handles all damage! Hooray!
public void damagePlayer(Player source, double damage);
}
This is great, but if we want to code something where we could find out if a player was damaged (maybe to make cool effects?), we'd need to modify the source (not possible for distributed plugins), or we'd need to find a way to figure out when #damagePlayer
has been called and with what values. So in comes a Proxy
:
public class PlayerProxy implements IvocationHandler {
private final Player src;
public PlayerProxy(Player src) {
this.src = src;
}
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
//Proceed to call the original Player object to adhere to the API
Object back = m.invoke(this.src, args);
if (m.getName().equals("damagePlayer") && args.length == 2) {
//Add our own effects!
//Alternatively, add a hook so you can register multiple things here, and avoid coding directly inside a Proxy
if (/* 50% random chance */) {
//double damage!
args[1] = (double) args[1] * 2;
//or perhaps use `source`/args[0] to add to a damage count?
}
}
}
}
With our Proxy, we've effectively created a fake Player class, one which will simply call the methods in place for Player
. If our PlayerProxy
is invoked with myPlayerProxy.someOtherMethod(...)
, then it will happily pass along a call to myPlayerProxy.src.someOtherMethod(...)
via reflection (the m#invoke
in the method above).
Simply put, you hot-potato the objects in the library to suit your needs:
//we'll use this to demonstrate "replacing" the player variables inside of the server
Map<String, Player> players = /* a reflected instance of the server's Player objects, mapped by name. Convenient! */;
players.replaceAll((name, player) ->
(PlayerProxy) Proxy.newProxyInstance(/* class loader */, new Class<?>[]{Player.class}, new PlayerProxy(player)));
InvocationHandler can handle multiple interfaces too. By using a generic Object
to pass along the invocations, you can then listen to a variety of different methods in the API all within the same Proxy
instance.
InvocationHandler
together with Proxy
allow implementation of an interface at runtime, without the faff of compiling interface-specfic code. It is often used to mediate access to an object of a class that implements the same interface. Proxy
does not allow changing the behaviour of existing objects or classes.
For instance, it can be used in remote method calling on the client side, forwarding method call across a network to a server.
My first use of Proxy
was for logging method calls to a wide interface that represented command received over a wire format. This easily produced very consistent debug output, but required little maintenance when the interface changed.
Java annotation interfaces may be represented by a Proxy
proxy object at runtime, to prevent an explosion of classes.
java.beans.EventHandler
was useful before lambdas and method references came along, to implement event listeners without bloating jars.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With