我解决了一个非常具体的问题,其解决方案似乎是一些基本的问题:

我的( Spring )应用程序的类加载器层次结构是这样的:SystemClassLoader -> PlatformClassLoader -> AppClassLoader
如果我使用Java CompleteableFuture运行线程。线程的ContextClassLoader是:SystemClassLoader -> PlatformClassLoader -> ThreadClassLoader
因此,尽管必须这样做,但是我无法访问AppClassLoader中的任何类,因为所有外部库类都驻留在该类中。

源代码库很大,因此我不想/不能将所有与线程相关的部分重写为其他内容(例如,将自定义执行程序传递给每个调用)。

所以我的问题是:我如何制作例如CompleteableFuture.supplyAsync()使用AppClassLoader作为 parent 吗? (而不是PlatformClassloader)

我发现ForkJoinPool用于创建线程。但是在我看来,所有东西都是静态的和最终的。因此,我怀疑在这种情况下,即使使用系统属性设置自定义ForkJoinWorkerThreadFactory也会有所帮助。还是会?

编辑以回答评论中的问题:

  • 部署到哪里?是否在 jetty /tomcat/任何JEE容器中运行?
  • 我正在使用默认的Spring Boot设置,因此使用了一个内部tomcat容器。
  • 您遇到的确切问题是什么?
  • 确切的问题是: java.lang.IllegalArgumentException:org.keycloak.admin.client.resource.Realms从类加载器
  • 中看不到方法引用的资源
  • 您提交到supplyAsync()的作业是从AppClassLoader创建的,不是吗?
  • 从使用supplyAsyncMainThread中调用AppClassLoader。但是,调试应用程序表明,所有此类线程都将PlatformClassLoader作为其父级。据我了解,这是因为ForkJoinPool.commonPool()是在应用程序启动期间构造的(因为它是静态的),因此使用默认的类加载器作为父类PlatformClassLoader。因此,该池中的所有线程都将PlatformClassLoader作为ContextClassLoader的父级(而不是AppClassLoader)。
  • 当我在MainThread中创建自己的执行程序并将该执行程序传递给supplyAsync时,一切正常-我可以在调试过程中看到,现在AppClassLoader实际上是ThreadClassLoader的父级。这似乎在第一种情况下证实了我的假设,即至少在使用MainThread本身时,不是由AppClassLoader创建的公共(public)池。

  • 完整堆栈跟踪:
    java.lang.IllegalArgumentException: org.keycloak.admin.client.resource.RealmsResource referenced from a method is not visible from class loader
        at java.base/java.lang.reflect.Proxy$ProxyBuilder.ensureVisible(Proxy.java:851) ~[na:na]
        at java.base/java.lang.reflect.Proxy$ProxyBuilder.validateProxyInterfaces(Proxy.java:682) ~[na:na]
        at java.base/java.lang.reflect.Proxy$ProxyBuilder.<init>(Proxy.java:628) ~[na:na]
        at java.base/java.lang.reflect.Proxy.lambda$getProxyConstructor$1(Proxy.java:426) ~[na:na]
        at java.base/jdk.internal.loader.AbstractClassLoaderValue$Memoizer.get(AbstractClassLoaderValue.java:327) ~[na:na]
        at java.base/jdk.internal.loader.AbstractClassLoaderValue.computeIfAbsent(AbstractClassLoaderValue.java:203) ~[na:na]
        at java.base/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:424) ~[na:na]
        at java.base/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:999) ~[na:na]
        at org.jboss.resteasy.client.jaxrs.ProxyBuilder.proxy(ProxyBuilder.java:79) ~[resteasy-client-3.1.4.Final.jar!/:3.1.4.Final]
        at org.jboss.resteasy.client.jaxrs.ProxyBuilder.build(ProxyBuilder.java:131) ~[resteasy-client-3.1.4.Final.jar!/:3.1.4.Final]
        at org.jboss.resteasy.client.jaxrs.internal.ClientWebTarget.proxy(ClientWebTarget.java:93) ~[resteasy-client-3.1.4.Final.jar!/:3.1.4.Final]
        at org.keycloak.admin.client.Keycloak.realms(Keycloak.java:114) ~[keycloak-admin-client-3.4.3.Final.jar!/:3.4.3.Final]
        at org.keycloak.admin.client.Keycloak.realm(Keycloak.java:118) ~[keycloak-admin-client-3.4.3.Final.jar!/:3.4.3.Final]
    

    最佳答案

    因此,这是一个非常肮脏的解决方案,我不为此感到骄傲,并且可能会破坏的工作:

    问题是该应用程序的类加载器未用于ForkJoinPool.commonPool()。由于commonPool的设置是静态的,因此在应用程序启动期间,没有轻易的机会(至少据我所知)稍后进行更改。因此,我们需要依赖 Java反射API

    在应用程序成功启动后,

  • 创建一个钩子(Hook)

    在我的情况下(Spring Boot环境)中的
  • 将为ApplicationReadyEvent
  • 要收听此事件,您需要一个类似于以下内容的组件
    @Component
    class ForkJoinCommonPoolFix : ApplicationListener<ApplicationReadyEvent> {
        override fun onApplicationEvent(event: ApplicationReadyEvent?) {
      }
    }
    
  • 在钩子(Hook)内部,您需要将commonPool的ForkJoinWorkerThreadFactory设置为自定义实现(因此此自定义实现将使用应用程序类加载器)

    Kotlin

  • val javaClass = ForkJoinPool.commonPool()::class.java
    val field = javaClass.getDeclaredField("factory")
    field.isAccessible = true
    val modifiers = field::class.java.getDeclaredField("modifiers")
    modifiers.isAccessible = true
    modifiers.setInt(field, field.modifiers and Modifier.FINAL.inv())
    field.set(ForkJoinPool.commonPool(), CustomForkJoinWorkerThreadFactory())
    field.isAccessible = false
    
  • CustomForkJoinWorkerThreadFactory的简单实现

    Kotlin

  • //Custom class
    class CustomForkJoinWorkerThreadFactory : ForkJoinPool.ForkJoinWorkerThreadFactory {
      override fun newThread(pool: ForkJoinPool?): ForkJoinWorkerThread {
        return CustomForkJoinWorkerThread(pool)
      }
    }
    // helper class (probably only needed in kotlin)
    class CustomForkJoinWorkerThread(pool: ForkJoinPool?) : ForkJoinWorkerThread(pool)
    

  • 如果您需要,请提供更多有关反射的信息,以及为什么更改最终字段please refer to herehere不好。简短摘要:由于优化,更新的final字段对其他对象可能不可见,并且可能发生其他未知的副作用。

    如前所述:这是一个非常肮脏的解决方案。如果使用此解决方案,可能会发生有害的副作用。使用这样的反射不是一个好主意。如果您可以使用没有反射(reflection)的解决方案(并在此处发布答案!)。

    编辑:单个 call 的替代方法

    就像问题本身所述:如果您仅在少数几个地方遇到此问题(即,自行解决此问题本身就没有问题),则可以使用自己的Executor。一个简单的示例copied from here:
    ExecutorService pool = Executors.newFixedThreadPool(10);
    final CompletableFuture<String> future =
        CompletableFuture.supplyAsync(() -> { /* ... */ }, pool);
    

    08-07 03:02