Java SE 8とJava EE 7によるアプリケーションのモダナイゼーション~中間ふりかえり~ #javaee
はじめに
この記事は、「Java EE Advent Calendar 2014」の5日目の記事となります。
昨日は、@kazuhira_rさんの「JCacheの実装とキャッシュ管理のご紹介」でした。
明日は、@susumuisさんの「ねこ踏んじゃった系エントリ:Tomcatいじめたら意外と強かったこと」です。
このエントリは、9月にこのブログで前置きしておいた、既存のアプリケーション(Java SE 6、Java EE 5、JBoss AS 5.1.0.GA)のバージョンアップについてまとめたものです。
このプロジェクト、正直なところ、11月に終わっている予定だったのですが、諸々の事情により、まだ完了しておりません…。本来であればこのAdvent Calendarもドヤ顔で報告する算段だったのですが。
というわけで、中間ふりかえりという形で、ハマった点や新しいJavaを使ってみた所感などをまとめてみたいと思います。
以下のトピックスでお送りします(Java EEと関係ないものも混ざっていますが、このタイミングでブログに書いておかないと、ずっと書かなそうなので…)
- IDE(Eclipse)周りのトラブル
- JBoss Seam 2.2.2.FinalからJSF 2.2 + CDI 1.1への移行ポイント
- Java SE 8の使いどころ(依存ライブラリの対応状況、新クラス)
- ユニットテスト
- その他
なお、Java EEサーバとしてはWildFly 8.1.0.Finalを想定しています(今は8.2.0.Finalにバージョンアップしてますが)。
IDE(Eclipse)周りのトラブル
そもそもNetBeansやIntelliJ IDEAを使わずにEclipseを使う時点で茨の道だったのかもしれないですが、とにかくEclipseを使いました。バージョンは、Java SE 8がサポートされていることがMUSTなので、必然的に4.4以上です。
「さぁ開発するぞ!」と思って、build.gradle
に諸々の依存関係を追加して開発し始めたら、エディタ機能(コードアシスト)が致命的に重たく、とても開発できる状態にはなりませんでした。
原因は当時の最新のLombokのバージョン(1.14.6)の不具合でした。1.12.6にダウングレードしたら解消されました。なお、この不具合も現在は解決しているようなので、今は最新のバージョンを使いましょう。
別の問題として、Eclipseを4.4.1にアップデートした途端に、Gradle IDE Pluginがエラーを吐く問題にも遭遇しました。バグを踏んだのとトピックが起票されたのが半日も差がなく、タイムリーな問題でした。
いくつかワークアラウンドが提示されていましたが、当時はGradleのNightly Build版を使うようにして解消しました。こちらも今は解消されているみたいです。
JBoss Seam 2.2.2.FinalからJSF 2.2 + CDI 1.1への移行ポイント
対象のアプリはJBoss Seam 2.2.2.Finalで作られていたので、これをJSF 2.2 + CDI 1.1に移行しました。
Seamは個人的にはすごい使いやすいフレームワークであり、Java EE 5ベースであっても、以下のようなアノテーションの対応によってスムーズに移行できると思います。
Seam | CDI |
---|---|
@Name | @Named |
@Scope(ScopeType) | @RequestScoped, @ViewScoped, @SesssionScoped, @ApplicationScoped等 |
@In | @Inject |
@Out | @Inject ※ スコープで対応 |
@Create | @PostConstruct |
@Logger | @Inject ※ きしださんの記事を参考にロガープロデューサを作っておく |
@RequestParameter | @Inject ※ Qiitaに書いた拙作のようにプロデューサを作っておく |
@Begin,@End | Conversationを@Injectして会話スコープの作法に習う |
英語の記事ですが、Migration from JBoss Seam 2.2 to Java EE 7が丁寧に書かれていると思います。
いくつかはプロデューサを作って対応していますが、それ以外に作った便利なプロデューサとしてはFacesContextやDataSource用のプロデューサがあります。
FacesContextプロデューサを作っておくと、毎回 FacesContext.getCurrentInstance()
を呼ぶのに比べて、ちょっとだけコードがスッキリします。
DataSouceプロデューサは mappedName
が変わった時の修正範囲が最小化してくれます。
アプリケーションの開発ルールとして、ビュー(xhtml)とバッキングBeanは1:1に対応づけるようにし、バッキングBeanは原則ViewScopedにしました。 ログインユーザ情報を保持するSessionScopedのCDIオブジェクトや、アプリ全体で利用するSelectItemのリストを返すようなApplicationScopedのCDIオブジェクトなんかは、バッキングBeanと別パッケージにまとめておきました。
JSFはまだ使いこなせておらず、 @PostConstruct メソッド内でセットした FacesMessage が画面に表示されなかった点などでハマりました。原因と解決はここを参考にしました。
Java SE 8の使いどころ(依存ライブラリの対応状況、新クラス)
どこか(Twitter?)で見たのですが、Java EE 7はJava SE 7の仕様をベースにしているようです(記憶違いならすみません)。
そのため、Java SE 8で追加されたAPIをフルに使って開発するぜ!と意気込んでも、ハマったりします。
アプリのコードをJava SE 8式で書いても、そもそもJava SE 8に対応しているライブラリはまだ少ないので、Date and Time APIからjava.util.Date
へ変換するユーティリティを書く必要があったりで、悩ましいところです。
ハマった例を挙げると、インタフェースのデフォルトメソッド内でOptionalを使っていると、デプロイに失敗しました。Optionalが悪いかどうかはイマイチ自信がないのですが、Optionalを使わないように書き換えたところ、少なくともデプロイは成功したので、現状はそのように回避しています
*1。
他には、JAX-RSでJSONオブジェクトを返す際、オブジェクトの型がOptional, OptionalInt, LocalDateTimeだったりすると、期待するエンコード結果にならなかったりしました。その辺は先日Qiitaにまとめておきました。
逆にスムーズにJava SE 8で書き換えできるような箇所は、匿名クラスをラムダで書き直したり、単純なループをStream APIに書き直したり、といった点です。
二重ループだったり、以下のようにある日付からある日付までをループするような処理は、ちょっと苦しくなります。
LocalDate from = LocalDate.of(2014, 2, 10); LocalDate to = LocalDate.of(2014, 3, 3); for (LocalDate d = from; d.isBefore(to); d = d.plusDays(1)) { System.out.println(d); }
ちなみに上記のループは、以下のようにStream APIで書いてみました。無限に生成されるストリームに歯止めをかけるのが、 limit()
しかない(?)ので、期間が何日あるかを求めて、 limit()
に与えています。
long days = Math.max(0, ChronoUnit.DAYS.between(from, to)); Stream.iterate(from, (d) -> d.plusDays(1)) .limit(days) .forEach(System.out::println);
その他、Optional
も ifEmpty()
がなかったり、プリミティブ型の OptionalInt
に map()
がなかったり、細かい所でもどかしさを感じたりしました。
ユニットテスト
Advent Calendarの2日目に@irofさんがユニットテストについて触れていますが、モックフレームワークとして、EasyMockを引き続き使っています。
しばらくバージョンアップしてなかったのですが、最新のEasyMock 3.3だと、JUnitのルールやアノテーションを使って以前のテストコードに比べて定型コードが少なくなってテストコードがすっきり書けるようになった印象です。この辺はまた別にまとめておこうかなと。
ちなみにちょっと調べて、Java EE関連のテストフレームワークとしてneedle4jとかCDI-Unitの存在を知ったのですが、使っている方がいれば評価を伺いたいところです。
その他
PicketLink
認証・認可周りのライブラリとしてPicketLinkの採用を検討しましたが、結局見送りました。理由は、現行の認証・認可の仕組みが、DBスキーマ、AOP等々、アプリケーションの独自実装で作り込まれていたので、あえて乗り換えるためのコストが高そうと判断したためです。
HTML5 Friendly Markup
JSF 2.2のHTML5 Friendly Markupも、使ったり使わなかったりと中途半端な状態になっています。
テンプレートを使うための <ui:composition>
, <ui:include>
タグやAjaxのための <f:ajax>
なども含めて、あらゆるものをHTML5 Friendly Markupで書くのが辛そうだと判断したので、いっそ割り切って、使いやすい部分だけ使うことにしました。
JMS
MDBの onMessage()
メソッド内の処理を書く際に、 ObjectMessage
から目的のオブジェクトを取得するためのAPIとして getBody(Class<T>)
メソッドが追加されていたのが嬉しい修正でした。
今までは instanceof
で型チェックしてキャスト、みたいなことをするしかなかったので。
ただ、型チェックしてそれぞれキャスト、みたいなことをしなくなった代わりに、型ごとにMDBを用意しなければならなくなると思うので、その辺はメッセージセレクタで振り分るのが常なんでしょうか…?
@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "java:/queue/HogeQueue"), @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), @ActivationConfigProperty(propertyName = "messageSelector", propertyValue = "TYPE = 'A'") }) public class SampleMDB implements MessageListener { public void onMessage(Message message) { // ... } }
JAX-RSのClient API
Twitterでチラッとつぶやいたのですが、接続タイムアウト、読み取りタイムアウトを設定する方法がよく分からずじまい。。
JAX-RS 2.0のClient APIで(シンプルに)タイムアウトを設定する方法がよく分からない。Jerseyでの例はすぐ見つかるし、実際に今まで使ってたから、分かるんだけど。。
— tqed (endo) (@tq_jappy) November 17, 2014
おわりに
相変わらず全然内容が整理されていないなぁと思いつつ、以上、中間ふりかえりとして、思いついた点を洗い出してみました。Java SE 8もJava EE 7も、それぞれの新機能についての解説はあるのですが、「じゃあ実際どうなの?」という事例はまだまだ少ない気がするので、参考になれば幸いです。
ここでのまとめは、実際にアプリを開発して、色々と試行錯誤した内容についての記録なので、間違いもあるかと思います。そうした点はご指摘いただけると嬉しいです。
また、こうやって大変だった点を中心にまとめると、Javaにネガティブなイメージを持ってしまうかもしれません。ただ、決してそんなことはなく、実際に開発してみて、それが軌道に乗ってくると(以前と比べて)本当にサクサク開発できるようになって、楽しいなぁと感じました*2。