Koe*_*nVE 10 java implementation interface mocking invocationhandler
今天在课堂上,我们讨论了 Java 编程中的反射。今天课程的一部分是关于在 Java 中使用InvocationHandler,而不仅仅是实现一个接口。当我问老师使用调用处理程序有什么好处时,没有明确的答案。假设我们有一个接口插件
public interface Plugin {
    void calculate(double a, double b);
    String getCommand();
}
您可以在Multiply类中轻松实现此接口
public class Multiply implements Plugin {
    @Override
    public void calculate(double a, double b){
         return a * b;
    }
    @Override
    public String getCommand(){
         return "*";
    }
}
那么为什么我更喜欢使用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;
     }
 }
提前致谢 :)
Kay*_*man 17
代理是一个动态 代理,允许您在运行时更改对象的行为,而不必在编译时决定它。
例如,假设我们只想在夜间返回空值。如果要静态实现它,则需要将逻辑写入所有类,例如
if(isNight())
    return null;
return normalValue;
这要求您实际上可以更改类,并且您需要更改所有类。
但是使用 a Proxy,您可以将上述逻辑写入InvocationHandler而普通类甚至不会知道它们的值在夜间未使用。您的代码现在使用的是动态代理,而不是原始类,但它不知道有什么区别。
这也允许您拥有多个InvocationHandlers,因此您可以使用参数运行代码来决定是否要记录调用,出于安全原因阻止调用,或任何其他此类事情,这对于静态实现是完全不可能的。
不过,您不太可能直接使用这些类,因为它们的级别非常低。然而,AOP使用动态代理或字节码操作来完成它的任务。如果您曾经使用过 Spring,那么您很可能在InvocationHandler不知不觉中使用过它。当你@Transactional使用一个方法时,它InvocationHandler会拦截方法调用并为你开始(和结束)事务。
InvocationHandler连同Proxy允许在运行时实现接口,而无需编译特定于接口的代码。它通常用于调解对实现相同接口的类的对象的访问。Proxy 不容许改变现有的对象或类的行为。
例如,它可以用于客户端的远程方法调用,通过网络将方法调用转发到服务器。
我的第一个用途Proxy是将方法调用记录到表示通过有线格式接收的命令的宽接口。这很容易产生非常一致的调试输出,但在接口更改时几乎不需要维护。
Java 注释接口可以Proxy在运行时由代理对象表示,以防止类爆炸。
java.beans.EventHandler 在 lambdas 和方法引用出现之前,它很有用,可以实现事件侦听器而不会使 jars 膨胀。
根据更具体或现实世界的示例,您可能会更多地使用第三方或开源 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 中的各种不同方法。