Java11環境でSOAPサービスを呼び出そうとしたら大変だった
Java11 上で動かしている JBoss (Red Hat SSO) 環境で、SOAP サービスを呼び出そうとしたら、とても大変だったのでそのまとめです。
環境
- AdoptOpenJDK Hotspot 11.0.11+9
- Red Hat SSO 7.4 (JBoss EAP 7.3.7)
- Apache CXF 3.4.3
ひとつめの問題
Red Hat SSO に、カスタムプロバイダ (認証機能を拡張するモジュール) を載せた状態で当該機能にアクセスした際、以下のような例外が発生しました。
08:03:12,265 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-5) Uncaught server error: javax.xml.ws.WebServiceException: Provider com.sun.xml.internal.ws.spi.ProviderImpl not found
at javax.xml.ws.spi.FactoryFinder$1.createException(FactoryFinder.java:31)
at javax.xml.ws.spi.FactoryFinder$1.createException(FactoryFinder.java:28)
at javax.xml.ws.spi.ServiceLoaderUtil.newInstance(ServiceLoaderUtil.java:73)
at javax.xml.ws.spi.FactoryFinder.find(FactoryFinder.java:82)
at javax.xml.ws.spi.Provider.provider(Provider.java:66)
at javax.xml.ws.Service.<init>(Service.java:82)
...
Caused by: java.lang.ClassNotFoundException: com.sun.xml.internal.ws.spi.ProviderImpl from [Module "deployment.keycloak-server.war" from Service Module Loader]
at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:255)
at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:410)
at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:398)
at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:116)
at javax.xml.ws.spi.ServiceLoaderUtil.nullSafeLoadClass(ServiceLoaderUtil.java:60)
at javax.xml.ws.spi.ServiceLoaderUtil.safeLoadClass(ServiceLoaderUtil.java:93)
at javax.xml.ws.spi.ServiceLoaderUtil.newInstance(ServiceLoaderUtil.java:71)
... 88 more
com.sun.xml.internal.ws.spi.ProviderImpl
が見つからないのだそうです。
これは、Java8 以前のバージョンで、rt.jar
の中に含まれていたクラスです。
Java9 以降では rt.jar
がランタイムに含まれなくなったため、当該クラスが見つからずに例外をスローした、ということのようです。
これを解消するには、com.sun.xml.internal.ws.spi.ProviderImpl
に代わるクラスを用意して、用意されたクラスが使用されればよいです。
代わるクラスについては、JEP 320 でも言及されており、以下のような流れで対応します。
com.sun.xml.ws:jaxws-ri:2.3.1
をpom
スコープで依存に追加します (執筆時点で、2.x の最新は2.3.4
ですが、使用すると別の ClassNotFoundException が発生するため回避します *後述)com.sun.xml.bind:jaxb-ri:2.3.1
をpom
スコープで依存に追加します (こちらは必要に応じて追加します)- アプリケーションの起動スクリプト等に、システムプロパティ
-Djavax.xml.ws.spi.Provider=com.sun.xml.ws.spi.ProviderImpl
を設定します。
ふたつめの問題
ここまで対応しても、以下の通り例外は解消されません。しかし着実に前進はしています。 例外内容が変わらないため、効果のない対応だったのではないかと、見誤ってしまいそうになりますが、大丈夫です。
11:01:37,324 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-2) Uncaught server error: javax.xml.ws.WebServiceException: Provider com.sun.xml.ws.spi.ProviderImpl not found
at javax.xml.ws.spi.FactoryFinder$1.createException(FactoryFinder.java:31)
at javax.xml.ws.spi.FactoryFinder$1.createException(FactoryFinder.java:28)
at javax.xml.ws.spi.ServiceLoaderUtil.newInstance(ServiceLoaderUtil.java:73)
at javax.xml.ws.spi.FactoryFinder.fromSystemProperty(FactoryFinder.java:92)
at javax.xml.ws.spi.FactoryFinder.find(FactoryFinder.java:69)
at javax.xml.ws.spi.Provider.provider(Provider.java:66)
at javax.xml.ws.Service.<init>(Service.java:82)
...
Caused by: java.lang.ClassNotFoundException: com.sun.xml.ws.spi.ProviderImpl from [Module "deployment.keycloak-server.war" from Service Module Loader]
at org.jboss.modules.ModuleClassLoader.findClass(ModuleClassLoader.java:255)
at org.jboss.modules.ConcurrentClassLoader.performLoadClassUnchecked(ConcurrentClassLoader.java:410)
at org.jboss.modules.ConcurrentClassLoader.performLoadClass(ConcurrentClassLoader.java:398)
at org.jboss.modules.ConcurrentClassLoader.loadClass(ConcurrentClassLoader.java:116)
at javax.xml.ws.spi.ServiceLoaderUtil.nullSafeLoadClass(ServiceLoaderUtil.java:60)
at javax.xml.ws.spi.ServiceLoaderUtil.safeLoadClass(ServiceLoaderUtil.java:93)
at javax.xml.ws.spi.ServiceLoaderUtil.newInstance(ServiceLoaderUtil.java:71)
... 89 more
これは、JBoss のクラスローダーに、対応したクラスがロードされていないことに起因します。 ここでは、例外が発生する処理の前後でクラスローダーを切り替えて対応します。 必要なクラスはすでに依存に含まれているため、適切なクラスローダーに切り替えることで、対象のクラスを見つけられるようになります。
今回は、以下のような WebService クラスから Port オブジェクトを取得する処理中に、 例外が発生していたため、この前後でクラスローダーを切り替えます。
変更前は以下のような実装です。
new DemoService(new URL(wsdlLocation)).getDemoPort();
これを以下のように書き換えます。
ClassLoader targetClassLoader = DemoService.class.getClassLoader();
Thread currentThread = Thread.currentThread();
ClassLoader contextClassLoader = currentThread.getContextClassLoader();
try {
currentThread.setContextClassLoader(targetClassLoader);
new DemoService(new URL(wsdlLocation)).getDemoPort();
} finally {
currentThread.setContextClassLoader(contextClassLoader);
}
あとから聞いた話によると、このようなテクニックは、JBoss 他、アプリケーションサーバー内の実装では度々行われているそうで、 アプリケーションサーバー上で動作するユーザーアプリケーションのために、実行環境を整える必要があるような場合に、 同様にクラスローダーを切り替えるのだそうです。
みっつめの問題
これはここまでの流れに従っている場合は発生しません。 [[ひとつめの問題]] の対応時に、依存するモジュールのバージョンを誤っていた場合に発生するものです。
com.sun.xml.ws:jaxws-ri:2.3.1
を誤って、com.sun.xml.ws:jaxws-ri:2.3.4
に依存していた場合に、この問題が発生します。
一見パッチバージョンのアップデートに見える 2.3.4
ですが、後方互換性を失っているようで、以下のような例外が発生してしまいます。
16:59:49,586 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-4) Uncaught server error: java.lang.NoClassDefFoundError: com/oracle/webservices/api/message/AccessorFactory$MethodHandleAccessor
at com.oracle.webservices.api.message.AccessorFactory.createAccessor(AccessorFactory.java:43)
at com.oracle.webservices.api.message.BasePropertySet.lambda$parse$0(BasePropertySet.java:176)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at com.oracle.webservices.api.message.BasePropertySet.parse(BasePropertySet.java:148)
at com.oracle.webservices.api.message.BasePropertySet.parse(BasePropertySet.java:130)
at com.sun.xml.ws.client.RequestContext.<clinit>(RequestContext.java:361)
at com.sun.xml.ws.client.Stub.<init>(Stub.java:136)
at com.sun.xml.ws.client.Stub.<init>(Stub.java:213)
at com.sun.xml.ws.client.Stub.<init>(Stub.java:228)
at com.sun.xml.ws.client.sei.SEIStub.<init>(SEIStub.java:68)
at com.sun.xml.ws.client.WSServiceDelegate.getStubHandler(WSServiceDelegate.java:791)
at com.sun.xml.ws.client.WSServiceDelegate.createEndpointIFBaseProxy(WSServiceDelegate.java:780)
at com.sun.xml.ws.client.WSServiceDelegate.getPort(WSServiceDelegate.java:422)
at com.sun.xml.ws.client.WSServiceDelegate.getPort(WSServiceDelegate.java:390)
at com.sun.xml.ws.client.WSServiceDelegate.getPort(WSServiceDelegate.java:372)
at javax.xml.ws.Service.getPort(Service.java:139)
...
Caused by: java.lang.ClassNotFoundException: com.oracle.webservices.api.message.AccessorFactory$MethodHandleAccessor
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 101 more
こちらへの対応は、前述の通り、com.sun.xml.ws:jaxws-ri
の依存バージョンを 2.3.1
に修正すればよいです。
まとめ
以上ですべての対応は完了です。 ここまでをまとめると、一部のアプリケーションサーバー上で SOAP 関連のモジュールを動かそうとして例外が発生した場合には、以下を試してみると良いでしょう。
com.sun.xml.ws:jaxws-ri:2.3.1
をpom
スコープで依存に追加するcom.sun.xml.bind:jaxb-ri:2.3.1
をpom
スコープで依存に追加する- システムプロパティ
-Djavax.xml.ws.spi.Provider=com.sun.xml.ws.spi.ProviderImpl
を設定する - 例外が発生する処理の前後でコンテキストクラスローダーをアプリケーション由来のものに切り替える