[Bukkit_Spigot]一种特别的方式加载插件 - 编程开发 - Minecraft(我的世界)中文论坛 -.html

[Bukkit/Spigot]一种特别的方式加载插件 - 编程开发 - Minecraft(我的世界)中文论坛 -

Minecraft(我的世界)中文论坛

 找回密码
 注册(register)

!header_login!

只需一步,立刻登录

查看: 2490|回复: 6
打印 上一主题 下一主题

[插件开发教程] [Bukkit/Spigot]一种特别的方式加载插件

[复制链接]
夏雨吖 当前离线
积分
981
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2020-1-29
查看详细资料

来自:甘肃

跳转到指定楼层
楼主
 楼主| 发表于 2020-2-7 00:25:49 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

您尚未登录,立即登录享受更好的浏览体验!

您需要 登录 才可以下载或查看,没有帐号?注册(register)

x
本帖最后由 M8_mve 于 2020-2-7 00:23 编辑




用特别的方式加载插件



看这篇水贴教程之前需要

Java基础

反射知识

类加载器的简单原理

对ClassLoader的理解






这篇教程是哪里来的

之前写过一个UI化服务器的插件烂坑

是用Java FX写的UI

但是插件每次reload之后会重新实例化Plugin

这就会重新创建FX Application然后丢异常给我

我想能不能让插件只会实例化一次呢?

然后就有了这个想法和这篇教程



一、Loader

1、HyperPlugin
创建一个类做下面加载的类的父类,当然也可以直接用子类
  1. public class HyperPlugin
  2. {
  3.         public Plugin plugin;

  4.         public void onLoad() { }

  5.         public void onEnable() { }

  6.         public void onDisable() { }
  7. }
复制代码
这里的Plugin是保存每次加载时候的Plugin实例的,因为每次加载插件插件实例都会改变
因为plugin实例要在调用onEnable之前更新,访问权限不公开的话要用反射改,会拉低效率
当然如果你不考虑效率也可以用反射
反正都能反射改,公开不公开又有什么区别呢233
一会要写的Core的主类要继承这个类,重写这里的三个方法
当然也可以改个名字,或者加一些别的方法
2、HyperPluginClassLoader
创建一个用来加载Core的类加载器,可以继承java的URLClassLoader但是要打破双亲委派的规则
  1. public class HyperPluginClassLoader extends URLClassLoader
  2. {
  3.         public static final HyperPluginClassLoader LOADER;
  4.         private final Map<String, Class<?>> classes = new ConcurrentHashMap<>();
  5.         private final JarFile jar;
  6.         private final HyperPlugin plugin;
  7.         private Map<String, Class<?>> parentClasses = new HashMap<>();

  8.         private HyperPluginClassLoader(File pluginFile) throws IOException
  9.         {
  10.                 super(new URL[]{pluginFile.toURI().toURL()});
  11.                 this.jar = new JarFile(pluginFile);
  12.                 HyperPlugin plugin = null;
  13.                 try
  14.                 {
  15.                         Class<?> main = Class.forName("org.mve.hy.Main", true, this);
  16.                         plugin = (HyperPlugin) main.newInstance();
  17.                 }
  18.                 catch (Exception e)
  19.                 {
  20.                         e.printStackTrace();
  21.                 }
  22.                 this.plugin = plugin;
  23.                 if (this.plugin == null)
  24.                         throw new IllegalStateException("Can not load plugin: "+pluginFile);

  25.                 this.plugin.onLoad();
  26.         }

  27.         public void onEnable(Plugin plugin)
  28.         {
  29.                 this.parentClasses.putAll(this.classes);
  30.                 this.plugin.plugin = plugin;
  31.                 this.plugin.onEnable();
  32.         }

  33.         public void onDisable()
  34.         {
  35.                 this.plugin.onDisable();
  36.                 this.plugin.plugin = null;
  37.         }

  38.         @Override
  39.         public Class<?> findClass(String name) throws ClassNotFoundException
  40.         {
  41.                 return this.loadClass(name);
  42.         }

  43.         @Override
  44.         public Class<?> loadClass(String name) throws ClassNotFoundException
  45.         {
  46.                 Class<?> clazz = this.classes.get(name);
  47.                 if (clazz != null) return clazz;
  48.                 clazz = this.parentClasses.get(name);
  49.                 if (clazz != null) return clazz;
  50.                 try
  51.                 {
  52.                         String path = name.replace('.', '/').concat(".class");
  53.                         JarEntry entry = jar.getJarEntry(path);
  54.                         if (entry != null)
  55.                         {
  56.                                 InputStream in = this.jar.getInputStream(entry);
  57.                                 byte[] bytes = HyperPluginClassLoader.toByteArray(in);
  58.                                 in.close();
  59.                                 clazz = this.defineClass(name, bytes, 0, bytes.length);
  60.                                 if (clazz != null)
  61.                                 {
  62.                                         this.classes.put(name, clazz);
  63.                                         this.parentClasses.put(name, clazz);
  64.                                 }
  65.                         }
  66.                 }
  67.                 catch (Exception ignored) {}
  68.                 return clazz != null ? clazz : this.getParent().loadClass(name);
  69.         }

  70.         private static byte[] toByteArray(InputStream in) throws IOException
  71.         {
  72.                 ByteArrayOutputStream out = new ByteArrayOutputStream();
  73.                 int len;
  74.                 byte[] b = new byte[1024];
  75.                 while ((len = in.read(b)) > -1)
  76.                 {
  77.                         out.write(b, 0, len);
  78.                 }
  79.                 out.flush();
  80.                 out.close();
  81.                 return out.toByteArray();
  82.         }

  83.         static
  84.         {
  85.                 HyperPluginClassLoader loader = null;
  86.                 try
  87.                 {
  88.                         File coreFile = new File("plugins/HyperPlugin/Core.jar");
  89.                         loader = new HyperPluginClassLoader(coreFile);
  90.                 }
  91.                 catch (IOException e)
  92.                 {
  93.                         e.printStackTrace();
  94.                 }
  95.                 LOADER = loader;
  96.         }
  97. }
