WildFly10へJavaEE7のアプリケーションをデプロイする時の注意点

2016/10/06 久賀宏貴
Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn0Share on Tumblr0

wildfly10 logo

javaee7 logo

こんにちは。ユニトラストの久賀です。
突然ですが、皆さんJavaEE7使ってますか?

軽量コンテナがJavaの仕様になったということで前々から使ってみたかったわけですが、
ちょうどバッチ処理が必要なアプリケーション開発をする機会がありましたので、
JSR352も利用できる事もありJavaEE7を選択しました。

今回ご紹介するのはそこで起きたハプニングの一つです。
具体的には、題名の通りデプロイ時のトラブルシューティングです。
ClassCastExceptionが発生した場合の原因調査・具体的な解決策及びその考察(なぜ)の紹介です。

■環境

▼ビルド環境

・Windows8 64bit

・Java1.8

・NetBeans8.1

・Ant(NetBeansビルトイン)

▼デプロイ環境

・CentOS6.6 Final

・WildFly 10.0.0 Final

■java.lang.ClassCastException発生!

ビルドは出来たのでいざデプロイしたところ以下のエラーが出力
java.lang.ClassCastException: org.dom4j.DocumentFactory cannot be cast to org.dom4j.DocumentFactory

同じクラスへのキャストができない・・・だと!?スタックトレースを追うとHibernate絡みだと分かる

ERROR [org.jboss.msc.service.fail] (ServerService Thread Pool -- 4) MSC000001: Failed to start service jboss.persistenceunit."***.war#***": org.jboss.msc.service.StartException in service jboss.persistenceunit."***.war#***": java.lang.ClassCastException: org.dom4j.DocumentFactory cannot be cast to org.dom4j.DocumentFactory
	at org.jboss.as.jpa.service.PersistenceUnitServiceImpl$1$1.run(PersistenceUnitServiceImpl.java:172)
	at org.jboss.as.jpa.service.PersistenceUnitServiceImpl$1$1.run(PersistenceUnitServiceImpl.java:117)
	at org.wildfly.security.manager.WildFlySecurityManager.doChecked(WildFlySecurityManager.java:667)
	at org.jboss.as.jpa.service.PersistenceUnitServiceImpl$1.run(PersistenceUnitServiceImpl.java:182)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
	at org.jboss.threads.JBossThread.run(JBossThread.java:320)
Caused by: java.lang.ClassCastException: org.dom4j.DocumentFactory cannot be cast to org.dom4j.DocumentFactory
	at org.dom4j.DocumentFactory.getInstance(DocumentFactory.java:97)
	at org.hibernate.internal.util.xml.XMLHelper$1.doWork(XMLHelper.java:33)
	at org.hibernate.internal.util.xml.XMLHelper$1.doWork(XMLHelper.java:27)
	at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.workWithClassLoader(ClassLoaderServiceImpl.java:342)
	at org.hibernate.internal.util.xml.XMLHelper.<init>(XMLHelper.java:26)
	at org.hibernate.envers.boot.internal.EnversServiceImpl.initialize(EnversServiceImpl.java:115)
	at org.hibernate.envers.boot.internal.AdditionalJaxbMappingProducerImpl.produceAdditionalMappings(AdditionalJaxbMappingProducerImpl.java:99)
	at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:288)
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:847)
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:874)
	at org.jboss.as.jpa.hibernate5.TwoPhaseBootstrapImpl.build(TwoPhaseBootstrapImpl.java:44)
	at org.jboss.as.jpa.service.PersistenceUnitServiceImpl$1$1.run(PersistenceUnitServiceImpl.java:154)
	... 7 more

ここから怒涛の調査が始まる・・・

まず同じクラスへのキャストができない例外は複数の同一ライブラリを参照しているから
⇒dom4jを複数クラスパスに設定してたっけ?
⇒1つしかない
⇒ライブラリ追加履歴をみるとどうやらPOI関連だ(OOXML形式対応してるので)
※このWebアプリケーションはExcelファイルのアップロード機能がある

じゃぁ

このHibernate絡みのdom4jはどこで設定されているの?
⇒Hibernateは定義ファイルをXMLで書けるのでdom4j使っているのは納得できる
⇒スタックトレースをよく読むと「org.jboss.as.jpa.hibernate5」と出ている
⇒そうだ、WildflyのJPAプロバイダがHibernateだ!(JPAは仕様なのでそれを実装しているのがプロバイダ=APサーバ依存)
⇒だからデプロイしたときに発生するのか。

OK.

後はライブラリの依存関係を解決できればデプロイできるぞ!
⇒Antだった・・・orz…Mavenなら簡単なのに。。。(Google先生に聞いても「Maven使え」ばかり)
⇒それならサーバ側でどうだ!って調べてみるとビンゴ
⇒Wildflyはデプロイ時の設定ファイルが定義でき、そこで依存関係も設定できちゃう♪

<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="org.dom4j"/>
        </dependencies>
    </deployment>
</jboss-deployment-structure>

※ホントはMavenでやればよかったのですが、Mavenプロジェクトはディレクトリ構成が決まっているので、
開発途中でそれに変更するのは色々影響があるので止むを得ずAnt使っていました。。。

■まとめ

今回の問題のトリガーとなったClassCastExceptionについては同一ライブラリ参照が直接の原因とわかりました。
ただし解決方法やなぜそういった解決方法が提供されているのか理解することが大変でした。
つまり、JavaEEは仕様だよってことを本質的に(あるいは実感として)理解していませんでした。
アプリケーションサーバがJavaEEの仕様に従って実装するということは、
当然そのアプリケーションサーバ内で使用されるライブラリが多くなるということです。
今回は競合していた箇所がdom4jだった訳ですが、他にも独自に追加したライブラリが原因で同様の事象が起こりえます。
それの解決策をアプリケーションサーバ側が提供してくれているのは、
JavaEEの実装をこれが担っている以上、ある意味当然の成り行きなのかもしれません。
Antでビルドしていた怪我の功名ですが、実感としてJavaEEが仕様ということが実感できたのが今回の大きな収穫でした。

- 余談 -

Wildfly9以前ではこれは発生しません(理由は未調査ですが)
開発環境がWildfly9だったことが別環境へデプロイするまで気づかなかった原因ですね。

Share on Facebook0Tweet about this on TwitterShare on Google+0Share on LinkedIn0Share on Tumblr0