こまぶろ

技術のこととか仕事のこととか。

Java 9 以降の非推奨モジュールに依存したプログラムを動作させる:OpenAPI Generatorで生成したSpringスタブサーバが動作しない場合の対処法

前回の記事で、OpenAPI GeneratorでSpringのスタブサーバのソースコードを生成する方法を紹介した。

ky-yk-d.hatenablog.com

上の記事に記載した手順を手元のMacBook Proで実行したとき、XmlModelPluginに関わるエラーが発生してSpring Bootアプリケーションとして動作しなかった。別の端末(iMac)では正常に動作したので前回の記事では言及しなかったが、調査してみたところわかったことが多かったので、記事として公開することにした。

あらかじめ書いておくが、今回のエラーの根本原因は後述の通り自分がJavaのバージョンに無頓着であったことであり、OpenAPI Generatorの問題ではない。下記の内容は、OpenAPI Generator固有の問題を扱っているものではなく、Javaのバージョンの差異によって生じるエラーのケーススタディ的なものだと思ってもらえばいい。

概要

項目 内容
事象 生成したSpringのスタブサーバが特定環境で動作しない
原因 Java 11 で非推奨のモジュールが解決されなかったため
対応 pom.xmldependenciesJAXB APIを追加する

発生した事象

OpenAPI Genaratorでソースコードを生成したSpringのスタブサーバが動作しなかった。具体的には、下記のような手順でスタブサーバを起動しようとすると、エラーが発生してしまっていた。

実行手順

下記のコマンドを実行し、OpenAPI Generatorによるスタブサーバのソースコードを生成する。

openapi-generator generate
    -i docs/openapi.yaml # 生成元ファイル
    -o generated-sources/spring_stub # 出力先ディレクトリ
    -g spring
    --additional-properties returnSuccessCode=true

STSを開き、Open Projects from File System ...を選択。生成されたソースコードを読み込ませる。

f:id:ky_yk_d:20190113103609p:plain
ファイルシステムからプロジェクトを開く

STS上のBoot dashboardから、読み込んだプロジェクトを選択して起動する。

f:id:ky_yk_d:20190113105011p:plain
Boot dashboard から起動する

発生するエラー

下記のエラーメッセージがコンソールに表示され、アプリケーションの起動に失敗する。

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-01-12 17:17:36.297 ERROR 6665 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'xmlModelPlugin': Lookup method resolution failed; nested exception is java.lang.IllegalStateException: Failed to introspect Class [springfox.documentation.schema.XmlModelPlugin] from ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:262) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.determineConstructorsFromBeanPostProcessors(AbstractAutowireCapableBeanFactory.java:1198) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1123) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:541) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:501) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:760) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) ~[spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:395) ~[spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:327) ~[spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE]
    at org.openapitools.OpenAPI2SpringBoot.main(OpenAPI2SpringBoot.java:24) ~[classes/:na]
Caused by: java.lang.IllegalStateException: Failed to introspect Class [springfox.documentation.schema.XmlModelPlugin] from ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17]
    at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:659) ~[spring-core-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:556) ~[spring-core-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:541) ~[spring-core-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(AutowiredAnnotationBeanPostProcessor.java:245) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    ... 16 common frames omitted
Caused by: java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlType
    at java.base/java.lang.Class.getDeclaredMethods0(Native Method) ~[na:na]
    at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3167) ~[na:na]
    at java.base/java.lang.Class.getDeclaredMethods(Class.java:2310) ~[na:na]
    at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:641) ~[spring-core-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    ... 19 common frames omitted
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.annotation.XmlType
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582) ~[na:na]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
    ... 23 common frames omitted

発生原因

OpenAPI Generatorが生成したソースコードが想定するJavaのバージョンが8であるのに対し、MacBook ProにインストールしているJavaのバージョンが 11 であり、Java 9 以降で非推奨となっているjavax.xml.bindのモジュールが解決されなかったため。

Java 9 以降で非推奨になったモジュール群

(エラーの発生しなかった)iMacにインストールしてあったのがJava 8 であったのに対し、MacBook Proでは、以前遊びでJava 11 をインストールしてあった。下記の記事に拠れば、Java 9 以降ではいくつかのモジュールが非推奨となっており、デフォルトで解決されずに例外が発生してしまう。

javax.xml.bind

今回の場合、エラーの原因となっていたのは、javax.xml.bindというモジュールだった。このモジュールも、Java 9 以降で非推奨となっているものだ。エラーメッセージの末尾をみると、確かにClassNotFoundExceptionが発生していることがわかる。

Caused by: java.lang.ClassNotFoundException: javax.xml.bind.annotation.XmlType
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582) ~[na:na]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
    ... 23 common frames omitted

改めて、エラーメッセージをみてみると、以下のように、XmlModelPluginクラスでIllegalStateExceptionが発生している。つまり、このXmlModelPluiginが、解決されないjavax.xml.bindモジュールに依存していることにより、今回のエラーは発生していたようだ。

java.lang.IllegalStateException: Failed to introspect Class [springfox.documentation.schema.XmlModelPlugin] from ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17]

OpenAPI GeneratorとSpringFox

なお、上記のXmlModelPluginSpringFoxの一部であるが、OpenAPI GeneratorのGitHubリポジトリの文書を確認してみると、生成されるSpringのスタブサーバはデフォルトでSpringFoxを利用しているという記載が確かに存在する。

library
    library template (sub-template) to use (Default: spring-boot)
        spring-boot - Spring-boot Server application using the SpringFox integration.
        spring-mvc - Spring-MVC Server application using the SpringFox integration.
        spring-cloud - Spring-Cloud-Feign client with Spring-Boot auto-configured settings.

エラー解消のための対応

Java 8 に戻す……のではなく、前掲の記事にあるように、明示的に「Maven Central 上のアーティファクトを利用するよう依存ライブラリを追加」する。具体的には、下記をpom.xmldependenciesに追加すればよい。

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
</dependency>

以上の記述を追加してから、Boot dashboardから再起動すると、エラーが発生することなく正常に動作するようになった。ブラウザからAPIにアクセスすると、下記のように表示される。

f:id:ky_yk_d:20190112172255p:plain
http://localhost:8080/detailsにブラウザでアクセスした場合

感想

Javaのバージョンの差異に因って生じるエラーということは思いつかなかった。そもそも自分のマシンにインストールされているJavaのバージョンをあまり意識できていなかった。自宅ではJavaを書くことがほとんどないとはいえ、Javaで飯を食っている人間としては猛省すべき事案だった。「iMacなら動くし、まぁいっか」で済ませてしまわずに、原因を調査したことで左記のことに気づくことができたのが救いか。

今回は、対応として依存モジュールを明示するという方法を書いたが、Java 11 で動作するソースコードを生成できればそれに越したことはない。この点については、そのような変更がOpenAPI Generatorで可能なのかどうかをまだ理解できていないので、また何かわかったら記事にしたいと思う。