JAVA:与接口的实现相比,InvocationHandler 有哪些优势?
JAVA: What are the advantages of an InvocationHandler over an implementation of an Interface?
今天在 class 中,我们讨论了 Java 编程中的反射。今天课程的一部分是关于在 Java 中使用 InvocationHandler,而不仅仅是实现一个接口。
当我问老师使用调用处理程序有什么好处时,没有明确的答案。
假设我们有一个接口 Plugin
public interface Plugin {
void calculate(double a, double b);
String getCommand();
}
您可以在 class 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;
}
}
提前致谢:)
Proxy is a dynamic proxy,允许您在 运行 时间 更改对象的行为,而不必在 编译时决定它-时间.
例如,假设我们希望 return 仅在夜间为 null。如果要静态地实现它,则需要将逻辑写入所有 classes,例如
if(isNight())
return null;
return normalValue;
这需要您实际更改 class,并且您需要更改 所有 classes。
但是对于 Proxy
,你可以将上面的逻辑写入 InvocationHandler
并且正常的 classes 甚至不知道它们的值在 InvocationHandler
期间没有被使用夜晚。您的代码现在使用的是动态代理,而不是原来的 class,但它不知道其中的区别。
这还允许您拥有多个 InvocationHandlers
,这样您就可以 运行 您的带有参数的代码来决定是否要记录呼叫、出于安全原因阻止呼叫或任何其他类似的事情,这对于静态实现来说是完全不可能的。
你不太可能直接使用那些 classes,因为它们的级别很低。然而 AOP 使用动态代理或字节码操作来完成它的任务。如果您曾经使用过 Spring,您很可能在不知情的情况下使用过 InvocationHandler
。当您将 @Transactional
放在方法上时,InvocationHandler
将拦截方法调用并为您启动(和结束)事务。
InvocationHandler
与 Proxy
一起允许在运行时实现接口,而无需编译特定于接口的代码。它通常用于调解对实现相同接口的 class 对象的访问。 Proxy
不允许 更改现有对象或 classes 的行为。
比如可以用于客户端的远程方法调用,将方法调用通过网络转发到服务器。
我第一次使用 Proxy
是为了记录对宽接口的方法调用,该接口表示通过有线格式接收的命令。这很容易产生非常一致的调试输出,但在界面更改时几乎不需要维护。
Java 注释接口可以在运行时由 Proxy
代理对象表示,以防止 classes 的爆炸。
java.beans.EventHandler
在 lambda 和方法引用出现之前很有用,可以在不使用膨胀罐的情况下实现事件监听器。
根据更具体或更真实的示例,您可以 运行 使用第三方或开源 API 更多地了解这些类型的反射用法。一个非常流行的例子是我的世界,特别是 Bukkit/Spigot.
这个api是用来写插件的,然后主服务器加载,运行s。这意味着您无法 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?
}
}
}
}
使用我们的代理,我们已经有效地创建了一个 fake Player class,它会简单地调用 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 中的各种不同方法。
今天在 class 中,我们讨论了 Java 编程中的反射。今天课程的一部分是关于在 Java 中使用 InvocationHandler,而不仅仅是实现一个接口。 当我问老师使用调用处理程序有什么好处时,没有明确的答案。 假设我们有一个接口 Plugin
public interface Plugin {
void calculate(double a, double b);
String getCommand();
}
您可以在 class 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;
}
}
提前致谢:)
Proxy is a dynamic proxy,允许您在 运行 时间 更改对象的行为,而不必在 编译时决定它-时间.
例如,假设我们希望 return 仅在夜间为 null。如果要静态地实现它,则需要将逻辑写入所有 classes,例如
if(isNight())
return null;
return normalValue;
这需要您实际更改 class,并且您需要更改 所有 classes。
但是对于 Proxy
,你可以将上面的逻辑写入 InvocationHandler
并且正常的 classes 甚至不知道它们的值在 InvocationHandler
期间没有被使用夜晚。您的代码现在使用的是动态代理,而不是原来的 class,但它不知道其中的区别。
这还允许您拥有多个 InvocationHandlers
,这样您就可以 运行 您的带有参数的代码来决定是否要记录呼叫、出于安全原因阻止呼叫或任何其他类似的事情,这对于静态实现来说是完全不可能的。
你不太可能直接使用那些 classes,因为它们的级别很低。然而 AOP 使用动态代理或字节码操作来完成它的任务。如果您曾经使用过 Spring,您很可能在不知情的情况下使用过 InvocationHandler
。当您将 @Transactional
放在方法上时,InvocationHandler
将拦截方法调用并为您启动(和结束)事务。
InvocationHandler
与 Proxy
一起允许在运行时实现接口,而无需编译特定于接口的代码。它通常用于调解对实现相同接口的 class 对象的访问。 Proxy
不允许 更改现有对象或 classes 的行为。
比如可以用于客户端的远程方法调用,将方法调用通过网络转发到服务器。
我第一次使用 Proxy
是为了记录对宽接口的方法调用,该接口表示通过有线格式接收的命令。这很容易产生非常一致的调试输出,但在界面更改时几乎不需要维护。
Java 注释接口可以在运行时由 Proxy
代理对象表示,以防止 classes 的爆炸。
java.beans.EventHandler
在 lambda 和方法引用出现之前很有用,可以在不使用膨胀罐的情况下实现事件监听器。
根据更具体或更真实的示例,您可以 运行 使用第三方或开源 API 更多地了解这些类型的反射用法。一个非常流行的例子是我的世界,特别是 Bukkit/Spigot.
这个api是用来写插件的,然后主服务器加载,运行s。这意味着您无法 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?
}
}
}
}
使用我们的代理,我们已经有效地创建了一个 fake Player class,它会简单地调用 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 中的各种不同方法。