Java объявить анонимный класс с помощью прокси API - PullRequest
0 голосов
/ 30 августа 2018

Этим утром я попал в частный случай, который никогда не случался со мной раньше. Я разрабатываю плагин для Minecraft с использованием API сервера Minecraft, который обычно называется NMS со ссылкой на имя его пакетов (например, net.minecraft.server.v1_13_R1 для версии 1.13).

Основная проблема с использованием API сервера minecraft заключается в том, что сложно написать код кросс-версии: в действительности имя пакетов меняется с каждой новой версией.

Когда плагин поддерживает только две версии, обычно проще использовать интерфейсы для написания двух разных кодов в зависимости от версии. Но когда вам нужно поддерживать дюжину разных версий (и это мой случай), это плохая идея (плагин будет слишком тяжелым, ему придется импортировать каждый jar в IDE, и мне придется переделывать код с каждой новой версией). В этих случаях я обычно использую отражение, но я не думаю, что это возможно здесь:

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

Как вы, наверное, догадались, MinecraftKey - это класс из NMS, и мне сказали использовать API динамического прокси Java. Я никогда не использовал это и хотел бы знать, если бы вы знали место, которое объяснило бы мне, как это сделать просто? Если вы знаете другой лучший метод, который меня тоже интересует!

Когда я думаю об этом, я думаю, что это действительно большая проблема для крошечного куска кода x)

РЕДАКТИРОВАТЬ: Мой плагин использует PacketPlayOutCustomPayload (также называемые сообщениями плагинов) для связи с модами игроков. Это позволяет мне отправить сообщение (байт []) по определенному каналу (строка). Но в версии 1.13 эта строка была заменена на MinecraftKey (оболочка для строки, которая заменяет некоторые символы и требует использования ":"). Это создает проблему, когда игроки подключаются к 1.12 на моем сервере 1.13, поэтому у меня нет выбора: в этом случае мне нужно переопределить объект MinecraftKey.

1 Ответ

0 голосов
/ 31 августа 2018

Я на самом деле не думаю, что использование прокси-классов является хорошим решением здесь, это только усложнит отладку, но если вам нужно что-то подобное, вы должны использовать библиотеку типа ByteBuddy: (поскольку Java не может генерировать прокси для класс, разрешены только интерфейсы)

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 и содержит много интерфейсов для представления типов NMS в некотором нормализованном и удобочитаемом виде.
И раздельные модули для каждой версии, тогда у вас не будет большого количества кода для дублирования, так как большая часть его будет абстрагироваться и обрабатываться этим модулем «ядро / основа».
Затем вы можете создать его как одну единственную толстую банку или отдельный .jar за версию.

Другим решением может быть использование некоторых шаблонизаторов и препроцессоров для генерации Java-источников во время сборки, посмотрите, как это делает fastutil: https://github.com/vigna/fastutil

И еще одним решением для простых классов и частей кода было бы использование встроенного javascript или внешнего языка сценариев, например groovy, для создания этой строки шаблона, но во время выполнения. Но я бы использовал это только для самых простых вещей.

Также для использования только методов вы можете использовать обычные отражения.

Вы также можете всегда вводить в netty и вместо использования сериализатора пакетов по умолчанию просто записывать собственные байты, тогда вам вообще не нужен этот ключ.

...