根据更具体或现实世界的示例,您可能会更多地使用第三方或开源 API 遇到此类反射用法。一个非常流行的例子是我的世界,特别是 Bukkit/Spigot。
此 api 用于编写插件,然后主服务器加载并运行这些插件。这意味着您不能 100% 控制该代码库中存在的某些代码,从而邀请使用反射的解决方案。具体来说,当您想要拦截在 API(甚至是其他插件的 API,例如熟悉的 Vault)中进行的调用时,您可能希望使用Proxy
.
我们将继续使用 minecraft 示例,但我们在这里与 bukkit 的 api 分开(并假装它不接受 PR)。假设 API 的一部分不能完全按照您需要的方式工作。
public interface Player {
//This method handles all damage! Hooray!
public void damagePlayer(Player source, double damage);
}
这很好,但是如果我们想编写一些代码来发现播放器是否损坏(也许是为了制作很酷的效果?),我们需要修改源代码(对于分布式插件来说不可能),或者我们会需要找到一种方法来确定何时#damagePlayer
被调用以及使用什么值。所以进来了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?
}
}
}
}
使用我们的代理,我们有效地创建了一个虚假的Player 类,它会简单地调用Player
. 如果我们PlayerProxy
用 调用,那么它会很高兴地通过反射myPlayerProxy.someOtherMethod(...)
传递一个调用(在上面的方法中)。myPlayerProxy.src.someOtherMethod(...)
m#invoke
简而言之,您可以对库中的对象进行热土豆以满足您的需求:
//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 也可以处理多个接口。通过使用泛型Object
传递调用,您可以在同一个Proxy
实例中侦听 API 中的各种不同方法。