我正在为Minecraft服务器实现CraftBukkit编写一个插件,我遇到了一个问题,需要转换到通过反射找到的类。
这是交易。我写的原始代码是这样的,去掉了不相关的部分:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import net.minecraft.server.v1_7_R3.EntityAnimal;
import net.minecraft.server.v1_7_R3.EntityHuman;
import org.bukkit.craftbukkit.v1_7_R3.entity.CraftAnimals;
import org.bukkit.craftbukkit.v1_7_R3.entity.CrafteEntity;
import org.bukkit.World;
import org.bukkit.entity.Animals;
import org.bukkit.entity.Entity;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
public class Task extends BukkitRunnable {
private static final int MATING_DISTANCE = 14;
private final JavaPlugin plugin;
private final Random randomizer;
private boolean mateMode;
private double chance;
public Task(JavaPlugin plugin, double chance, boolean mateMode) {
this.plugin = plugin;
this.randomizer = new Random();
this.chance = chance;
this.mateMode = mateMode;
this.theTaskListener = listener;
}
public void run() {
List<World> worlds = plugin.getServer().getWorlds();
Iterator<World> worldIterator = worlds.iterator();
while (worldIterator.hasNext()) {
World world = worldIterator.next();
Collection<Animals> animals = world.getEntitiesByClass(Animals.class);
Iterator<Animals> animalIterator = animals.iterator();
while (animalIterator.hasNext()) {
Animals animal = (Animals) animalIterator.next();
EntityAnimal entity = (EntityAnimal) ((CraftEntity) ((CraftAnimals) animal)).getHandle();
EntityHuman feeder = null;
entity.f(feeder);
}
}
}
}
然而,正如您在导入中看到的,这段代码仅从Minecraft服务器包的一个版本v1_7_R3导入了类。现在的问题是,我想添加更多的支持,我希望能够做到这一点,而无需为每个版本的《我的世界》创建单独版本的插件。尽管包中的大多数类都是相同的(至少是我需要的所有类),但包名称不同,因此无法使用静态导入(或者至少我认为是这样?)
因此,我决定使用反射来获得我需要的正确类(此代码在另一个类中):
private static final String[] requiredClasses = {
"net.minecraft.server.%s.EntityAnimal",
"net.minecraft.server.%s.EntityHuman",
"org.bukkit.craftbukkit.%s.entity.CraftAnimals",
"org.bukkit.craftbukkit.%s.entity.CraftEntity"
};
public static final String[] supportedVersions = {
"v1_7_R3",
"v1_7_R4"
};
public Class<?>[] initializeClasses() {
String correctVersion = null;
for (int i = 0; i < supportedVersions.length; i++) {
String version = supportedVersions[i];
boolean hadIssues = false;
for (int j = 0; j < requiredClasses.length; j++) {
String className = requiredClasses[j];
try {
Class.forName(String.format(className, version));
} catch (ClassNotFoundException e) {
getLogger().log(Level.INFO, String.format("The correct version isn't %s.", version));
hadIssues = true;
break;
}
}
if (!hadIssues) {
correctVersion = version;
break;
}
}
Class[] classes = new Class[requiredClasses.length];
if (correctVersion != null) {
getLogger().log(Level.INFO, String.format("The correct version is %s.", correctVersion));
for (int i = 0; i < requiredClasses.length; i++) {
String className = requiredClasses[i];
try {
classes[i] = Class.forName(String.format(className, correctVersion));
} catch (ClassNotFoundException e) {}
}
} else {
getLogger().log(Level.WARNING, "The version of Minecraft on this server is not supported.");
getLogger().log(Level.WARNING, "Due to this, the plugin will self-disable.");
getLogger().log(Level.WARNING, "To fix this issue, get build that supports your version.");
this.setEnabled(false);
}
return classes;
}
现在,这种方法成功地检索了当前支持的两个版本中所需的类。我使用实例变量和经过编辑的构造函数将这些传递给重写的Task类,并删除了特定于版本的导入:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import org.bukkit.World;
import org.bukkit.entity.Animals;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
public class Task extends BukkitRunnable {
private static final int MATING_DISTANCE = 14;
private final JavaPlugin plugin;
private final Random randomizer;
private boolean mateMode;
private double chance;
private Class entityAnimal;
private Class entityHuman;
private Class craftAnimals;
private Class craftEntity;
public Task(JavaPlugin plugin, Class[] classes, double chance, boolean mateMode) {
this.plugin = plugin;
this.randomizer = new Random();
this.chance = chance;
this.mateMode = mateMode;
this.entityAnimal = classes[0];
this.entityHuman = classes[1];
this.craftAnimals = classes[2];
this.craftEntity = classes[3];
}
现在,我如何重写Task.run()方法,使其使用反射类?这涉及到大量的类型转换,不幸的是,由于Minecraft代码中大量的重载,这一切都是必要的。例如,不能简单地通过执行entity.f(null)来调用entity-f(EntityHuman-han)方法,因为还有其他重载entity.f(Object对象)方法。
我对所有的建议都持开放态度,因为我在这里面临着死胡同。如果有更好的方法来解决这个问题,我也可以改变。
谢谢!
在面向对象的语言中,我们可以访问正是为此目的开发的各种设计模式。我们特别使用了两种模式。
适配器模式用于为许多不同的实现提供相同的接口。它有时被称为垫片。每个服务器的每个版本创建一个类,并将库导入到每个服务器。该类实现了它们共同拥有的接口。
Factory模式用于在适配器类中进行选择。您可以使用所需的任何方法来确定您拥有的服务器版本,它将创建一个实现正确接口的对象。主代码保持不变。它调用工厂来获得一个知道如何处理服务器的对象。
这种方法的优点有几个。导入重叠的库不会污染名称空间。随着新服务器版本的添加,主代码不太容易发生更改;唯一需要编写的代码是新的服务器填充程序和决定生产哪个适配器的工厂。
只是一个集思广益的想法。如果:
- 导入所有支持的版本
- 完全引用适当程序包的类型
-
检查针对特定运行时的版本(假设它可以以某种方式获得)
import net.minecraft.server.v1_7_R3.*; import net.minecraft.server.v1_7_R4.*; enum Version { V1_7_R3, V1_7_R4 } Version currentVersion; net.minecraft.server.v1_7_R3.EntityAnimal animal3; net.minecraft.server.v1_7_R4.EntityAnimal animal4; // obtain currentVersion switch ( currentVersion ) { case V1_7_R3: animal3.method(); break; case V1_7_R4: animal4.method(); break; }
当然,这在某种程度上是丑陋的,但在特定的情况下,这是我首先想到的可能性。
在阅读Gerold Broser的回复后,我意识到我必须以某种方式修改我的方法,才能创建某种处理程序类来执行特定于版本的操作-当然,这将是一个接口,每个版本由一个类单独实现。
然而,当我意识到Maven不允许我调用同一groupid.artifactd对象的两个版本时,这就成了一个问题。
我很快做了一些研究,找到了mbaxter的多版本教程以及AbstrationExamplePlugin实现,它完美地展示了这种方法。
这种方法非常有效,是每个Bukkit开发人员都应该使用的方法。这是我完成的插件,如有必要,可供进一步参考。