复制代码

这里的loadClass和findClass要自己实现,我把findClass推给了loadClass
我这里用了单例模式,防止反复实例化classloader
loadClass一定不能遵循双亲委派的规则,不然会出现ClassNotFoundException之类的奇怪的东西
这里的parentClasses是加载插件的PluginClassLoader的classes
这样可以假装自己加载过的类,父类加载器也加载过
如果其他插件要连接你的插件需要这个东西
在loadClass方法里加载新的类需要把加载的Class实例也put到这个parentClasses里
但是自己保存的classes也是必须的,因为每次重载插件,父类加载器都会变
这样这里的parentClasses也要变,在每次onEnable的时候需要把自己加载过的所有类都put进去
注意: 这里的静态代码块里指定的File一定是之后插件本体的位置
当然你可以改路径和名字但是别忘了改这里
这里的构造方法里加载的类一定是插件本体继承之前写的HyperPlugin的类

二、Plugin

1、用来加载插件本体的BukkitPlugin
现在写的类是让Bukkit来加载的
  1. public class PluginMain extends JavaPlugin
  2. {
  3.         @Override
  4.         public void onEnable()
  5.         {
  6.                 InputStream pluginIn = this.getResource("HyperPlugin.class");
  7.                 InputStream loaderIn = this.getResource("HyperPluginClassLoader.class");
  8.                 if (pluginIn == null)
  9.                         throw new IllegalStateException(
  10.                                 "Can not load core",
  11.                                 new FileNotFoundException("HyperPlugin.class")
  12.                         );
  13.                 if (loaderIn == null)
  14.                         throw new IllegalStateException(
  15.                                 "Can not load core",
  16.                                 new FileNotFoundException("HyperPluginClassLoader.class")
  17.                         );
  18.                 try
  19.                 {
  20.                         SystemClassLoader.define(IO.toByteArray(pluginIn));
  21.                         SystemClassLoader.define(IO.toByteArray(loaderIn));
  22.                 }
  23.                 catch (Throwable e)
  24.                 {
  25.                         if (! (e instanceof LinkageError))
  26.                                 throw new IllegalStateException("Can not load core", e);
  27.                 }
  28.                 try
  29.                 {
  30.                         Reflect.setFinalField(
  31.                                 ClassLoader.class,
  32.                                 "parent",
  33.                                 HyperPluginClassLoader.LOADER,
  34.                                 this.getClass().getClassLoader()
  35.                         );
  36.                         Reflect.setField(
  37.                                 HyperPluginClassLoader.class,
  38.                                 "parentClasses",
  39.                                 HyperPluginClassLoader.LOADER,
  40.                                 Reflect.reflectField(
  41.                                         PluginClassLoader.class,
  42.                                         "classes"
  43.                                 )
  44.                         );
  45.                 }
  46.                 catch (NoSuchFieldException | IllegalAccessException e)
  47.                 {
  48.                         e.printStackTrace();
  49.                 }
  50.                 HyperPluginClassLoader.LOADER.onEnable(this);
  51.         }

  52.         @Override
  53.         public void onDisable()
  54.         {
  55.                 HyperPluginClassLoader.LOADER.onDisable();
  56.         }
  57. }
