本帖最后由 M8_mve 于 2020-2-7 00:23 编辑
用特别的方式加载插件
看这篇水贴教程之前需要
Java基础
反射知识
类加载器的简单原理
对ClassLoader的理解
这篇教程是哪里来的
之前写过一个UI化服务器的插件烂坑
是用Java FX写的UI
但是插件每次reload之后会重新实例化Plugin
这就会重新创建FX Application然后丢异常给我
我想能不能让插件只会实例化一次呢?
然后就有了这个想法和这篇教程
创建一个类做下面加载的类的父类,当然也可以直接用子类 - public class HyperPlugin
- {
- public Plugin plugin;
- public void onLoad() { }
- public void onEnable() { }
- public void onDisable() { }
- }
复制代码这里的Plugin是保存每次加载时候的Plugin实例的,因为每次加载插件插件实例都会改变 因为plugin实例要在调用onEnable之前更新,访问权限不公开的话要用反射改,会拉低效率 当然如果你不考虑效率也可以用反射 反正都能反射改,公开不公开又有什么区别呢233
一会要写的Core的主类要继承这个类,重写这里的三个方法 当然也可以改个名字,或者加一些别的方法 |
|
创建一个用来加载Core的类加载器,可以继承java的URLClassLoader但是要打破双亲委派的规则 - public class HyperPluginClassLoader extends URLClassLoader
- {
- public static final HyperPluginClassLoader LOADER;
- private final Map<String, Class<?>> classes = new ConcurrentHashMap<>();
- private final JarFile jar;
- private final HyperPlugin plugin;
- private Map<String, Class<?>> parentClasses = new HashMap<>();
- private HyperPluginClassLoader(File pluginFile) throws IOException
- {
- super(new URL[]{pluginFile.toURI().toURL()});
- this.jar = new JarFile(pluginFile);
- HyperPlugin plugin = null;
- try
- {
- Class<?> main = Class.forName("org.mve.hy.Main", true, this);
- plugin = (HyperPlugin) main.newInstance();
- }
- catch (Exception e)
- {
- e.printStackTrace();
- }
- this.plugin = plugin;
- if (this.plugin == null)
- throw new IllegalStateException("Can not load plugin: "+pluginFile);
- this.plugin.onLoad();
- }
- public void onEnable(Plugin plugin)
- {
- this.parentClasses.putAll(this.classes);
- this.plugin.plugin = plugin;
- this.plugin.onEnable();
- }
- public void onDisable()
- {
- this.plugin.onDisable();
- this.plugin.plugin = null;
- }
- @Override
- public Class<?> findClass(String name) throws ClassNotFoundException
- {
- return this.loadClass(name);
- }
- @Override
- public Class<?> loadClass(String name) throws ClassNotFoundException
- {
- Class<?> clazz = this.classes.get(name);
- if (clazz != null) return clazz;
- clazz = this.parentClasses.get(name);
- if (clazz != null) return clazz;
- try
- {
- String path = name.replace('.', '/').concat(".class");
- JarEntry entry = jar.getJarEntry(path);
- if (entry != null)
- {
- InputStream in = this.jar.getInputStream(entry);
- byte[] bytes = HyperPluginClassLoader.toByteArray(in);
- in.close();
- clazz = this.defineClass(name, bytes, 0, bytes.length);
- if (clazz != null)
- {
- this.classes.put(name, clazz);
- this.parentClasses.put(name, clazz);
- }
- }
- }
- catch (Exception ignored) {}
- return clazz != null ? clazz : this.getParent().loadClass(name);
- }
- private static byte[] toByteArray(InputStream in) throws IOException
- {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- int len;
- byte[] b = new byte[1024];
- while ((len = in.read(b)) > -1)
- {
- out.write(b, 0, len);
- }
- out.flush();
- out.close();
- return out.toByteArray();
- }
- static
- {
- HyperPluginClassLoader loader = null;
- try
- {
- File coreFile = new File("plugins/HyperPlugin/Core.jar");
- loader = new HyperPluginClassLoader(coreFile);
- }
- catch (IOException e)
- {
- e.printStackTrace();
- }
- LOADER = loader;
- }
- }
复制代码 这里的loadClass和findClass要自己实现,我把findClass推给了loadClass
我这里用了单例模式,防止反复实例化classloader loadClass一定不能遵循双亲委派的规则,不然会出现ClassNotFoundException之类的奇怪的东西 这里的parentClasses是加载插件的PluginClassLoader的classes 这样可以假装自己加载过的类,父类加载器也加载过 如果其他插件要连接你的插件需要这个东西 在loadClass方法里加载新的类需要把加载的Class实例也put到这个parentClasses里 但是自己保存的classes也是必须的,因为每次重载插件,父类加载器都会变 这样这里的parentClasses也要变,在每次onEnable的时候需要把自己加载过的所有类都put进去 注意: 这里的静态代码块里指定的File一定是之后插件本体的位置 当然你可以改路径和名字但是别忘了改这里 这里的构造方法里加载的类一定是插件本体继承之前写的HyperPlugin的类 |
|
|
现在写的类是让Bukkit来加载的 - public class PluginMain extends JavaPlugin
- {
- @Override
- public void onEnable()
- {
- InputStream pluginIn = this.getResource("HyperPlugin.class");
- InputStream loaderIn = this.getResource("HyperPluginClassLoader.class");
- if (pluginIn == null)
- throw new IllegalStateException(
- "Can not load core",
- new FileNotFoundException("HyperPlugin.class")
- );
- if (loaderIn == null)
- throw new IllegalStateException(
- "Can not load core",
- new FileNotFoundException("HyperPluginClassLoader.class")
- );
- try
- {
- SystemClassLoader.define(IO.toByteArray(pluginIn));
- SystemClassLoader.define(IO.toByteArray(loaderIn));
- }
- catch (Throwable e)
- {
- if (! (e instanceof LinkageError))
- throw new IllegalStateException("Can not load core", e);
- }
- try
- {
- Reflect.setFinalField(
- ClassLoader.class,
- "parent",
- HyperPluginClassLoader.LOADER,
- this.getClass().getClassLoader()
- );
- Reflect.setField(
- HyperPluginClassLoader.class,
- "parentClasses",
- HyperPluginClassLoader.LOADER,
- Reflect.reflectField(
- PluginClassLoader.class,
- "classes"
- )
- );
- }
- catch (NoSuchFieldException | IllegalAccessException e)
- {
- e.printStackTrace();
- }
- HyperPluginClassLoader.LOADER.onEnable(this);
- }
- @Override
- public void onDisable()
- {
- HyperPluginClassLoader.LOADER.onDisable();
- }
- }
复制代码 |
|
之前写的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你的插件本体啦 |
|
|
这里写的插件本体在服务器运行的过程里只会调用一次onLoad方法 虽然不知道有什么用,但是之前写fx的时候发现的这个方法就来水一贴了哈哈哈哈
本体的主类要继承之前写的HyperPlugin而不是JavaPlugin - public class Main extends HyperPlugin
- {
- @Override
- public void onLoad()
- {
- System.out.println("Plugin load");
- System.out.println(this.getClass().getClassLoader());
- }
- @Override
- public void onEnable()
- {
- System.out.println("Plugin enable");
- System.out.println(this.getClass().getClassLoader());
- }
- @Override
- public void onDisable()
- {
- System.out.println("Plugin disable");
- System.out.println(this.getClass().getClassLoader());
- }
- }
复制代码可以输出一下类加载器看看前后的类是不是唯一 |
|
|
第二节的Plugin编译的jar放在plugins文件夹里 第三节Core编译的jar放在之前类加载器指定的目录里 我指定的是plugins/HyperPlugin/Core.jar 当然你也可以改但是别忘记改类加载器里的路径
onLoad->onEnable->reload->onDisable->onEnable onLoad后面的onEnable我没截图,反正都一样
这里输出的内存地址都一样说明插件在reload前后实例没有变 成功啦!嗷呜~ |
|
|
插件源码
这里IO工具和Reflect工具我也和源码一起丢出来了可以扒源码自己看
如果有什么不明白的地方可以来问我啦 嗷呜~
又是一篇不会有人看的没什么用的教程呢
|