「はじめて、Spring Boot フレームワークでアプリケーションを作ったのですが、はまってしまいました。
自分のパソコンで開発・デバッグしている場合には何の問題もなかったのですが、お客様環境にデプロイしてアプリケーションを起動した場合に問題が発生しました。
Spring Boot に限らず、このような問題が発生すると非常にやっかいです。環境差異による問題はインストールされているその他のアプリケーションを確認したり、ネットワークや環境変数の設定などを見直したりしなければならず、調査に時間がかかります。
問題発生
今回の問題は、お客様環境でのみ、以下の様な例外が発生しました。
[code] INFO 2017/07/06 15:02:00.175 org.springframework.context.support.AbstractApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4deecce2: startup date [Thu Jul 06 15:02:00 JST 2017]; root of context hierarchy INFO 2017/07/06 15:02:00.331 org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [META-INF/applicationContext.xml] WARN 2017/07/06 15:02:00.783 org.springframework.util.xml.SimpleSaxErrorHandler - Ignored XML validation warning org.xml.sax.SAXParseException; lineNumber: 5; columnNumber: 120; schema_reference.4: Failed to read schema document 'http://www.springframework.org/schema/beans/spring-beans-3.0.xsd', because 1) could not find the document; 2) the document could not be read; 3) the root element of the document is not <xsd:schema>. at org.apache.xerces.util.ErrorHandlerWrapper.createSAXParseException(Unknown Source) at org.apache.xerces.util.ErrorHandlerWrapper.warning(Unknown Source) at org.apache.xerces.impl.XMLErrorReporter.reportError(Unknown Source) at org.apache.xerces.impl.XMLErrorReporter.reportError(Unknown Source) at org.apache.xerces.impl.xs.traversers.XSDHandler.reportSchemaWarning(Unknown Source) at org.apache.xerces.impl.xs.traversers.XSDHandler.getSchemaDocument(Unknown Source) at org.apache.xerces.impl.xs.traversers.XSDHandler.parseSchema(Unknown Source) at org.apache.xerces.impl.xs.XMLSchemaLoader.loadSchema(Unknown Source) at org.apache.xerces.impl.xs.XMLSchemaValidator.findSchemaGrammar(Unknown Source) at org.apache.xerces.impl.xs.XMLSchemaValidator.handleStartElement(Unknown Source) at org.apache.xerces.impl.xs.XMLSchemaValidator.startElement(Unknown Source) at org.apache.xerces.impl.XMLNSDocumentScannerImpl.scanStartElement(Unknown Source) at org.apache.xerces.impl.XMLNSDocumentScannerImpl$NSContentDispatcher.scanRootElementHook(Unknown Source) at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source) at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) at org.apache.xerces.parsers.XMLParser.parse(Unknown Source) at org.apache.xerces.parsers.DOMParser.parse(Unknown Source) at org.apache.xerces.jaxp.DocumentBuilderImpl.parse(Unknown Source) at org.springframework.beans.factory.xml.DefaultDocumentLoader.loadDocument(DefaultDocumentLoader.java:76) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadDocument(XmlBeanDefinitionReader.java:429) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:391) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:336) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:304) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:181) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:217) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:188) at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:252) at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:127) at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:93) at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:129) at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:614) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:515) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139) at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
[/code]
初めて見るエラーでしたので、パッとは何のことかわかりませんでした。
よく見るとXMLのパースで例外となっているようです。これは「【Spring】newしたインスタンスのフィールドがDIされる方法」で記載したとおり、ApplicationContextを生成するためにapplicationContext.xmlを読み込んでいる個所でした。
Spring Bootフレームワーク側に問題があるとは思いつかず、XMLパーサーのライブラリがお客様環境でのみ何らかの差異があるのかと思って調査しました。
[adsense]
手がかり
しかし、特にライブラリのバージョンが古いなどは見受けられず、途方に暮れていたのですが、そもそもログに詳しい原因が記載されていました。
Failed to read schema document 'http://www.springframework.org/schema/beans/spring-beans-3.0.xsd'
applicationContext.xml内の「xsi:schemaLocation」で上記のURLを指定しています。
XMLの知識がある方はお気づきかもしれませんが、これはXMLのスキーマをXMLファイル内で読み込める設定です。記載の通り、インターネット上のURLを指定して取得する設定になっています。
その為、ネットワーク上で何らかの問題が発生すると、取得に失敗することとなります。ただし、Springでは「spring.schemas」というスキーマに関する情報を保有しているファイルを持っています。
この「spring.schemas」はスキーマがネットワーク上で取得できなかった場合に、ローカルに保有するファイルを取得するための情報が記載されています。
そこで、まずはお客様環境を確認しましたが、現在、インターネット環境に接続できないような状況になっておりました。
その為、スキーマの取得に失敗したのです。しかし、spring.schemasによってローカルファイルから読み込まれるはずなので、問題はないかと思われました。
[adsense]
Springのバグ?
念のため、jarファイルを展開し、META-INF/spring.schemasを確認したところ、上記のスキーマに関する情報が欠落していました。
ちなみにjarの展開は
[shell]
jar xvf xxxxx.jar META-INF/spring.schemas
[/shell]
といった感じでspring.schemasのみ展開できると思います。
期待としてはspring.schemas内に
[code]
[/code]
という記載があるはずなのですが、それがありません。これではファイルパスが解決できずに例外となって当然です。
しかし、基本的には自動でこのファイルは生成されるので、意識はしないのですが、なんらかのビルド順序などでこのようになることがあるようです。 [adsense]
解決方法
私の場合は、リソースファイルとして、spring.schemasを用意してしまうこととしました。
必要な情報をすべて記載して、spring.schemasを上書きしてパッケージングしてしまえば、フレームワークにまかせず自分自身で制御できます。
フレームワークが自動でやってくれることは便利なのですが、時として暴走する場合があります...工夫してプログラマーが制御する方法をみつけるのも重要なことです。
まずは、プロジェクトの「src/main/resources」配下に「META-INF」フォルダを作成し、その中に「spring.schemas」ファイルを作成して、以下の情報を入力しておきます。
[text]
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd=org/springframework/transaction/config/spring-tx-2.0.xsd http://www.springframework.org/schema/tx/spring-tx-2.5.xsd=org/springframework/transaction/config/spring-tx-2.5.xsd http://www.springframework.org/schema/tx/spring-tx-3.0.xsd=org/springframework/transaction/config/spring-tx-3.0.xsd http://www.springframework.org/schema/tx/spring-tx.xsd=org/springframework/transaction/config/spring-tx-3.0.xsd http://www.springframework.org/schema/aop/spring-aop-2.0.xsd=org/springframework/aop/config/spring-aop-2.0.xsd http://www.springframework.org/schema/aop/spring-aop-2.5.xsd=org/springframework/aop/config/spring-aop-2.5.xsd http://www.springframework.org/schema/aop/spring-aop-3.0.xsd=org/springframework/aop/config/spring-aop-3.0.xsd http://www.springframework.org/schema/aop/spring-aop.xsd=org/springframework/aop/config/spring-aop-3.0.xsd http://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd http://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd http://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd http://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd http://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd http://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd http://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd http://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd http://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd http://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd http://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd http://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd http://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd http://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd http://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.0.xsd http://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd http://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd http://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd http://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd http://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd http://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd http://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd http://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd http://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd http://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd=org/springframework/jdbc/config/spring-jdbc-3.0.xsd http://www.springframework.org/schema/jdbc/spring-jdbc.xsd=org/springframework/jdbc/config/spring-jdbc-3.0.xsd
[/text]
今回、直接的に問題となった情報以外にも今後必要になりそうなものをほとんど書いてあります。
念のため、これでビルドした結果のjarファイルを展開し、spring.schemasが期待通りになっているか確認しましょう。どうしても、期待通りにならない場合は、mavenでプロジェクト管理している場合は、buildにプラグインを組み込むことで、わたしは解決できました。
pom.xmlに以下を記載します。
[xml] <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>1.2.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> </plugin> </plugins> </build> [/xml] ポイントは自分で用意したリソースファイルを上書きしてしまうことです。うまくいかない場合は、リソースファイルをパッケージング時に含める方法を調べたら幸せになれるかもしれません。
この様な問題は本当に嫌いなので、発覚するとドキドキします... なるべくならば、アプリケーションが稼働する本番環境と開発環境はあわせたいものです。