Resources & ServiceLoader resolving

This commit is contained in:
Karlatemp 2022-03-11 18:16:20 +08:00
parent 94828544cb
commit d6f4e2e567
No known key found for this signature in database
GPG Key ID: C6B606FF23D8FED7
15 changed files with 300 additions and 7 deletions

View File

@ -12,6 +12,7 @@ package net.mamoe.console.integrationtest.ep.dependonother
import net.mamoe.console.integrationtest.ep.mcitselftest.MCITSelfTestPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
import net.mamoe.mirai.utils.error
import net.mamoe.mirai.utils.info
import kotlin.test.assertFailsWith
import kotlin.test.assertNotEquals
@ -45,7 +46,8 @@ public object PluginDependOnOther : KotlinPlugin(
logger.info { "Gson located $gsonC <${gsonC.classLoader}>" }
assertSame(gsonC, Class.forName(gsonC.name, false, pluginDepDynDownload.classLoader))
assertFailsWith<ClassNotFoundException> {
Class.forName("com.zaxxer.sparsebits.SparseBitSet") // private in dynamic-dep-download
val c = Class.forName("com.zaxxer.sparsebits.SparseBitSet") // private in dynamic-dep-download
logger.error { "C: $c, from: ${c.classLoader}" }
}
assertFailsWith<ClassNotFoundException> {
Class.forName("net.mamoe.assertion.something.not.existing")

View File

@ -0,0 +1,27 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("UnusedImport")
plugins {
kotlin("jvm")
kotlin("plugin.serialization")
id("java")
}
version = "0.0.0"
kotlin {
explicitApiWarning()
}
dependencies {
api(project(":mirai-console.integration-test"))
api(parent!!.project("module-service-loader-typedef"))
}

View File

@ -0,0 +1,10 @@
#
# Copyright 2019-2022 Mamoe Technologies and contributors.
#
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
#
# https://github.com/mamoe/mirai/blob/dev/LICENSE
#
net.mamoe.console.integrationtest.mod.serviceimpl.ServiceImpl

View File

@ -0,0 +1,14 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.console.integrationtest.mod.serviceimpl
import net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef
public class ServiceImpl : ServiceTypedef {
}

View File

@ -0,0 +1,13 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.console.integrationtest.mod.servicetypedef
public interface ServiceTypedef {
}

View File

@ -0,0 +1,10 @@
#
# Copyright 2019-2022 Mamoe Technologies and contributors.
#
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
#
# https://github.com/mamoe/mirai/blob/dev/LICENSE
#
net.mamoe.console.itest.serviceloader.PMain

View File

@ -0,0 +1,27 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("UnusedImport")
plugins {
kotlin("jvm")
kotlin("plugin.serialization")
id("java")
}
version = "0.0.0"
kotlin {
explicitApiWarning()
}
dependencies {
api(project(":mirai-console.integration-test"))
api(parent!!.project("module-service-loader-typedef"))
}

View File

@ -0,0 +1,10 @@
#
# Copyright 2019-2022 Mamoe Technologies and contributors.
#
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
#
# https://github.com/mamoe/mirai/blob/dev/LICENSE
#
net.mamoe.console.itest.serviceloader.ndep.PS

View File

@ -0,0 +1,10 @@
#
# Copyright 2019-2022 Mamoe Technologies and contributors.
#
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
#
# https://github.com/mamoe/mirai/blob/dev/LICENSE
#
net.mamoe.console.itest.serviceloader.ndep.PMain

View File

@ -0,0 +1,40 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.console.itest.serviceloader.ndep
import net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
import net.mamoe.mirai.utils.info
import java.util.*
import kotlin.test.assertEquals
internal class PS : ServiceTypedef
internal object PMain : KotlinPlugin(JvmPluginDescription("net.mamoe.console.itest.serviceloader-ndp", "0.0.0") {
dependsOn("net.mamoe.console.itest.serviceloader")
}) {
override fun onEnable() {
val loader = ServiceLoader.load(
Class.forName("net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef"),
javaClass.classLoader,
)
val services = loader.asSequence().map { it.javaClass.name }.toMutableList()
services.sort()
services.forEach { service ->
logger.info { "Service: $service" }
}
assertEquals(mutableListOf(
"net.mamoe.console.integrationtest.mod.serviceimpl.ServiceImpl",
"net.mamoe.console.itest.serviceloader.ndep.PS",
), services)
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package net.mamoe.console.itest.serviceloader
import net.mamoe.mirai.console.internal.plugin.DynLibClassLoader
import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription
import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin
import net.mamoe.mirai.utils.info
import java.io.File
import java.util.*
import kotlin.test.assertEquals
internal object PMain : KotlinPlugin(JvmPluginDescription("net.mamoe.console.itest.serviceloader", "0.0.0")) {
init {
val cl = PMain.javaClass.classLoader.parent as DynLibClassLoader
cl.addLib(File("modules/module-service-loader-typedef-0.0.0.jar"))
cl.addLib(File("modules/module-service-loader-impl-0.0.0.jar"))
}
override fun onEnable() {
val loader = ServiceLoader.load(
Class.forName("net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef"),
javaClass.classLoader,
)
val services = loader.asSequence().map { it.javaClass.name}.toMutableList()
services.forEach { service ->
logger.info { "Service: $service" }
}
assertEquals(mutableListOf("net.mamoe.console.integrationtest.mod.serviceimpl.ServiceImpl"), services)
}
}

View File

@ -82,6 +82,26 @@ internal class DynLibClassLoader(
clName?.let { return "DynLibClassLoader{$it}" }
return "DynLibClassLoader@" + hashCode()
}
override fun getResource(name: String?): URL? {
if (name == null) return null
findResource(name)?.let { return it }
if (parent is DynLibClassLoader) {
return parent.getResource(name)
}
return null
}
override fun getResources(name: String?): Enumeration<URL> {
if (name == null) return Collections.emptyEnumeration()
val res = findResources(name)
return if (parent is DynLibClassLoader) {
res + parent.getResources(name)
} else {
res
}
}
}
@Suppress("JoinDeclarationAndAssignment")
@ -210,7 +230,8 @@ internal class JvmPluginClassLoaderN : URLClassLoader {
findLoadedClass(name)?.let { return it }
try {
return super.findClass(name)
} catch (ignored: ClassNotFoundException) {}
} catch (ignored: ClassNotFoundException) {
}
}
}
return null
@ -265,11 +286,48 @@ internal class JvmPluginClassLoaderN : URLClassLoader {
internal fun loadedClass(name: String): Class<*>? = super.findLoadedClass(name)
//// 只允许插件 getResource 时获取插件自身资源, https://github.com/mamoe/mirai-console/issues/205
override fun getResources(name: String?): Enumeration<URL> = findResources(name)
override fun getResource(name: String?): URL? = findResource(name)
// getResourceAsStream 在 URLClassLoader 中通过 getResource 确定资源
// 因此无需 override getResourceAsStream
private fun getRes(name: String, shared: Boolean): Enumeration<URL> {
val src = mutableListOf<Enumeration<URL>>(
findResources(name),
)
if (dependencies.isEmpty()) {
if (shared) {
src.add(sharedLibrariesLogger.getResources(name))
}
} else {
dependencies.forEach { dep ->
src.add(dep.getRes(name, false))
}
}
src.add(pluginIndependentCL.getResources(name))
val resolved = mutableSetOf<URL>()
src.forEach { nested -> nested.iterator().forEach { resolved.add(it) } }
return Collections.enumeration(resolved)
}
override fun getResources(name: String?): Enumeration<URL> {
name ?: return Collections.emptyEnumeration()
if (name.startsWith("META-INF/mirai-console-plugin/"))
return findResources(name)
return getRes(name, true)
}
override fun getResource(name: String?): URL? {
name ?: return null
if (name.startsWith("META-INF/mirai-console-plugin/"))
return findResource(name)
findResource(name)?.let { return it }
// parent: ctx.sharedLibrariesLoader
sharedLibrariesLogger.getResource(name)?.let { return it }
dependencies.forEach { dep ->
dep.getResource(name)?.let { return it }
}
return pluginIndependentCL.getResource(name)
}
override fun toString(): String {
return "JvmPluginClassLoader{${file.name}}"
@ -278,3 +336,35 @@ internal class JvmPluginClassLoaderN : URLClassLoader {
private fun String.pkgName(): String = substringBeforeLast('.', "")
internal fun Artifact.depId(): String = "$groupId:$artifactId"
private operator fun <E> Enumeration<E>.plus(next: Enumeration<E>): Enumeration<E> {
return compoundEnumerations(listOf(this, next).iterator())
}
private fun <E> compoundEnumerations(iter: Iterator<Enumeration<E>>): Enumeration<E> {
return object : Enumeration<E> {
private lateinit var crt: Enumeration<E>
override fun hasMoreElements(): Boolean {
return (::crt.isInitialized && crt.hasMoreElements()) || iter.hasNext()
}
override fun nextElement(): E {
if (::crt.isInitialized) {
val c = crt
return if (c.hasMoreElements()) {
c.nextElement()
} else if (iter.hasNext()) {
crt = iter.next()
nextElement()
} else {
throw NoSuchElementException()
}
} else if (iter.hasNext()) {
crt = iter.next()
return nextElement()
} else {
throw NoSuchElementException()
}
}
}
}