在不调用超类构造函数的情况下创建 object 的实例
Creating instance of object without calling superclass constructor
是的,我的标题听起来很疯狂,但请耐心等待。
我正在尝试记录 Minecraft Forge mod 通过其 SimpleNetworkWrapper
实例发送的所有内容。为此,我将实例包装在虚拟 class 中,并通过反射将虚拟实例插入到 mod 中。
虚拟 class:
package net.benjaminurquhart.decimated;
import cpw.mods.fml.common.network.simpleimpl.IMessage;
import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.Packet;
public class FakeNetworkWrapper extends SimpleNetworkWrapper {
private SimpleNetworkWrapper real;
public FakeNetworkWrapper() {
super("ctx");
}
protected void setChannel(SimpleNetworkWrapper channel) {
this.real = channel;
}
@Override
public Packet getPacketFrom(IMessage message) {
return real.getPacketFrom(message);
}
@Override
public void sendToAll(IMessage message) {
real.sendToAll(message);
}
@Override
public void sendTo(IMessage message, EntityPlayerMP player) {
real.sendTo(message, player);
}
@Override
public void sendToServer(IMessage message) {
// Intercept messages here, do logging stuff
real.sendToServer(message);
}
}
问题是,superclass 构造函数使用给定的名称(在本例中为 ctx
)创建了一个新的数据包通道。这打破了真实频道,因为我正在创建一个同名的新频道。我不能省略 super 调用,因为没有默认构造函数,我不想用不同的名称创建一个新的未使用的通道。
目前,我正在使用 Unsafe
:
绕过构造函数
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
unsafe = (Unsafe) unsafeField.get(null);
FakeNetworkWrapper wrapper = (FakeNetworkWrapper) unsafe.allocateInstance(FakeNetworkWrapper.class);
wrapper.setChannel(mod.getPacketChannel());
显然,我不想使用内部 classes。
快速 google 搜索表明我可以使用序列化或 ReflectionFactory
而不是 Unsafe
。 ReflectionFactory
也是一个内部 class 并且序列化似乎有点过分了。我也不想包含 Forge 未提供的依赖项(如 this answer 建议)。
有没有办法在不使用内部方法或序列化的情况下以某种方式绕过对 superclass 构造函数的调用?同样,我理解 这是我不应该做的事情,但我看不到其他方式。
在我看来,您应该继续使用 Unsafe,它有点 "unsupported" api 而不是内部的。 (它在 java 9+ 中有自己的模块,与纯内部模块分开)
但首先让我告诉你,任何创建 class 实例而不调用它的超级构造函数的方法都是你不应该做的,所以无论你选择什么解决方案,它永远不会干净。这就是为什么我只是建议坚持不安全。
还要记住,构造函数不仅是构造函数内部的代码,还有初始化字段和代码块的代码:
private int something = 5;
{ somethingElse = new X(); }
因此 something
将为 0,而 somethingElse
将在不调用构造函数的情况下为 null。
但还有其他选择 - 前 2 个是干净的,但只有在少数情况下才有可能:
- 如果您仅使用此 class,则只需创建不扩展原始调用的包装器并在您的代码库中的任何地方使用它。
也许你不需要直接扩展SimpleNetworkWrapper?但是 extend/implement SimpleNetworkWrapper 正在实施的东西?
您可以使用像 ASM 这样的库在运行时生成 class,这将在不调用其构造函数的情况下创建对象实例,因为在字节码中创建对象和构造函数调用是分开的事情。遗憾的是,这个选项需要 -noverify
jvm flag for java 7+
您需要做的就是生成一个 class 方法,该方法创建并 return FakeNetworkWrapper 的新实例但不调用构造函数。
mv.visitTypeInsn(NEW, "my/package/FakeNetworkWrapper"); // creates new instance
mv.visitInsn(ARETURN);
您还可以使用 ASM 生成扩展 class - 然后更改构造函数,这样它将调用对象超级构造函数而不是实际的超级 class - 这不需要 -noverify
继续使用不安全。
- 使用 ReflectionFactory,但我真的没有理由这样做。
- 使用工具根据 startup/on 需求修改 SimpleNetworkWrapper 并将代码添加到您要跟踪的每个方法,或者只是更改它的构造函数以便调用两次是安全的。但这也需要启动标志或更多 hack 才能在运行时执行此操作,并且不适用于纯 JRE - JDK 需要在运行时执行此操作。
- 如果您可以在加载 SimpleNetworkWrapper class 之前执行一些代码,您可以创建自己的版本并首先将其加载到与原始加载器相同的 class 加载器中 - 也可能需要如果它与您的不同,则将其注入正确的 class 加载程序。
是的,我的标题听起来很疯狂,但请耐心等待。
我正在尝试记录 Minecraft Forge mod 通过其 SimpleNetworkWrapper
实例发送的所有内容。为此,我将实例包装在虚拟 class 中,并通过反射将虚拟实例插入到 mod 中。
虚拟 class:
package net.benjaminurquhart.decimated;
import cpw.mods.fml.common.network.simpleimpl.IMessage;
import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.Packet;
public class FakeNetworkWrapper extends SimpleNetworkWrapper {
private SimpleNetworkWrapper real;
public FakeNetworkWrapper() {
super("ctx");
}
protected void setChannel(SimpleNetworkWrapper channel) {
this.real = channel;
}
@Override
public Packet getPacketFrom(IMessage message) {
return real.getPacketFrom(message);
}
@Override
public void sendToAll(IMessage message) {
real.sendToAll(message);
}
@Override
public void sendTo(IMessage message, EntityPlayerMP player) {
real.sendTo(message, player);
}
@Override
public void sendToServer(IMessage message) {
// Intercept messages here, do logging stuff
real.sendToServer(message);
}
}
问题是,superclass 构造函数使用给定的名称(在本例中为 ctx
)创建了一个新的数据包通道。这打破了真实频道,因为我正在创建一个同名的新频道。我不能省略 super 调用,因为没有默认构造函数,我不想用不同的名称创建一个新的未使用的通道。
目前,我正在使用 Unsafe
:
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
unsafe = (Unsafe) unsafeField.get(null);
FakeNetworkWrapper wrapper = (FakeNetworkWrapper) unsafe.allocateInstance(FakeNetworkWrapper.class);
wrapper.setChannel(mod.getPacketChannel());
显然,我不想使用内部 classes。
快速 google 搜索表明我可以使用序列化或 ReflectionFactory
而不是 Unsafe
。 ReflectionFactory
也是一个内部 class 并且序列化似乎有点过分了。我也不想包含 Forge 未提供的依赖项(如 this answer 建议)。
有没有办法在不使用内部方法或序列化的情况下以某种方式绕过对 superclass 构造函数的调用?同样,我理解 这是我不应该做的事情,但我看不到其他方式。
在我看来,您应该继续使用 Unsafe,它有点 "unsupported" api 而不是内部的。 (它在 java 9+ 中有自己的模块,与纯内部模块分开)
但首先让我告诉你,任何创建 class 实例而不调用它的超级构造函数的方法都是你不应该做的,所以无论你选择什么解决方案,它永远不会干净。这就是为什么我只是建议坚持不安全。
还要记住,构造函数不仅是构造函数内部的代码,还有初始化字段和代码块的代码:
private int something = 5;
{ somethingElse = new X(); }
因此 something
将为 0,而 somethingElse
将在不调用构造函数的情况下为 null。
但还有其他选择 - 前 2 个是干净的,但只有在少数情况下才有可能:
- 如果您仅使用此 class,则只需创建不扩展原始调用的包装器并在您的代码库中的任何地方使用它。
也许你不需要直接扩展SimpleNetworkWrapper?但是 extend/implement SimpleNetworkWrapper 正在实施的东西?
您可以使用像 ASM 这样的库在运行时生成 class,这将在不调用其构造函数的情况下创建对象实例,因为在字节码中创建对象和构造函数调用是分开的事情。遗憾的是,这个选项需要
-noverify
jvm flag for java 7+
您需要做的就是生成一个 class 方法,该方法创建并 return FakeNetworkWrapper 的新实例但不调用构造函数。mv.visitTypeInsn(NEW, "my/package/FakeNetworkWrapper"); // creates new instance mv.visitInsn(ARETURN);
您还可以使用 ASM 生成扩展 class - 然后更改构造函数,这样它将调用对象超级构造函数而不是实际的超级 class - 这不需要
-noverify
继续使用不安全。
- 使用 ReflectionFactory,但我真的没有理由这样做。
- 使用工具根据 startup/on 需求修改 SimpleNetworkWrapper 并将代码添加到您要跟踪的每个方法,或者只是更改它的构造函数以便调用两次是安全的。但这也需要启动标志或更多 hack 才能在运行时执行此操作,并且不适用于纯 JRE - JDK 需要在运行时执行此操作。
- 如果您可以在加载 SimpleNetworkWrapper class 之前执行一些代码,您可以创建自己的版本并首先将其加载到与原始加载器相同的 class 加载器中 - 也可能需要如果它与您的不同,则将其注入正确的 class 加载程序。