2017-02-19 7 views
2

ByteBuddyを使用して、別のライブラリのクラスをリベースして、Spring依存性注入を追加します。問題は、インターセプタとして使用されるクラスをインスタンス化できないことです。つまり、私はSpringを使用してApplicationContextをインターセプタに注入できません。 実行時に生成されたクラスでKotlinオブジェクトを使用する

は、この問題を回避するために、私は ApplicationContextAware実装することにより、注入さ ApplicationContextなるオブジェクト StaticAppContext、作成した:私はしようとすると、これがうまく注入しつつある

@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") 
    } 
} 

を(私は、ログメッセージを見ることができる)が、インターセプタからApplicationContextにアクセスするには、私はkotlin.UninitializedPropertyAccessException: lateinit property context has not been initializedを得ます。

クラスとincerceptorは、このクラスで定義されていリベースクラス:私はこれをデバッグするときインターセプタはStaticAppContextにアクセスしようとすると、IntelliJのはStaticAppContextの新しいインスタンスが作成されていることを示して

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 <[email protected]>" 
    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.getDeclaredField("client") 
      clientField.isAccessible = true 
      clientField.set(loader, discordClient) 

      val canModuleLoadMethod = loaderClass.getDeclaredMethod("canModuleLoad", IModule::class.java) 
      canModuleLoadMethod.isAccessible = true 

      val factory = StaticAppContext.context.autowireCapableBeanFactory 
      for (moduleClass in modules) { 
       try { 
        val wired = factory.autowire(moduleClass, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false) as IModule 
        LOGGER.info("Loading autowired module {}@{} by {}", wired.name, wired.version, wired.author) 
        if (canModuleLoadMethod.invoke(loader, wired) as Boolean) { 
         loadedModules.add(wired) 
        } else { 
         LOGGER.info("${wired.name} needs at least version ${wired.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.getDeclaredMethod("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") 
     } 
    } 
} 

、例外がスローされてから理にかなっています。

生成されたコードから呼び出されたとき、Kotlinオブジェクトは実際にはシングルトンではありませんか、何か間違っていますか?これを回避する方法は何でしょうか?

プロジェクトはまた、Githubの上で見つけることができます:https://github.com/jmesserli/discord-bernbot/tree/master/src/main/kotlin/nu/peg/discord


編集
私はClassLoader自分自身を追加spring-boot-devtoolsを除去することによって、問題を解決することができました。 Thread.currentThread().contextClassLoaderの使用を提案したところ、別の例外がありました。これは既に別のClassLoader(これはClassLoaderに問題があることを確認しています)によって既に読み込まれています。また、レースがあるかもしれないという前提が正しいと思われます。

私は今、別の問題を抱えています。自分で解決できるかどうかを調べるためにいくつかの調査を行います。

答えて

0

免責事項:私は趣味のプログラマーであり、まだ春と仕事をしていません。春について私がと聞いたところでは、と聞いています。


私は、これはクラスローダの問題かもしれません勘を持っている - あなたはD4JModuleLoaderReplacer.replaceModuleLoader()ClassLoader.getSystemClassLoader()のご利用状況による2つの異なるクラスローダにロードされた2つのStaticAppContextクラスを有することができます。

これを確認するには、init { ... }ブロックにStaticAppContextオブジェクトのログを作成します。例:

私の理論が正しい場合は、2つの作成ログメッセージを取得する必要があります。

この場合、現在のコンテキストクラスローダー(Thread.currentThread().getContextClassLoader())を使用する必要があります。

1

A Kotlin object次のレイアウトにコンパイルされます。

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

クラスは、暗黙的にシングルトンです。私は、問題がクラスローディングのレースかどうか疑問に思います。静的初期化子が既に呼び出されている可能性があります。正しいログメッセージを受け取っていますか?

関連する問題