从Bukkit nms再看Java反射机制

本文探讨了在Bukkit插件开发中遇到的版本兼容问题,以及如何利用Java反射机制解决这个问题。通过反射,可以在运行时动态获取类和对象信息,减少对外部依赖,但也会带来代码复杂性和性能损失。文中详细展示了如何使用反射创建和操作NMS类,以实现跨版本的兼容性,并讨论了反射在获取注解和减少依赖方面的潜力。
摘要由CSDN通过智能技术生成

前段时间研究Bukkit NMS包时遇到了每个版本包名不同导致的插件严格受到依赖版本限制的问题,最终尝试了使用反射机制变相调用NMS包的内容,虽然最后由于方法名混淆导致依旧没有成功,但是关于Java反射机制又有了新的认识。

什么是反射

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

这是比较正规的说法,用我自己的理解来说就是:
在Java中万物皆为对象,也就是说包括类本身、类的方法、属性、构造器也都可以看做是对象,而实际上Java的reflect包中也确实提供了对应的类来表示这些对象,这样我们就可以利用反射机制来实现在不导入任何包的情况下调用任意类方法和属性,甚至获取这个类的对象

不使用反射的代码

    public void AITaskTest(Player sender){
        Location loc = sender.getEyeLocation();
        WorldServer world = ((CraftWorld) sender.getWorld()).getHandle();
        EntitySkeleton skeleton = new EntitySkeleton(world);
        skeleton.setCustomName(ChatColor.RED + "a_skeleton");
        skeleton.setLocation(loc.getX(), loc.getY(), loc.getZ(), 0f, 0f);
        skeleton.goalSelector = new PathfinderGoalSelector(world != null && world.methodProfiler != null ? world.methodProfiler : null);
        skeleton.goalSelector.a(1, new PathfinderGoalMeleeAttack(skeleton, 1, false));
        skeleton.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(skeleton, EntityCreeper.class, true));
        skeleton.goalSelector.a(3, new PathfinderGoalMoveTowardsTarget(skeleton, 10D, 10f));
        world.addEntity(skeleton);
        sender.sendMessage(ChatColor.RED + "Command run finished");
    }

如上述代码,WorldServer、EntitySkeleton、PathfinderGoalSelector等类都是属于NMS的类,其包名命名规则为:net.minecraft.server.版本号.*;
也就是说Bukkit版本不同类的包名就不同,插件在不同版本的服务器上完全没有兼容性可言。
为此需要对代码进行亿点小小的改动,使用反射机制来实现以上代码
由此最终代码被改为如下内容:

private Class<?> worldServerClass;
    private Class<?> worldClass;
    private Class<?> craftWorldClass;
    private Class<?> entitySkeletonClass;
    private Class<?> pathfinderGoalSelector;
    private Class<?> pathfinderGoal;
//    private Class<?> gameProfilerFiller;
    private Class<?> methodProfiler;
    private Class<?> pathfinderGoalMeleeAttack;
    private Class<?> entityCreature;
    private Class<?> entityCreeper;
