복붙노트

[SPRING] CompletableFuture / ForkJoinPool 세트 클래스 로더

SPRING

CompletableFuture / ForkJoinPool 세트 클래스 로더

나는 매우 구체적인 문제를 해결했는데, 그 해결책은 기본적인 것 같습니다.

내 (봄) 응용 프로그램의 클래스 로더 계층 구조는 다음과 같습니다 .SystemClassLoader-> PlatformClassLoader-> AppClassLoader

Java CompleteableFuture를 사용하여 스레드를 실행하는 경우 스레드의 ContextClassLoader는 다음과 같습니다. SystemClassLoader-> PlatformClassLoader-> ThreadClassLoader

따라서 모든 외부 라이브러리 클래스가 거기에 있기 때문에 AppClassLoader의 클래스에 액세스 할 수 없습니다.

소스베이스는 상당히 커서 모든 스레드 관련 조각을 다른 것에 다시 쓰고 싶지 않습니다 / 예를 들어 각 호출에 사용자 정의 실행기를 전달하십시오.

그래서 내 질문은 : 어떻게 스레드를 만들 수 있습니까? CompleteableFuture.supplyAsync ()는 AppClassLoader를 부모로 사용합니까? (PlatformClassloader 대신)

ForkJoinPool이 스레드를 만드는 데 사용된다는 것을 알았습니다. 그러나 나에게 보이는 것처럼 모든 것이 정적이고 최종적입니다. 따라서 시스템 속성으로 사용자 정의 ForkJoinWorkerThreadFactory를 설정해도 도움이 될 것입니다. 아니면?

주석의 질문에 답변하도록 편집하십시오.

전체 스택 추적 :

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]

해결법

  1. ==============================

    1.그래서 여기에 내가 자랑스럽지 않은 해결책이 있습니다.

    그래서 여기에 내가 자랑스럽지 않은 해결책이 있습니다.

    문제는 응용 프로그램의 클래스 로더가 ForkJoinPool.commonPool ()에 사용되지 않았다는 것입니다. commonPool의 설정은 정적이므로 응용 프로그램을 시작하는 동안 나중에 변경하기가 쉽지 않습니다 (적어도 내 지식으로는). 따라서 Java reflection API를 사용해야합니다.

    반영에 대한 자세한 정보와 최종 필드를 변경하는 것이 좋지 않은 이유는 여기 및 여기를 참조하십시오. 요약 : 최적화로 인해 업데이트 된 최종 필드가 다른 개체에 표시되지 않고 다른 알 수없는 부작용이 발생할 수 있습니다.

    앞에서 언급했듯이 이것은 매우 더러운 솔루션입니다. 이 솔루션을 사용하면 원하지 않는 부작용이 발생할 수 있습니다. 이와 같은 반사를 사용하는 것은 좋은 생각이 아닙니다. 성찰없이 솔루션을 사용할 수 있다면 (여기에 답변으로 게시하십시오!).

    편집 : 단일 통화에 대한 대안

    질문 자체에 명시된 바와 같이 : 소수의 장소에서만이 문제가있는 경우 (즉, 전화 자체를 고치는 데 아무런 문제가없는 경우) 자신의 집행자를 사용할 수 있습니다. 여기에서 복사 한 간단한 예 :

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

    2.resteasy lib는 스레드 컨텍스트 클래스 로더를 사용하여 일부 리소스를로드하는 것 같습니다. http://grepcode.com/file/repo1.maven.org/maven2/org.jboss.resteasy/resteasy-client/3.0-beta-1/org/jboss/resteasy/client/jaxrs/ProxyBuilder.java#21.

    resteasy lib는 스레드 컨텍스트 클래스 로더를 사용하여 일부 리소스를로드하는 것 같습니다. http://grepcode.com/file/repo1.maven.org/maven2/org.jboss.resteasy/resteasy-client/3.0-beta-1/org/jboss/resteasy/client/jaxrs/ProxyBuilder.java#21.

    resteasy가 요청한 클래스를로드하려고 시도하면 요청 된 클래스가 클래스 로더가 볼 수없는 클래스 경로에있을 때 스레드 클래스 로더가 클래스 클래스를 찾아 요청하고 가능한 경우로드하도록 요청합니다.

    그리고 그것은 당신의 어플리케이션에 정확히 무슨 일이 일어나는가 : 이 클래스 경로의 자원은 AppClassLoader 및 해당 하위에서만 액세스 할 수 있으므로 ThreadClassLoader가 애플리케이션 클래스 경로에있는 자원을로드하려고 시도했지만 ThreadClassLoader가이를로드하지 못했습니다 (ThreadClassLoader는 AppClassLoader의 하위가 아님).

    가능한 해결책은 앱 ClassLoader에 의해 스레드 컨텍스트 ClassLoader를 대체하는 것입니다. thread.setContextClassLoader (appClass.class.getClassLoader ())

  3. ==============================

    3.나는 비슷한 것을 만났고 리플렉션을 사용하지 않고 JDK9-JDK11과 잘 작동하는 솔루션을 생각해 냈습니다.

    나는 비슷한 것을 만났고 리플렉션을 사용하지 않고 JDK9-JDK11과 잘 작동하는 솔루션을 생각해 냈습니다.

    다음은 javadoc이 말하는 것입니다.

    따라서 고유 한 버전의 ForkJoinWorkerThreadFactory를 롤아웃하고 대신 시스템 특성을 사용하여 올바른 클래스 로더를 사용하도록 설정하면 작동합니다.

    내 사용자 정의 ForkJoinWorkerThreadFactory는 다음과 같습니다.

    package foo;
    
    public class MyForkJoinWorkerThreadFactory implements ForkJoinWorkerThreadFactory {
    
        @Override
        public final ForkJoinWorkerThread newThread(ForkJoinPool pool) {
            return new MyForkJoinWorkerThread(pool);
        }
    
        private static class MyForkJoinWorkerThread extends ForkJoinWorkerThread {
    
            private MyForkJoinWorkerThread(final ForkJoinPool pool) {
                super(pool);
                // set the correct classloader here
                setContextClassLoader(Thread.currentThread().getContextClassLoader());
            }
        }
    } 
    

    그런 다음 앱 시작 스크립트에서 시스템 속성을 설정하십시오.

    위의 솔루션은 ForkJoinPool 클래스가 처음으로 참조되고 commonPool을 초기화 할 때이 스레드의 컨텍스트 클래스 로더가 필요한 것 (시스템 클래스 로더가 아님)이라고 가정합니다.

    도움이 될만한 몇 가지 배경은 다음과 같습니다.

    이전 버전과의 비 호환성 변경으로 인해 JDK8에서 작동하던 ForkJoinPool을 사용하는 것은 JDK9 +에서 작동하지 않을 수 있습니다.

  4. from https://stackoverflow.com/questions/49113207/completablefuture-forkjoinpool-set-class-loader by cc-by-sa and MIT license