mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-25 15:40:28 +08:00
Merge pull request #29 from Sincky/master
add PluginsClassLoader and fix hot-reload from Sincky
This commit is contained in:
commit
ceeb0c9c10
@ -22,8 +22,8 @@ import java.io.File
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.lang.reflect.Constructor
|
import java.lang.reflect.Constructor
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
|
import java.net.JarURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLClassLoader
|
|
||||||
import java.util.jar.JarFile
|
import java.util.jar.JarFile
|
||||||
|
|
||||||
|
|
||||||
@ -40,6 +40,7 @@ object PluginManager {
|
|||||||
//已完成加载的
|
//已完成加载的
|
||||||
private val nameToPluginBaseMap: MutableMap<String, PluginBase> = mutableMapOf()
|
private val nameToPluginBaseMap: MutableMap<String, PluginBase> = mutableMapOf()
|
||||||
private val pluginDescriptions: MutableMap<String, PluginDescription> = mutableMapOf()
|
private val pluginDescriptions: MutableMap<String, PluginDescription> = mutableMapOf()
|
||||||
|
private val pluginsClassLoader: PluginsClassLoader = PluginsClassLoader(this.javaClass.classLoader)
|
||||||
|
|
||||||
internal fun onCommand(command: Command, sender: CommandSender, args: List<String>) {
|
internal fun onCommand(command: Command, sender: CommandSender, args: List<String>) {
|
||||||
nameToPluginBaseMap.values.forEach {
|
nameToPluginBaseMap.values.forEach {
|
||||||
@ -83,14 +84,19 @@ object PluginManager {
|
|||||||
val jar = JarFile(file)
|
val jar = JarFile(file)
|
||||||
val pluginYml =
|
val pluginYml =
|
||||||
jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull()
|
jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull()
|
||||||
|
|
||||||
if (pluginYml == null) {
|
if (pluginYml == null) {
|
||||||
logger.info("plugin.yml not found in jar " + jar.name + ", it will not be consider as a Plugin")
|
logger.info("plugin.yml not found in jar " + jar.name + ", it will not be consider as a Plugin")
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
val description =
|
val description = PluginDescription.readFromContent(
|
||||||
PluginDescription.readFromContent(
|
URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().let {
|
||||||
URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().inputStream.use {
|
val res = it.inputStream.use { input ->
|
||||||
it.readBytes().encodeToString()
|
input.readBytes().encodeToString()
|
||||||
|
}
|
||||||
|
// 关闭jarFile,解决热更新插件问题
|
||||||
|
(it as JarURLConnection).jarFile.close()
|
||||||
|
res
|
||||||
})
|
})
|
||||||
pluginsFound[description.name] = description
|
pluginsFound[description.name] = description
|
||||||
pluginsLocation[description.name] = file
|
pluginsLocation[description.name] = file
|
||||||
@ -101,8 +107,6 @@ object PluginManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val pluginsClassLoader = PluginsClassLoader(pluginsLocation.values,this.javaClass.classLoader)
|
|
||||||
|
|
||||||
//不仅要解决A->B->C->A, 还要解决A->B->C->A
|
//不仅要解决A->B->C->A, 还要解决A->B->C->A
|
||||||
fun checkNoCircularDepends(
|
fun checkNoCircularDepends(
|
||||||
target: PluginDescription,
|
target: PluginDescription,
|
||||||
@ -133,6 +137,9 @@ object PluginManager {
|
|||||||
checkNoCircularDepends(it, it.depends, mutableListOf())
|
checkNoCircularDepends(it, it.depends, mutableListOf())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//插件加载器导入插件jar
|
||||||
|
pluginsClassLoader.loadPlugins(pluginsLocation)
|
||||||
|
|
||||||
//load plugin
|
//load plugin
|
||||||
fun loadPlugin(description: PluginDescription): Boolean {
|
fun loadPlugin(description: PluginDescription): Boolean {
|
||||||
if (!description.noCircularDepend) {
|
if (!description.noCircularDepend) {
|
||||||
@ -167,7 +174,7 @@ object PluginManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val subClass = pluginClass.asSubclass(PluginBase::class.java)
|
val subClass = pluginClass!!.asSubclass(PluginBase::class.java)
|
||||||
|
|
||||||
lastPluginName = description.name
|
lastPluginName = description.name
|
||||||
val plugin: PluginBase =
|
val plugin: PluginBase =
|
||||||
@ -240,6 +247,7 @@ object PluginManager {
|
|||||||
plugin.disable(exception)
|
plugin.disable(exception)
|
||||||
nameToPluginBaseMap.remove(plugin.pluginName)
|
nameToPluginBaseMap.remove(plugin.pluginName)
|
||||||
pluginDescriptions.remove(plugin.pluginName)
|
pluginDescriptions.remove(plugin.pluginName)
|
||||||
|
pluginsClassLoader.remove(plugin.pluginName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -251,12 +259,14 @@ object PluginManager {
|
|||||||
}
|
}
|
||||||
nameToPluginBaseMap.clear()
|
nameToPluginBaseMap.clear()
|
||||||
pluginDescriptions.clear()
|
pluginDescriptions.clear()
|
||||||
|
pluginsClassLoader.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据插件名字找Jar的文件
|
* 根据插件名字找Jar的文件
|
||||||
* null => 没找到
|
* null => 没找到
|
||||||
|
* 这里的url的jarFile没关,热更新插件可能出事
|
||||||
*/
|
*/
|
||||||
fun getJarFileByName(pluginName: String): File? {
|
fun getJarFileByName(pluginName: String): File? {
|
||||||
File(pluginsPath).listFiles()?.forEach { file ->
|
File(pluginsPath).listFiles()?.forEach { file ->
|
||||||
@ -282,6 +292,7 @@ object PluginManager {
|
|||||||
/**
|
/**
|
||||||
* 根据插件名字找Jar中的文件
|
* 根据插件名字找Jar中的文件
|
||||||
* null => 没找到
|
* null => 没找到
|
||||||
|
* 这里的url的jarFile没关,热更新插件可能出事
|
||||||
*/
|
*/
|
||||||
fun getFileInJarByName(pluginName: String, toFind: String): InputStream? {
|
fun getFileInJarByName(pluginName: String, toFind: String): InputStream? {
|
||||||
val jarFile = getJarFileByName(pluginName) ?: return null
|
val jarFile = getJarFileByName(pluginName) ?: return null
|
||||||
@ -308,4 +319,3 @@ private fun Constructor<out PluginBase>.againstPermission() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class PluginsClassLoader(files: Collection<File>, parent: ClassLoader) : URLClassLoader(files.map{it.toURI().toURL()}.toTypedArray(), parent)
|
|
||||||
|
@ -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<String, PluginClassLoader>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载多个插件
|
||||||
|
*/
|
||||||
|
fun loadPlugins(pluginsLocation: Map<String, File>) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user