前回の記事で、OpenAPI GeneratorでSpringのスタブサーバのソースコードを生成する方法を紹介した。
上の記事に記載した手順を手元のMacBook Proで実行したとき、XmlModelPluginに関わるエラーが発生してSpring Bootアプリケーションとして動作しなかった。別の端末(iMac)では正常に動作したので前回の記事では言及しなかったが、調査してみたところわかったことが多かったので、記事として公開することにした。
あらかじめ書いておくが、今回のエラーの根本原因は後述の通り自分がJavaのバージョンに無頓着であったことであり、OpenAPI Generatorの問題ではない。下記の内容は、OpenAPI Generator固有の問題を扱っているものではなく、Javaのバージョンの差異によって生じるエラーのケーススタディ的なものだと思ってもらえばいい。
概要
項目 | 内容 |
---|---|
事象 | 生成したSpringのスタブサーバが特定環境で動作しない |
原因 | Java 11 で非推奨のモジュールが解決されなかったため |
対応 | pom.xml のdependencies にJAXB 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 ...
を選択。生成されたソースコードを読み込ませる。
STS上の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
なお、上記のXmlModelPlugin
はSpringFoxの一部であるが、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.xml
のdependencies
に追加すればよい。
<dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> </dependency>
以上の記述を追加してから、Boot dashboardから再起動すると、エラーが発生することなく正常に動作するようになった。ブラウザからAPIにアクセスすると、下記のように表示される。
感想
Javaのバージョンの差異に因って生じるエラーということは思いつかなかった。そもそも自分のマシンにインストールされているJavaのバージョンをあまり意識できていなかった。自宅ではJavaを書くことがほとんどないとはいえ、Javaで飯を食っている人間としては猛省すべき事案だった。「iMacなら動くし、まぁいっか」で済ませてしまわずに、原因を調査したことで左記のことに気づくことができたのが救いか。
今回は、対応として依存モジュールを明示するという方法を書いたが、Java 11 で動作するソースコードを生成できればそれに越したことはない。この点については、そのような変更がOpenAPI Generatorで可能なのかどうかをまだ理解できていないので、また何かわかったら記事にしたいと思う。