复制代码

2、正确的加载
之前写的HyperPlugin和HyperPluginClassLoader这两个类一定不能直接丢到插件里
不能让PluginClassLoader加载它,不然和普通的插件加载没什么区别了
只要这两个class不在package指定的包里就可以,我直接放在缺省包里了

SystemClassLoader.define(IO.toByteArray(pluginIn));
SystemClassLoader.define(IO.toByteArray(loaderIn));

然后在插件的onEnable方法里把这两个类define到AppClassLoader也就是JVM第三级类加载器
也可以在onLoad方法里define,反正都一样只要别在onDisable里define就好 嗷呜~
因为每次插件onEnable都会define,重复加载相同的类会报错,需要捕捉一下异常,直接忽略掉
之后要改一下HyperPluginClassLoader的parent
还要改一下之前说过的parentClasses,这个Map在Bukkit的PluginClassLoader里
然后就可以onEnable你的插件本体啦

三、Core

现在就可以写插件的本体啦
这里写的插件本体在服务器运行的过程里只会调用一次onLoad方法
也就是只会加载一次类
虽然不知道有什么用,但是之前写fx的时候发现的这个方法就来水一贴了哈哈哈哈
本体的主类要继承之前写的HyperPlugin而不是JavaPlugin
主类必须和之类加载器里指定的主类一致(全限定名)
  1. public class Main extends HyperPlugin
  2. {
  3.         @Override
  4.         public void onLoad()
  5.         {
  6.                 System.out.println("Plugin load");
  7.                 System.out.println(this.getClass().getClassLoader());
  8.         }

  9.         @Override
  10.         public void onEnable()
  11.         {
  12.                 System.out.println("Plugin enable");
  13.                 System.out.println(this.getClass().getClassLoader());
  14.         }

  15.         @Override
  16.         public void onDisable()
  17.         {
  18.                 System.out.println("Plugin disable");
  19.                 System.out.println(this.getClass().getClassLoader());
  20.         }
  21. }
复制代码
可以输出一下类加载器看看前后的类是不是唯一

四、测试!测试!测试!

写好啦?快丢到服务器上面测试一下
第二节的Plugin编译的jar放在plugins文件夹里
第三节Core编译的jar放在之前类加载器指定的目录里
我指定的是plugins/HyperPlugin/Core.jar 当然你也可以改但是别忘记改类加载器里的路径
快把服务器run起来看一下


这里的顺序是
onLoad->onEnable->reload->onDisable->onEnable
onLoad后面的onEnable我没截图,反正都一样
这里输出的内存地址都一样说明插件在reload前后实例没有变
成功啦!嗷呜~