//    private Class<?> entityInsentient;
    private Class<?> pathfinderGoalNearestAttackableTarget;

    public DreamLandsCommandExecutor(){
        try {
            worldServerClass = BaseUtil.getNmsClass("WorldServer");
            worldClass = BaseUtil.getNmsClass("World");
            craftWorldClass = BaseUtil.getCraftBukkitClass("CraftWorld");
            entitySkeletonClass = BaseUtil.getNmsClass("EntitySkeleton");
            pathfinderGoalSelector = BaseUtil.getNmsClass("PathfinderGoalSelector");
            pathfinderGoal = BaseUtil.getNmsClass("PathfinderGoal");
//            gameProfilerFiller = BaseUtil.getNmsClass("GameProfilerFiller");
            pathfinderGoalMeleeAttack = BaseUtil.getNmsClass("PathfinderGoalMeleeAttack");
            entityCreature = BaseUtil.getNmsClass("EntityCreature");
            entityCreeper = BaseUtil.getNmsClass("EntityCreeper");
//            entityInsentient = BaseUtil.getNmsClass("EntityInsentient");
            pathfinderGoalNearestAttackableTarget = BaseUtil.getNmsClass("PathfinderGoalNearestAttackableTarget");
            methodProfiler = BaseUtil.getNmsClass("MethodProfiler");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
private void AITaskTest(Player sender) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        Location loc = sender.getEyeLocation();
        // 获取CraftWorld下的getHandle方法
        Method getHandle = craftWorldClass.getMethod("getHandle");
        // 将World对象强制类型转换为CraftWorld,并执行getHandle方法,获得WorldServer对象
        Object world = getHandle.invoke(craftWorldClass.cast(sender.getWorld()));
        // 调用EntitySkeleton的构造器并新建一个对象
        Object skeleton = entitySkeletonClass.getConstructor(worldClass).newInstance(world);
        // 调用setCustomName方法为骷髅命名
        // 这里在spigot中运行会抛出NoSuchMethodException异常
        // 但是实际上我找到的方法名就是这个,如果有懂的老哥还请赐教
//        entitySkeletonClass.getMethod("setCustomName", String.class).invoke(skeleton, ChatColor.RED + "a_skeleton");
        // 调用setLocation方法设置骷髅的位置,即玩家目光所视之处
//        entitySkeletonClass.getMethod("setLocation", double.class, double.class, double.class).invoke(skeleton, loc.getX(), loc.getY(), loc.getZ(), 0f, 0f);
        // 重新设置骷髅的goalSelector属性
        Object methodProfiler = world == null ? null : worldServerClass.getField("methodProfiler").get(world);
        Field goalSelector = entitySkeletonClass.getField("goalSelector");
        goalSelector.set(skeleton, pathfinderGoalSelector.getConstructor(this.methodProfiler).newInstance(methodProfiler));
        // 获取PathfinderGoalMeleeAttack的构造器
        Constructor<?> constructor = pathfinderGoalMeleeAttack.getConstructor(entityCreature, double.class, boolean.class);
        // 调用PathfinderGoalSelector的a方法并传入PathfinderGoalMeleeAttack的构造器产生的实例
        // 此时invoke的对象应该的skeleton对象的goalSelector属性,所以使用Field goalSelector的get方法获取EntitySkeletonClass类下的goalSelector属性
        Object selector = constructor.newInstance(skeleton, 1, false);
        pathfinderGoalSelector.getMethod("a", int.class, pathfinderGoal).invoke(goalSelector.get(skeleton), 1, selector);
        // 获取EntitySkeletonClass对象的targetSelector属性
        Field targetSelector = entitySkeletonClass.getField("targetSelector");
        // 使用PathfinderGoalNearestAttackableTarget的构造器新建一个PathfinderGoalNearestAttackableTarget对象
        Object target = pathfinderGoalNearestAttackableTarget.getConstructor(entityCreature, Class.class, boolean.class).newInstance(skeleton, entityCreeper, true);
        // 与上面道理相同,invoke的对象应该是skeleton的targetSelector属性
        pathfinderGoalSelector.getMethod("a", int.class, pathfinderGoal).invoke(targetSelector.get(skeleton), 2, target);
//        worldServerClass.getMethod("addEntity", entityInsentient).invoke(world, skeleton);
        sender.sendMessage(ChatColor.RED + "command run finished");
    }

BaseUtil的代码如下:

public static Class<?> getNmsClass(String name) throws ClassNotFoundException {
        return Class.forName("net.minecraft.server." + version + "." + name);
}

public static Class<?> getCraftBukkitClass(String name) throws ClassNotFoundException {
        return Class.forName("org.bukkit.craftbukkit." + version + "." + name);
}

不难发现,使用反射机制的话,我们完全不需要导入任何第三方类,所有对象都可以使用Object来表示,同时又能调用该对象所有已知且已存在的方法,但是同时也需要处理一堆异常,代码也远比原先复杂,那么付出这么大的代价,我们能做什么?

从这个例子上我们就可以得出,使用反射机制可以减少程序的外部依赖,尽管在Bukkit插件上,已经有Protocollib这类的发包前置库,但是不排除其他需要需要在不同包名下调用同一个类的场景。
再看java.lang.reflect包的UML图
在这里插入图片描述

Constructor、Method、Field、Class最终都是AnnotatedElement的实现类,与注解的使用范围一致,也就是说可以利用反射机制来获取相应对象的注解,以此来实现自定义注解的功能,通过自定义注解来实现各种功能。

最后值得一提的是,使用反射生成对象的效率远不如在程序运行时生成对象的效率,使用反射获取类同样会降低程序的运行效率,因此除了需要慎重使用外,在使用反射机制时应尽将反射相关的代码放在程序启动时,尽可能的将需要使用的类对象在程序初始化的时候就全部赋予。

vscode是一种功能强大的集成开发环境,可用于开发各种编程语言的应用程序和插件。对于开发bukkit插件,vscode提供了许多有用的功能和插件,使开发过程更加高效和方便。 首先,vscode具有丰富的代码编辑功能,包括语法高亮、自动补全、代码片段和代码导航。这些功能有助于开发者更容易地编写和调试bukkit插件的代码,并提高代码的可读性和质量。 其次,vscode支持许多流行的插件,可以轻松扩展开发环境的功能。例如,通过安装Java插件,可以为bukkit插件项目提供实时错误检查、智能建议和自动重构等功能。同时,还可以通过安装Git插件,轻松管理版本控制和团队合作。 此外,vscode还内置了强大的调试工具,可帮助开发者在开发过程中快速定位和解决问题。借助这些调试工具,开发者可以轻松设置断点、监视变量和查看调用栈,从而更好地理解和调试bukkit插件的运行时行为。 最后,vscode还具有丰富的扩展生态系统,可以满足开发者的个性化需求。开发者可以通过安装各种插件和主题,定制自己的开发环境,使其更符合自己的喜好和习惯。 综上所述,vscode是一款适用于开发bukkit插件的理想工具。其强大的编辑功能、丰富的插件支持、强大的调试工具和个性化定制能力,都使得开发者能够更高效、更舒适地进行bukkit插件开发工作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值