JAVA:InvocationHandler比Interface的实现有什么优势?

问题描述 投票:4回答:1

今天在课堂上,我们讲的是Java编程中的反射。今天的课程中有一部分是关于使用 InvocationHandler而不是仅仅实现一个接口.当我问老师使用调用处理程序有什么好处时,并没有一个明确的答案.那么我们就说我们有一个接口。插件

public interface Plugin {
    void calculate(double a, double b);
    String getCommand();
}

你可以很容易地在一个类中实现这个接口 乘法

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;
     }
 }

先谢谢你:)

java interface mocking implementation invocationhandler
1个回答
13
投票

代理 是一个 动态 代理,允许你改变对象的行为在 运行时 而不是非要在 编译时.

例如,假设我们想在晚上只返回nulls。如果你要静态地实现它,你需要把逻辑写进所有的类中,比如说

if(isNight())
    return null;
return normalValue;

这就要求你能真正改变类,你需要改变 的班级。

然而随着 Proxy,你可以把上面的逻辑写进 InvocationHandler 而普通类甚至不会知道它们的值在晚上没有被使用。你的代码现在使用的是动态代理,而不是原来的类,但它不会知道其中的区别。

这也允许你拥有多个 InvocationHandlers所以你可以用参数来运行你的代码,以决定是否要记录调用,是否要出于安全原因防止调用,或者其他类似的事情,这在静态实现中是不可能做到的。


不过你不太可能直接使用这些类,因为它们的级别很低。不过 AOP 使用动态代理或字节码操作来实现其任务。如果你曾经使用过Spring,你很有可能使用过一个叫做 InvocationHandler 而不自知。当你把 @Transactional 在方法上,一个 InvocationHandler 是将拦截方法调用,并为你开始(和结束)交易。


5
投票

InvocationHandlerProxy 允许在运行时实现一个接口,而不需要繁琐的编译接口专用代码。它通常用于调解对实现同一接口的类的对象的访问。Proxy 允许改变现有对象或类的行为。

例如,它可以用于客户端的远程方法调用,跨网络转发方法调用到服务器。

我第一次使用 Proxy 是用于记录方法调用到一个广泛的接口上,这个接口代表通过线格式接收的命令。这很容易产生 非常一致 调试输出,但当接口发生变化时几乎不需要维护。

Java注释接口可以用一个 Proxy 在运行时代理对象,以防止类的爆炸。

java.beans.EventHandler 在lambdas和方法引用出现之前,反思是很有用的,可以实现事件监听器而不至于让jar膨胀。


3
投票

按照一个更具体的或现实世界的例子,你可能会在使用第三方或开源API时更多地遇到这些反射的用法。一个非常流行的例子是minecraft,特别是BukkitSpigot。

这个api用来编写插件,然后主服务器加载并运行。这意味着你并不能100%控制该代码库中存在的一些代码,请使用反射来解决。具体来说,当你想 拦截 API中的调用(甚至是另一个插件的API,比如熟悉的Vault),你可以使用一个 "调用 "来解决。Proxy.

我们还是以 minecraft 为例,但我们在这里要从 bukkit 的 api 中分离出来(并假装它不接受 PR)。假设API的某一部分不接受PR。颇为 工作的方式。

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. 如果我们的 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 传递调用,然后你就可以在同一个API中监听各种不同的方法。Proxy 例如:

© www.soinside.com 2019 - 2024. All rights reserved.