夏雨吖
本帖最后由 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)





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





Tds...
虽然看不懂,但是留个爪!

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

mr2044154518
支持支持支持

邓毅
其实我有点看不懂

dousha0v0
虽然真的看不懂 但是 楼主干巴爹!

不好,快跑!
        MCBBS有你更精彩?

KBlom
顶                                 

第一页 上一页 下一页 最后一页