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.