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 882f643c7..8571613db 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 @@ -48,9 +48,9 @@ object PluginManager { private val pluginDescriptions: MutableMap = mutableMapOf() /** - * 加载插件的ClassLoader + * 加载插件的PluginsLoader */ - private val pluginsClassLoader: PluginsClassLoader = PluginsClassLoader(this.javaClass.classLoader) + private val pluginsLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader) /** * 插件优先级队列 @@ -181,9 +181,6 @@ object PluginManager { checkNoCircularDepends(it, it.depends, mutableListOf()) } - //插件加载器导入插件jar - pluginsClassLoader.loadPlugins(pluginsLocation) - //load plugin individually fun loadPlugin(description: PluginDescription): Boolean { if (!description.noCircularDepend) { @@ -210,41 +207,31 @@ object PluginManager { logger.info("loading plugin " + description.name) - try { - val pluginClass = try{ - pluginsClassLoader.loadPluginMainClass(description.basePath) - } catch (e: ClassNotFoundException) { - pluginsClassLoader.loadPluginMainClass("${description.basePath}Kt") - } - - return try { - val subClass = pluginClass!!.asSubclass(PluginBase::class.java) - - lastPluginName = description.name - val plugin: PluginBase = - subClass.kotlin.objectInstance ?: subClass.getDeclaredConstructor().apply { - againstPermission() - }.newInstance() - plugin.dataFolder // initialize right now - - description.loaded = true - logger.info("successfully loaded plugin " + description.name + " version " + description.version + " by " + description.author) - logger.info(description.info) - - nameToPluginBaseMap[description.name] = plugin - pluginDescriptions[description.name] = description - plugin.pluginName = description.name - pluginsSequence.add(plugin)//按照实际加载顺序加入队列 - true - } catch (e: ClassCastException) { - logger.error("failed to load plugin " + description.name + " , Main class does not extends PluginBase ") - false - } + val jarFile = pluginsLocation[description.name]!! + val pluginClass = try{ + pluginsLoader.loadPluginMainClassByJarFile(description.name,description.basePath,jarFile) } catch (e: ClassNotFoundException) { - logger.error("failed to load plugin " + description.name + " , Main class not found under " + description.basePath) - logger.error(e) - return false + pluginsLoader.loadPluginMainClassByJarFile(description.name,"${description.basePath}Kt",jarFile) } + + val subClass = pluginClass.asSubclass(PluginBase::class.java) + + lastPluginName = description.name + val plugin: PluginBase = + subClass.kotlin.objectInstance ?: subClass.getDeclaredConstructor().apply { + againstPermission() + }.newInstance() + plugin.dataFolder // initialize right now + + description.loaded = true + logger.info("successfully loaded plugin " + description.name + " version " + description.version + " by " + description.author) + logger.info(description.info) + + nameToPluginBaseMap[description.name] = plugin + pluginDescriptions[description.name] = description + plugin.pluginName = description.name + pluginsSequence.add(plugin)//按照实际加载顺序加入队列 + return true } @@ -252,7 +239,18 @@ object PluginManager { pluginsSequence.clear() pluginsFound.values.forEach { - loadPlugin(it) + try{ + // 尝试加载插件 + loadPlugin(it) + }catch (e: Throwable) { + pluginsLoader.remove(it.name) + when(e){ + is ClassCastException -> logger.error("failed to load plugin " + it.name + " , Main class does not extends PluginBase",e) + is ClassNotFoundException -> logger.error("failed to load plugin " + it.name + " , Main class not found under " + it.basePath,e) + is NoClassDefFoundError -> logger.error("failed to load plugin " + it.name + " , dependent class not found.",e) + else -> logger.error("failed to load plugin " + it.name,e) + } + } } @@ -297,7 +295,7 @@ object PluginManager { plugin.disable(exception) nameToPluginBaseMap.remove(plugin.pluginName) pluginDescriptions.remove(plugin.pluginName) - //pluginsClassLoader.remove(plugin.pluginName) + pluginsLoader.remove(plugin.pluginName) pluginsSequence.remove(plugin) } @@ -310,7 +308,7 @@ object PluginManager { } nameToPluginBaseMap.clear() pluginDescriptions.clear() - pluginsClassLoader.clear() + pluginsLoader.clear() pluginsSequence.clear() } 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 index 047ad0691..3b9ae81b5 100644 --- 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 @@ -1,33 +1,128 @@ package net.mamoe.mirai.console.plugins +import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.utils.SimpleLogger import java.io.File +import java.io.IOException import java.net.URLClassLoader -internal class PluginsClassLoader(private val parent: ClassLoader) { - - private var cl : URLClassLoader? = null - - /** - * 加载多个插件 - */ - fun loadPlugins(pluginsLocation: Map) { - cl = URLClassLoader( - pluginsLocation.values.map { it.toURI().toURL() }.toTypedArray(), - parent) +internal class PluginsLoader(private val parentClassLoader: ClassLoader) { + private val loggerName = "PluginsLoader" + private val pluginLoaders = linkedMapOf() + private val classesCache = mutableMapOf>() + private val logger = SimpleLogger(loggerName) { p, message, e -> + MiraiConsole.logger(p, "[${loggerName}]", 0, message) + MiraiConsole.logger(p, "[${loggerName}]", 0, e) } /** * 清除所有插件加载器 */ fun clear() { - cl?.close() + val iterator = pluginLoaders.iterator() + while(iterator.hasNext()){ + val plugin = iterator.next() + var cl = "" + try { + cl = plugin.value.toString() + plugin.value.close() + iterator.remove() + }catch (e: Throwable){ + logger.error("Plugin(${plugin.key}) can't not close its ClassLoader(${cl})",e) + } + } + classesCache.clear() } /** - * 加载 + * 移除单个插件加载器 */ + fun remove(pluginName: String): Boolean { + pluginLoaders[pluginName]?.close() ?: return false + pluginLoaders.remove(pluginName) + return true + } - fun loadPluginMainClass(name: String) : Class<*>{ - return cl?.loadClass(name) ?: error("PluginsClassLoader has not yet run the loadPlugins func.") + fun loadPluginMainClassByJarFile(pluginName:String, mainClass: String, jarFile:File): Class<*> { + try { + if(!pluginLoaders.containsKey(pluginName)){ + pluginLoaders[pluginName] = PluginClassLoader(pluginName,jarFile, this, parentClassLoader) + } + return Class.forName(mainClass,true,pluginLoaders[pluginName]) + }catch (e : ClassNotFoundException){ + throw ClassNotFoundException("PluginsClassLoader(${pluginName}) can't load this pluginMainClass:${mainClass}",e) + }catch (e : Throwable){ + throw Throwable("init or load class error",e) + } + } + + /** + * 尝试加载插件的依赖,无则返回null + */ + fun loadDependentClass(name: String): Class<*>? { + var c: Class<*>? = null + // 尝试从缓存中读取 + if (classesCache.containsKey(name)) { + c = classesCache[name] + } + // 然后再交给插件的classloader来加载依赖 + if (c == null) { + pluginLoaders.values.forEach { + try { + c = it.findClass(name, false) + return@forEach + } catch (e: ClassNotFoundException) {/*nothing*/ + } + } + } + return c + } + + fun addClassCache(name: String, clz: Class<*>) { + synchronized(classesCache) { + if (!classesCache.containsKey(name)) { + classesCache[name] = clz + } + } } } + +internal class PluginClassLoader(private val pluginName: String,files: File, private val pluginsLoader: PluginsLoader, parent: ClassLoader) : + URLClassLoader(arrayOf((files.toURI().toURL())), parent) { + private val classesCache = mutableMapOf?>() + + override fun findClass(name: String): Class<*>? { + return this.findClass(name, true) + } + + fun findClass(name: String, isSearchDependent: Boolean): Class<*>? { + var clz: Class<*>? = null + // 缓存中找 + if (classesCache.containsKey(name)) { + + return classesCache[name] + } + // 是否寻找依赖 + if (isSearchDependent) { + clz = pluginsLoader.loadDependentClass(name) + } + // 交给super去findClass + if (clz == null) { + clz = super.findClass(name) + } + // 加入缓存 + if (clz != null) { + pluginsLoader.addClassCache(name, clz) + } + // 加入缓存 + synchronized(classesCache) { + classesCache[name] = clz + } + return clz + } + + override fun close() { + super.close() + classesCache.clear() + } +} \ No newline at end of file