java使用代理api声明匿名类

标签 java reflection proxy minecraft

今天早上,我遇到了一个以前从未发生过的特殊情况。我正在使用 Minecraft 服务器 API 开发一个 Minecraft 插件,根据其包的名称,该 API 通常称为 NMS(例如版本 1.13 的 net.minecraft.server.v1_13_R1)。

使用minecraft服务器API的主要问题是很难编写跨版本代码:确实,包的名称随着每个新版本的变化而变化。

当插件仅支持两个版本时,通常更容易使用接口(interface)根据版本编写两个不同的代码。但是当你必须支持十几个不同的版本时(这就是我的情况),这是一个坏主意(插件会太重,它必须导入 IDE 中的每个 jar,而且我必须为每个新版本重做代码)。 在这些情况下,我通常使用反射,但我认为在这里不可能:

            packet = packetConstructor.newInstance(
                    new MinecraftKey("q", "q") {
                        @Override
                        public String toString() {
                            return "FML|HS";
                        }
                    },
                    packetDataSerializerConstructor.newInstance(Unpooled.wrappedBuffer(data)));

正如您可能猜到的那样,MinecraftKey 是 NMS 的一个类,我被告知要使用 Java 动态代理 API。我从未使用过它,想知道您是否知道一个可以向我简单解释如何操作的地方?如果您知道我也感兴趣的另一种更好的方法!

仔细想想,我觉得这对于一小段代码来说确实是一个很大的麻烦 x)

编辑: 我的插件使用 PacketPlayOutCustomPayload(又名插件消息)与玩家的 mod 进行通信。它允许我在特定 channel (字符串)上发送消息(字节 [])。但在 1.13 中,该字符串已被 MinecraftKey 取代(字符串的包装器,替换了一些字符并需要使用“:”)。当玩家连接到我的 1.13 服务器上的 1.12 时,这会带来问题,因此我别无选择:在这种情况下,我必须覆盖 MinecraftKey 对象。

最佳答案

我真的不认为使用代理类在这里是一个好的解决方案,它只会让调试变得更加困难,但是如果你需要类似的东西,你应该使用像ByteBuddy这样的库:(因为java无法为类生成代理,只允许接口(interface))

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.implementation.FixedValue;
import static net.bytebuddy.matcher.ElementMatchers.*;

public class Main {
    public static void main(String[] args) throws Exception {
        SomeKey someKey = new SomeKey("my", "key");
        System.out.println(someKey); // :<
        // this class should be cached/saved somewhere, do not create new one each time.
        Class<? extends SomeKey> loaded = new ByteBuddy()
                .subclass(SomeKey.class)
                .method(named("toString").and(returns(String.class).and(takesArguments(0))))
                .intercept(FixedValue.value("something"))
                .make()
                .load(Main.class.getClassLoader()).getLoaded();
        someKey = loaded.getConstructor(String.class, String.class).newInstance("what", "ever");
        System.out.println(someKey); // YeY
    }
}
class SomeKey {
    final String group;
    final String name;
    public SomeKey(String group, String name) {
        this.group = group;
        this.name = name;
    }
    public String getGroup() { return this.group; }
    public String getName() { return this.name; }
    @Override public String toString() {
        return group+":"+name;
    }
}

但我只是在我的项目中创建单独的模块,该模块仅适用于真正的 bukkit API,并包含许多接口(interface)以某种标准化和可读的方式表示 NMS 类型。
并为每个版本提供单独的模块,这样您就不需要重复太多代码,因为其中大部分代码将由“核心/基础”模块抽象和处理。
然后,您可以将其构建为单个 fat jar 或每个版本的单独 .jar。

其他解决方案可能是使用一些模板引擎和预处理器在构建时生成java源,看看fastutil是如何做到这一点的: https://github.com/vigna/fastutil

对于简单类和部分代码的另一种解决方案是使用内置 javascript 或外部脚本语言(如 groovy)来创建此模式行,但在运行时。但我只会将其用于最简单的事情。

对于仅使用方法,您也可以使用普通反射。

您也可以随时注入(inject) netty,而不是使用默认的数据包序列化器,只需编写自己的字节,那么您根本不需要该 key 。

关于java使用代理api声明匿名类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52101627/

相关文章:

javascript - 尝试查找元素返回 "Proxy?"

c# - 如何为特定接口(interface)类型注册DI服务?

java - Springboot的@Controller和@RestController注解什么时候用,底层概念是什么?

java - 如何在 Jackson2 中使用多参数构造函数反序列化枚举?

java - 使用 java 查询 MySQL 数据库

scala - 从 Scala 宏访问代码文件和行号?

c# - 将生成 SelectList 的两个方法重构为一个方法

android - 如何在 Android 上捕获应用程序流量

Java 6 NTLM 代理身份验证和 HTTPS - 有人让它工作吗?

java - 空安全 boolean 表达式评估