插件源码

这里IO工具和Reflect工具我也和源码一起丢出来了可以扒源码自己看

如果有什么不明白的地方可以来问我啦 嗷呜~

PLL.zip (29.91 KB, 下载次数: 20)





又是一篇不会有人看的没什么用的教程呢




评分

参与人数 8人气 +12金粒 +140宝石 +12收起 理由
乙烯_中国+ 12MCBBS有你更精彩~
天宫时雨+ 1+ 10MCBBS有你更精彩~
Kellyft+ 1神乎其技!6的飞起!
清晨w+ 2+ 30神乎其技!6的飞起!
海螺螺+ 3+ 25MCBBS有你更精彩~ 高亮奖励
mr2044154518+ 1+ 15Ssssssssssssssssssss
橙色通知书+ 2+ 30神乎其技!6的飞起!
Tds...+ 2+ 30大佬NB,期待一夜逆袭!

查看全部评分

帖子永久链接: 

Minecraft中文论坛 - 论坛版权1、本主题所有言论和图片纯属会员个人意见,与本论坛立场无关
2、本站所有主题由该帖子作者发表,该帖子作者享有帖子相关版权
3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者的同意
4、帖子作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任
5、本帖若有内容转载自其它媒体,不代表本站赞同其观点和对其真实性负责
6、若本帖涉及任何版权问题,请立即告知本站,本站将及时予以删除并致以最深的歉意
7、Minecraft(我的世界)中文论坛管理员和版主有权不事先通知发贴者而删除本文

Tds... 当前离线
积分
1787
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2017-6-3
查看详细资料

来自:四川

沙发
发表于 2020-2-7 00:29:08 | 只看该作者
虽然看不懂,但是留个爪!

谢谢大佬的分享,收藏了,等我会了再来看!

评分

参与人数 1人气 +1金粒 +15收起 理由
橙色通知书+ 1+ 15丢人梦魂

查看全部评分

回复

使用道具 举报

mr2044154518 当前离线
积分
378
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2020-1-19
查看详细资料

来自:广东

受到警告 板凳
发表于 2020-2-7 00:32:12 | 只看该作者
支持支持支持

评分

参与人数 1人气 +1收起 理由
夏雨吖+ 1发现dalao快抓起来

查看全部评分

回复

使用道具 举报

邓毅 当前离线
积分
71
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2020-3-2
查看详细资料

来自:广东

受到警告 地板
发表于 2020-3-3 10:47:49 来自手机 | 只看该作者
其实我有点看不懂
回复

使用道具 举报

dousha0v0 当前离线
积分
704
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2020-2-24
查看详细资料

来自:江苏

受到警告 5#
发表于 2020-3-5 14:23:04 | 只看该作者
虽然真的看不懂 但是 楼主干巴爹!
回复

使用道具 举报

不好,快跑! 当前离线
积分
3067
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2018-2-3
查看详细资料

来自:江苏

受到警告 6#
发表于 2020-3-5 14:56:53 | 只看该作者
        MCBBS有你更精彩?
回复

使用道具 举报

KBlom 当前离线
积分
123
帖子
主题
精华
贡献
爱心
钻石
人气
下界之星
最后登录
1970-1-1
注册时间
2019-11-16
查看详细资料

来自:河南

受到警告 7#
发表于 2020-3-6 13:50:19 | 只看该作者
顶                                 
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册(register)

本版积分规则

Archiver|小黑屋|Mcbbs.net ( 京ICP备15023768号-1 ) | 京公网安备 11010502037624号 | 手机版

GMT+8, 2023-8-18 23:13 , Processed in 0.052110 second(s), Total 24, Slave 23 queries, Release: Build.2023.08.07 0824, Gzip On, Redis On.

"Minecraft"以及"我的世界"为美国微软公司的商标 本站与微软公司没有从属关系

© 2010-2023 我的世界中文论坛 版权所有 本站内原创内容版权属于其原创作者,除作者或版规特别声明外未经许可不得转载