プログラミング

実装・エラー解決

Java9以降でJAXB(SOAP)で作ったプログラムをTomcatで動かすためにやったこと

この記事に書いてあること

JAXBを利用するSOAPアプリが、テストでは動くのにTomcatへ置いたWARで動かなかった記録です。JDK 9ではJAXB・JAX-WS関連モジュールの既定の解決範囲が変わり、JDK 11ではこれらのモジュールが削除されました。

発生した問題

当時はTomcat 9で javax.xml.bind 系のアプリを動かしていました。テストの依存関係にJAXBがあっても、デプロイ先に必要なAPIや実装があるとは限りません。WAR内の WEB-INF/lib とTomcatの共有 lib を確認します。

解決した方法

当時は関連JARをWARから外し、共有lib側へ揃えることで動きました。共有libの変更は同じTomcat上の他アプリにも関わります。アプリが依存を持つ構成では、互換性のあるAPIと実装をWAR内に揃え、同じ依存を共有libへ重複配置しない構成をまず検討します。

次の診断例は、クラスの読み込み元とJAXB実装が選ばれるところまでを確認します。JAXBが動くことと、SOAPクライアントやWAR全体が動くことは別に確認します。

public class JaxbProbe {
    public static void main(String[] args) {
        inspect("javax.xml.bind.JAXBContext");
        inspect("jakarta.xml.bind.JAXBContext");
    }

    private static void inspect(String name) {
        try {
            Class<?> type = Class.forName(name, false, JaxbProbe.class.getClassLoader());
            System.out.println(name + " -> " + type.getProtectionDomain()
                    .getCodeSource().getLocation());
            Object context = type.getMethod("newInstance", Class[].class)
                    .invoke(null, (Object) new Class<?>[]{String.class});
            System.out.println("provider -> " + context.getClass().getName());
        } catch (ClassNotFoundException e) {
            System.out.println(name + " -> missing");
        } catch (ReflectiveOperationException e) {
            throw new IllegalStateException("API found, but context could not be created", e);
        }
    }
}
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>example</groupId>
  <artifactId>jaxb-classloading-probe</artifactId>
  <version>1.0</version>
  <properties>
    <maven.compiler.release>11</maven.compiler.release>
  </properties>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.14.1</version>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.6.3</version>
        <configuration>
          <mainClass>JaxbProbe</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <profiles>
    <profile>
      <id>javax</id>
      <dependencies>
        <dependency>
          <groupId>org.glassfish.jaxb</groupId>
          <artifactId>jaxb-runtime</artifactId>
          <version>2.3.6</version>
        </dependency>
      </dependencies>
    </profile>
    <profile>
      <id>jakarta</id>
      <dependencies>
        <dependency>
          <groupId>org.glassfish.jaxb</groupId>
          <artifactId>jaxb-runtime</artifactId>
          <version>4.0.4</version>
        </dependency>
      </dependencies>
    </profile>
  </profiles>
</project>
mvn compile exec:java
mvn -Pjavax compile exec:java
mvn -Pjakarta compile exec:java

JDK 17で依存を追加しない場合、どちらのJAXBContextも見つかりませんでした。javaxプロファイルでは com.sun.xml.bind.v2.runtime.JAXBContextImpl、jakartaプロファイルでは org.glassfish.jaxb.runtime.v2.runtime.JAXBContextImpl が選ばれました。1回の起動でプロファイルは片方だけ選びます。

追加情報

providedなし、war内にライブラリを内包した場合のエラー

WARに依存JARを入れても起動しない場合は、例外チェーンとWARの内容を確認します。javax.xml.bind 用のアプリへ jakarta.xml.bind 名前空間のJARを入れても、要求されたクラスは提供されません。Tomcat 10以降へ移行する場合は、Servlet側の名前空間の変更も別途確認します。

providedなし、war内とTomcatのlibフォルダ両方に配置

当時は、例外にWAR内とTomcat共有libの両方のJAXBContext.classが現れ、キャストに失敗していました。読み込んだJARとクラスローダーを確認し、APIと実装を同じ配置方針に揃えます。

出典 OracleのJDK 11移行ガイド、Tomcatのクラスローダー

-実装・エラー解決
-,