diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManager.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManager.kt index 65a4a3175..6f2e602a0 100644 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManager.kt +++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginManager.kt @@ -22,8 +22,8 @@ import java.io.File import java.io.InputStream import java.lang.reflect.Constructor import java.lang.reflect.Method +import java.net.JarURLConnection import java.net.URL -import java.net.URLClassLoader import java.util.jar.JarFile @@ -40,6 +40,7 @@ object PluginManager { //已完成加载的 private val nameToPluginBaseMap: MutableMap = mutableMapOf() private val pluginDescriptions: MutableMap = mutableMapOf() + private val pluginsClassLoader: PluginsClassLoader = PluginsClassLoader(this.javaClass.classLoader) internal fun onCommand(command: Command, sender: CommandSender, args: List) { nameToPluginBaseMap.values.forEach { @@ -83,15 +84,20 @@ object PluginManager { val jar = JarFile(file) val pluginYml = jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull() + if (pluginYml == null) { logger.info("plugin.yml not found in jar " + jar.name + ", it will not be consider as a Plugin") } else { try { - val description = - PluginDescription.readFromContent( - URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().inputStream.use { - it.readBytes().encodeToString() - }) + val description = PluginDescription.readFromContent( + URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().let { + val res = it.inputStream.use { input -> + input.readBytes().encodeToString() + } + // 关闭jarFile,解决热更新插件问题 + (it as JarURLConnection).jarFile.close() + res + }) pluginsFound[description.name] = description pluginsLocation[description.name] = file } catch (e: Exception) { @@ -101,8 +107,6 @@ object PluginManager { } } - val pluginsClassLoader = PluginsClassLoader(pluginsLocation.values,this.javaClass.classLoader) - //不仅要解决A->B->C->A, 还要解决A->B->C->A fun checkNoCircularDepends( target: PluginDescription, @@ -133,6 +137,9 @@ object PluginManager { checkNoCircularDepends(it, it.depends, mutableListOf()) } + //插件加载器导入插件jar + pluginsClassLoader.loadPlugins(pluginsLocation) + //load plugin fun loadPlugin(description: PluginDescription): Boolean { if (!description.noCircularDepend) { @@ -167,7 +174,7 @@ object PluginManager { } return try { - val subClass = pluginClass.asSubclass(PluginBase::class.java) + val subClass = pluginClass!!.asSubclass(PluginBase::class.java) lastPluginName = description.name val plugin: PluginBase = @@ -240,6 +247,7 @@ object PluginManager { plugin.disable(exception) nameToPluginBaseMap.remove(plugin.pluginName) pluginDescriptions.remove(plugin.pluginName) + pluginsClassLoader.remove(plugin.pluginName) } @@ -251,12 +259,14 @@ object PluginManager { } nameToPluginBaseMap.clear() pluginDescriptions.clear() + pluginsClassLoader.clear() } /** * 根据插件名字找Jar的文件 * null => 没找到 + * 这里的url的jarFile没关,热更新插件可能出事 */ fun getJarFileByName(pluginName: String): File? { File(pluginsPath).listFiles()?.forEach { file -> @@ -282,6 +292,7 @@ object PluginManager { /** * 根据插件名字找Jar中的文件 * null => 没找到 + * 这里的url的jarFile没关,热更新插件可能出事 */ fun getFileInJarByName(pluginName: String, toFind: String): InputStream? { val jarFile = getJarFileByName(pluginName) ?: return null @@ -308,4 +319,3 @@ private fun Constructor.againstPermission() { } } -internal class PluginsClassLoader(files: Collection, parent: ClassLoader) : URLClassLoader(files.map{it.toURI().toURL()}.toTypedArray(), parent) diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginsClassLoader.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginsClassLoader.kt new file mode 100644 index 000000000..e7257f91e --- /dev/null +++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugins/PluginsClassLoader.kt @@ -0,0 +1,105 @@ +package net.mamoe.mirai.console.plugins + +import java.io.File +import java.net.URLClassLoader + +internal class PluginsClassLoader(parent: ClassLoader) : ClassLoader(parent) { + private val pluginLoaders = mutableMapOf() + + /** + * 加载多个插件 + */ + fun loadPlugins(pluginsLocation: Map) { + for ((key, value) in pluginsLocation) { + pluginLoaders[key] = PluginClassLoader(value, this) + } + } + + + /** + * 清除所有插件加载器 + */ + fun clear() { + pluginLoaders.values.forEach { + it.close() + } + pluginLoaders.clear() + } + + /** + * 移除单个插件加载器 + */ + fun remove(pluginName: String): Boolean { + pluginLoaders[pluginName]?.close() ?: return false + pluginLoaders.remove(pluginName) + return true + } + + override fun loadClass(name: String): Class<*>? { + var c: Class<*>? = null + // 循环插件classloader loadClass + pluginLoaders.values.forEach { + it.runCatching { + c = this.loadClass(name) + return@forEach + } + } + // 如果为null,交给mirai的classloader进行加载 + if (c == null) { + c = parent.loadClass(name) // 如果无法加载这个类,这里会抛异常 + } + return c + } + + fun loadDependClass(name: String): Class<*>? { + var c: Class<*>? = null + // 依赖问题先交给mirai ClassLoader来处理 + runCatching { + c = parent.loadClass(name) + } + // 如果mirai加载不了依赖则交给插件的classloader进行加载 + if (c == null) { + pluginLoaders.values.forEach { + it.runCatching { + c = this.loadDependClass(name) + return@forEach + } + } + } + return c + } +} + +internal class PluginClassLoader(files: File, parent: PluginsClassLoader?) : + URLClassLoader(arrayOf((files.toURI().toURL())), parent) { + + override fun loadClass(name: String): Class<*>? { + synchronized(getClassLoadingLock(name)) { + // 看缓存中是否加载过此类 + var c = findLoadedClass(name) + if (c == null) { + c = try { + // 自己尝试加载 + this.findClass(name) //ClassNotFoundException + } catch (e: ClassNotFoundException) { + // 交给父类去加载非本插件的依赖 + (this.parent as PluginsClassLoader).loadDependClass(name) + } + } + return c + } + } + + fun loadDependClass(name: String): Class<*>? { + synchronized(getClassLoadingLock(name)) { + var c = findLoadedClass(name) + if (c == null) { + // 加载依赖类,没有则丢出异常 + c = this.findClass(name) // ClassNotFoundException + } + return c + } + } + +} +