diff --git a/mirai-console/backend/integration-test/testers/service-loader/service-loader-2dep-plugin/src/PMain.kt b/mirai-console/backend/integration-test/testers/service-loader/service-loader-2dep-plugin/src/PMain.kt index 87797b8d6..457b86755 100644 --- a/mirai-console/backend/integration-test/testers/service-loader/service-loader-2dep-plugin/src/PMain.kt +++ b/mirai-console/backend/integration-test/testers/service-loader/service-loader-2dep-plugin/src/PMain.kt @@ -17,6 +17,7 @@ import java.util.* import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull +import kotlin.test.assertTrue internal class PS : ServiceTypedef @@ -64,7 +65,45 @@ internal object PMain : KotlinPlugin(JvmPluginDescription("net.mamoe.console.ite println(it) }.size ) - assertNull(javaClass.getResource("/net/mamoe/mirai/console/MiraiConsole.class")) - assertNull(javaClass.getResource("/net/mamoe/mirai/Bot.class")) + + // ************************* resources loading tests ************************* + + val miraiConsoleClassPath = "net/mamoe/mirai/console/MiraiConsole.class" + val miraiBotClassPath = "net/mamoe/mirai/Bot.class" + val allClassesPath = "META-INF/mirai-console/allclasses.txt" + + fun ClassLoader.getSizeOfResources(path: String): Int { + return this.getResources(path).toList().size + } + + assertNull(javaClass.getResource("/$miraiConsoleClassPath")) + assertNull(javaClass.classLoader.getResource(miraiConsoleClassPath)) + val miraiConsoleClassResourceCount = javaClass.classLoader.getSizeOfResources(miraiConsoleClassPath) + .also { assertEquals(0, it) } + + assertNull(javaClass.getResource("/$miraiBotClassPath")) + assertNull(javaClass.classLoader.getResource(miraiBotClassPath)) + + val allClassesResourceCount = javaClass.classLoader.getSizeOfResources(allClassesPath) + .also { assertEquals(0, it) } + + jvmPluginClasspath.shouldResolveConsoleSystemResource = true + + assertNotNull(javaClass.classLoader.getResource(miraiConsoleClassPath)) + assertNotNull(javaClass.classLoader.getResource(miraiBotClassPath)) + + assertTrue(javaClass.classLoader.getSizeOfResources(miraiConsoleClassPath) > miraiConsoleClassResourceCount) + assertTrue(javaClass.classLoader.getSizeOfResources(allClassesPath) > allClassesResourceCount) + + jvmPluginClasspath.shouldResolveConsoleSystemResource = false + + assertNull(javaClass.getResource("/$miraiConsoleClassPath")) + assertNull(javaClass.classLoader.getResource(miraiConsoleClassPath)) + assertEquals(0, javaClass.classLoader.getSizeOfResources(miraiConsoleClassPath)) + + assertNull(javaClass.getResource("/$miraiBotClassPath")) + assertNull(javaClass.classLoader.getResource(miraiBotClassPath)) + + assertEquals(0, javaClass.classLoader.getSizeOfResources(allClassesPath)) } } \ No newline at end of file diff --git a/mirai-console/backend/mirai-console/compatibility-validation/jvm/api/jvm.api b/mirai-console/backend/mirai-console/compatibility-validation/jvm/api/jvm.api index 0bf9c5f36..846472f7b 100644 --- a/mirai-console/backend/mirai-console/compatibility-validation/jvm/api/jvm.api +++ b/mirai-console/backend/mirai-console/compatibility-validation/jvm/api/jvm.api @@ -2023,6 +2023,8 @@ public abstract interface class net/mamoe/mirai/console/plugin/jvm/JvmPluginClas public abstract fun getPluginFile ()Ljava/io/File; public abstract fun getPluginIndependentLibrariesClassLoader ()Ljava/lang/ClassLoader; public abstract fun getPluginSharedLibrariesClassLoader ()Ljava/lang/ClassLoader; + public abstract fun getShouldResolveConsoleSystemResource ()Z + public abstract fun setShouldResolveConsoleSystemResource (Z)V } public abstract interface class net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription : net/mamoe/mirai/console/plugin/description/PluginDescription { diff --git a/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt b/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt index a68eb299d..f03f5d8e2 100644 --- a/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt +++ b/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt @@ -22,6 +22,7 @@ import java.net.URLClassLoader import java.util.* import java.util.concurrent.atomic.AtomicBoolean import java.util.zip.ZipFile +import kotlin.collections.LinkedHashSet /* Class resolving: @@ -146,6 +147,29 @@ internal class DynLibClassLoader : DynamicClasspathClassLoader { } return null } + + fun tryFastOrStrictResolveResources(name: String): Enumeration { + if (name.startsWith("java/")) return JavaSystemPlatformClassLoader.getResources(name) + + // All mirai-core hard-linked should use same version to avoid errors (ClassCastException). + val fromDependencies = AllDependenciesClassesHolder.appClassLoader.getResources(name) + + return if ( + name.startsWith("net/mamoe/mirai/") + || name.startsWith("kotlin/") + || name.startsWith("kotlinx/") + || name.startsWith("org/slf4j/") + ) { // Avoid plugin classing cheating + fromDependencies + } else { + LinkedHashSet().apply { + addAll(fromDependencies) + addAll(JavaSystemPlatformClassLoader.getResources(name)) + }.let { + Collections.enumeration(it) + } + } + } } internal fun loadClassInThisClassLoader(name: String): Class<*>? { @@ -458,13 +482,12 @@ internal class JvmPluginClassLoaderN : URLClassLoader { } src.add(pluginIndependentCL.getResources(name)) - val resolved = mutableListOf() - src.forEach { nested -> - nested.iterator().forEach { url -> - if (url !in resolved) - resolved.add(url) - } + val resolved = LinkedHashSet() + + if (openaccess.shouldResolveConsoleSystemResource) { + DynLibClassLoader.tryFastOrStrictResolveResources(name).let { resolved.addAll(it) } } + src.forEach { nested -> resolved.addAll(nested) } return Collections.enumeration(resolved) } @@ -489,6 +512,12 @@ internal class JvmPluginClassLoaderN : URLClassLoader { if (name.startsWith("META-INF/services/net.mamoe.mirai.console.plugin.")) return findResource(name) + if (openaccess.shouldResolveConsoleSystemResource) { + DynLibClassLoader.tryFastOrStrictResolveResources(name) + .takeIf { it.hasMoreElements() } + ?.let { return it.nextElement() } + } + findResource(name)?.let { return it } // parent: ctx.sharedLibrariesLoader sharedLibrariesLogger.getResource(name)?.let { return it } @@ -514,6 +543,8 @@ internal class JvmPluginClassLoaderN : URLClassLoader { override val pluginIndependentLibrariesClassLoader: ClassLoader get() = pluginIndependentCL + override var shouldResolveConsoleSystemResource: Boolean = false + private val permitted by lazy { arrayOf( this@JvmPluginClassLoaderN, diff --git a/mirai-console/backend/mirai-console/src/plugin/jvm/JvmPluginClasspath.kt b/mirai-console/backend/mirai-console/src/plugin/jvm/JvmPluginClasspath.kt index 3ac7ad859..713062903 100644 --- a/mirai-console/backend/mirai-console/src/plugin/jvm/JvmPluginClasspath.kt +++ b/mirai-console/backend/mirai-console/src/plugin/jvm/JvmPluginClasspath.kt @@ -41,6 +41,13 @@ public interface JvmPluginClasspath { */ public val pluginIndependentLibrariesClassLoader: ClassLoader + /** + * [pluginClassLoader] 是否可以通过 [ClassLoader.getResource] 获取 Mirai Console (包括依赖) 的相关资源 + * + * 默认为 `false` + */ + public var shouldResolveConsoleSystemResource: Boolean + /** * 将 [file] 加入 [classLoader] 的搜索路径内 * diff --git a/mirai-core-utils/src/jvmBaseMain/kotlin/Collections.kt b/mirai-core-utils/src/jvmBaseMain/kotlin/Collections.kt index 9be23ec12..0427c4373 100644 --- a/mirai-core-utils/src/jvmBaseMain/kotlin/Collections.kt +++ b/mirai-core-utils/src/jvmBaseMain/kotlin/Collections.kt @@ -54,4 +54,18 @@ public actual fun , V> EnumMap(clazz: KClass): MutableMap { @Suppress("FunctionName") public actual fun ConcurrentSet(): MutableSet { return CopyOnWriteArraySet() +} + +/** + * Same as [MutableCollection.addAll]. + * + * Adds all the elements of the specified enumeration to this collection. + * @return true if any of the specified elements was added to the collection, false if the collection was not modified. + */ +public fun MutableCollection.addAll(enumeration: Enumeration): Boolean { + var addResult = false + while (enumeration.hasMoreElements()) { + addResult = this.add(enumeration.nextElement()) + } + return addResult } \ No newline at end of file