CompletableFuture/ForkJoinPool设置类加载器 [英] CompletableFuture / ForkJoinPool Set Class Loader

查看:557
本文介绍了CompletableFuture/ForkJoinPool设置类加载器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I tackled down a very specific problem, whose solution seems to be something basic:

我(Spring)应用程序的类加载器层次结构是这样的:SystemClassLoader -> PlatformClassLoader -> AppClassLoader

My (Spring) application's classloader hierarchy is something like this: SystemClassLoader -> PlatformClassLoader -> AppClassLoader

如果我使用Java CompleteableFuture运行线程.线程的ContextClassLoader是:SystemClassLoader -> PlatformClassLoader -> ThreadClassLoader

If I use Java CompleteableFuture to run threads. the ContextClassLoader of the threads is: SystemClassLoader -> PlatformClassLoader -> ThreadClassLoader

因此,尽管必须这样做,但我无法访问AppClassLoader中的任何类,因为所有外部库类都驻留在其中.

Thus, I cannot access any class in AppClassLoader although I have to because all external library classes reside there.

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

The source base is quite large so I don't want to/can't rewrite all the thread related pieces to something else (e.g. pass a custom executor to each call).

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

So my question is: How can I make the threads created by e.g. CompleteableFuture.supplyAsync() use the AppClassLoader as a parent? (instead of the PlatformClassloader)

我发现 ForkJoinPool 用于创建线程.但是在我看来,所有内容都是 static final .因此,我怀疑即使设置自定义 ForkJoinWorkerThreadFactory带有系统属性的在这种情况下会有所帮助.还是会?

I found out that ForkJoinPool is used to create the threads. But as it seems to me, everything there is static and final. So I doubt that even setting a custom ForkJoinWorkerThreadFactory with a system property will help in this case. Or would it?

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

  • 部署到哪里?这是在jetty/tomcat/任何JEE容器中运行吗?

  • 我使用的是默认的Spring Boot设置,因此使用了内部的tomcat容器.

您遇到的确切问题是什么?

  • 确切的问题是: java.lang.IllegalArgumentException:从方法引用的org.keycloak.admin.client.resource.RealmsResource在类加载器中不可见

您提交到supplyAsync()的作业是从AppClassLoader创建的,不是吗?

  • supplyAsync是从使用AppClassLoaderMainThread调用的.但是,调试应用程序表明,所有此类线程均以PlatformClassLoader作为其父级.据我了解,这是因为 ForkJoinPool.commonPool()是在应用程序启动期间构造的(因为它是静态的),因此使用默认的类加载器作为父级,即PlatformClassLoader.因此,该池中的所有线程都将PlatformClassLoader作为其 ContextClassLoader (而不是AppClassLoader).

  • The supplyAsync is called from the MainThread which uses the AppClassLoader. But, debugging the applications shows that all such threads have PlatformClassLoader as their parent. As to my understanding, this happens because ForkJoinPool.commonPool() is constructed during the application startup (because it's static) and so uses the default class loader as the parent which is PlatformClassLoader. So, all threads from this pool get PlatformClassLoader as their parent for ContextClassLoader (instead of AppClassLoader).

当我在MainThread中创建自己的执行程序并将该执行程序传递给supplyAsync时,一切正常-我可以在调试过程中看到,实际上AppClassLoader是我的ThreadClassLoader的父级.在第一种情况下,这似乎肯定了我的假设:公用池不是由MainThread创建的,至少不是在使用AppClassLoader本身时创建的.

When I'm creating my own executor inside the MainThread and pass this executor to supplyAsync everything works - and I can see during debugging that indeed now AppClassLoader is the parent of my ThreadClassLoader. Which seems to affirm my assumption in the first case that the common pool is not created by MainThread at least not when it's using AppClassLoader itself.

完整的堆栈跟踪:

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]

推荐答案

因此,这是一个非常肮脏的解决方案,我对此并不感到骄傲,如果您继续尝试,可能会为您带来麻烦带有它:

So, here is a very dirty solution of which I'm not proud of and may break things for you if you go along with it:

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

The problem was that the classloader of the application was not used for ForkJoinPool.commonPool(). Because the setup of commonPool is static and therefor during the application start up there is no easy possibility (at least to my knowledge) to make changes later. So we need to rely on Java reflection API.

  1. 在应用程序成功启动后创建一个钩子

  1. create a hook after your application successfully started

    在我的情况下(Spring Boot环境),这将是
  • in my case (Spring Boot environment) this will be the ApplicationReadyEvent
  • to listen to this event you need a component like the following

@Component
class ForkJoinCommonPoolFix : ApplicationListener<ApplicationReadyEvent> {
    override fun onApplicationEvent(event: ApplicationReadyEvent?) {
  }
}

在您的钩子中,您需要将commonPool的ForkJoinWorkerThreadFactory设置为自定义实现(因此该自定义实现将使用应用程序类加载器)

Inside your hook you need to set ForkJoinWorkerThreadFactory of commonPool to a custom implementation (so this custom implementation will use the app classloader)

  • 在科特林

  • in 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

  • 在科特林

  • in 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)

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

If you need more information about reflection and why it's not good to change final fields please refer to here and here. Short summary: due to optimizations the updated final field may not be visible to other objects and other unknown side effects may occur.

如前所述:这是一个非常肮脏的解决方案.如果使用此解决方案,可能会发生不良的副作用.像这样使用反射并不是一个好主意.如果您可以不加思索地使用解决方案(并在此处将其发布为答案!).

修改:单次通话的替代方式

就像问题本身所指出的那样:如果您仅在少数几个地方遇到此问题(即,自行修复呼叫本身就没有问题),则可以使用自己的从此处复制:

Like stated in the question itself: if you only have this problem in a small number of places (i.e. it's no problem to fix the call itself) you can use your own Executor. A simple example copied from here:

ExecutorService pool = Executors.newFixedThreadPool(10);
final CompletableFuture<String> future = 
    CompletableFuture.supplyAsync(() -> { /* ... */ }, pool);

这篇关于CompletableFuture/ForkJoinPool设置类加载器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