Usar un object Kotlin en una class generada en time de ejecución

Estoy usando ByteBuddy para volver a establecer una base de classs de otra biblioteca para agregarle la dependency injection de Spring. El problema es que no puedo crear una instancia de la class que se utiliza como interceptor, lo que significa que no puedo usar Spring para inyectar el ApplicationContext en el interceptor.

Para evitar esto, creé un object StaticAppContext , que obtiene el ApplicationContext inyectado mediante la implementación de ApplicationContextAware :

 @Component object StaticAppContext : ApplicationContextAware { private val LOGGER = getLogger(StaticAppContext::class) @Volatile @JvmStatic lateinit var context: ApplicationContext override fun setApplicationContext(applicationContext: ApplicationContext?) { context = applicationContext!! LOGGER.info("ApplicationContext injected") } } 

Esto se está inyectando muy bien (puedo ver el post de logging), pero cuando bash acceder al ApplicationContext desde el interceptor, obtengo kotlin.UninitializedPropertyAccessException: lateinit property context has not been initialized .

La class que rebases la class y el incerceptor se definen en esta class:

 package nu.peg.discord.d4j import net.bytebuddy.ByteBuddy import net.bytebuddy.dynamic.ClassFileLocator import net.bytebuddy.dynamic.loading.ClassLoadingStrategy import net.bytebuddy.implementation.MethodDelegation import net.bytebuddy.implementation.SuperMethodCall import net.bytebuddy.implementation.bind.annotation.* import net.bytebuddy.matcher.ElementMatchers import net.bytebuddy.pool.TypePool import nu.peg.discord.config.BeanNameRegistry.STATIC_APP_CONTEXT import nu.peg.discord.config.StaticAppContext import nu.peg.discord.util.getLogger import org.springframework.beans.BeansException import org.springframework.beans.factory.config.AutowireCapableBeanFactory import org.springframework.context.annotation.DependsOn import org.springframework.stereotype.Component import sx.blah.discord.api.IDiscordClient import sx.blah.discord.modules.Configuration import sx.blah.discord.modules.IModule import sx.blah.discord.modules.ModuleLoader import java.lang.reflect.Constructor import java.util.ArrayList import javax.annotation.PostConstruct /** * TODO Short summary * * @author Joel Messerli @15.02.2017 */ @Component @DependsOn(STATIC_APP_CONTEXT) class D4JModuleLoaderReplacer : IModule { companion object { private val LOGGER = getLogger(D4JModuleLoaderReplacer::class) } @PostConstruct fun replaceModuleLoader() { val pool = TypePool.Default.ofClassPath() ByteBuddy().rebase<Any>( pool.describe("sx.blah.discord.modules.ModuleLoader").resolve(), ClassFileLocator.ForClassLoader.ofClassPath() ).constructor( ElementMatchers.any() ).intercept( SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(pool.describe("nu.peg.discord.d4j.SpringInjectingModuleLoaderInterceptor").resolve())) ).make().load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION) LOGGER.info("The D4J ModuleLoader has been replaced with ByteBuddy to allow for Spring injection") } override fun getName() = "Spring Injecting Module Loader" override fun enable(client: IDiscordClient?) = true override fun getVersion() = "1.0.0" override fun getMinimumDiscord4JVersion() = "1.7" override fun getAuthor() = "Joel Messerli <hi.github@peg.nu>" override fun disable() {} } class SpringInjectingModuleLoaderInterceptor { companion object { private val LOGGER = getLogger(SpringInjectingModuleLoaderInterceptor::class) @Suppress("UNCHECKED_CAST") @JvmStatic fun <T> intercept( @This loader: ModuleLoader, @Origin ctor: Constructor<T>, @Argument(0) discordClient: IDiscordClient?, @FieldValue("modules") modules: List<Class<out IModule>>, @FieldValue("loadedModules") loadedModules: MutableList<IModule> ) { LOGGER.debug("Intercepting $ctor") val loaderClass = loader.javaClass val clientField = loaderClass.getDeclanetworkingField("client") clientField.isAccessible = true clientField.set(loader, discordClient) val canModuleLoadMethod = loaderClass.getDeclanetworkingMethod("canModuleLoad", IModule::class.java) canModuleLoadMethod.isAccessible = true val factory = StaticAppContext.context.autowireCapableBeanFactory for (moduleClass in modules) { try { val winetworking = factory.autowire(moduleClass, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false) as IModule LOGGER.info("Loading autowinetworking module {}@{} by {}", winetworking.name, winetworking.version, winetworking.author) if (canModuleLoadMethod.invoke(loader, winetworking) as Boolean) { loadedModules.add(winetworking) } else { LOGGER.info("${winetworking.name} needs at least version ${winetworking.minimumDiscord4JVersion} to be loaded (skipped)") } } catch (e: BeansException) { LOGGER.info("Spring could not create bean", e) } } if (Configuration.AUTOMATICALLY_ENABLE_MODULES) { // Handles module load order and loads the modules val toLoad = ArrayList<IModule>(loadedModules) val loadModuleMethod = loaderClass.getDeclanetworkingMethod("loadModule", IModule::class.java) while (toLoad.size > 0) { toLoad.filter { loadModuleMethod.invoke(loader, it) as Boolean }.forEach { toLoad.remove(it) } } } LOGGER.info("Module loading complete") } } } 

Cuando depuro esto, IntelliJ muestra que se crea una nueva instancia de StaticAppContext cuando el interceptor intenta acceder al StaticAppContext , lo cual tiene sentido ya que se lanza la exception.

¿Los objects de Kotlin no son realmente Singletons cuando se los llama desde el código generado o estoy haciendo algo mal? ¿Cuál sería una forma de evitar esto?

El proyecto también se puede encontrar en Github: https://github.com/jmesserli/discord-bernbot/tree/master/src/main/kotlin/nu/peg/discord


Editar
Pude solucionar el problema eliminando spring-boot-devtools que agregan su propio ClassLoader . Cuando probé la sugerencia de usar Thread.currentThread().contextClassLoader , recibí una exception diferente que me decía que ya estaba cargada por un ClassLoader diferente (lo que confirma que era un problema con los ClassLoader s). Además, parece que la suposition de que podría haber una carrera era correcta.

Ahora tengo un problema diferente, investigaré un poco para ver si puedo resolverlo yo mismo.

Un object Kotlin se comstack en el siguiente layout:

 public final class StaticAppContext { public static final StaticAppContext INSTANCE; private StaticAppContext(); static {} } 

La class es implícitamente un singleton. Por lo tanto, me pregunto si el problema es una carrera en la carga de classs. Existe una buena posibilidad de que el inicializador estático ya haya sido invocado. ¿Estás seguro de que recibes los posts de logging correctos?

Descargo de responsabilidad: soy un progtwigdor aficionado y aún no he trabajado con Spring. Aquí hay un montón de especulaciones basadas en lo que escuché sobre Spring.


Tengo la corazonada de que podría tratarse de un problema del cargador de classs: es posible que haya 2 classs de StaticAppContext cargadas en 2 cargadores de classs diferentes debido a su uso de ClassLoader.getSystemClassLoader() en D4JModuleLoaderReplacer.replaceModuleLoader() .

Para confirmar esto, registre la creación del object StaticAppContext en un bloque init { ... } . Ejemplo:

 @Component object StaticAppContext : ApplicationContextAware { private val LOGGER = getLogger(StaticAppContext::class) init { LOGGER.info("StaticAppContext created. Classloader: ${javaClass.classLoader}") } ... } 

Si mi teoría es correcta, debería get 2 posts de logging de creación.

Si este es el caso, creo que debería usar el cargador de classs de context actual ( Thread.currentThread().getContextClassLoader() ).

  • Spring Boot 2 y Kotlin (con Maven)
  • Kotlin + Spring Boot solicita sorting
  • Cómo inyectar el context DSL de JOOQ en la class Kotlin
  • ¿Cómo crear una matriz Java en Kotlin para @PropertySource?
  • No puedo excluir MongoAutoConfiguration en Springboot-Kotlin (MongoSocketOpenException)
  • Las testings de Spring Boot no reutilizan el context
  • ¿Cómo verificar si Mono está vacío?
  • Actualización parcial REST en Spring Boot y Kotlin
  • JUnit, @ControllerAdvice y la falta de excepciones marcadas en Kotlin
  • Reactive Spring 5 Nombre principal de security en Kotlin
  • @CreationTimestamp y @UpdateTimestamp no funcionan en Kotlin