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と関係ないものも混ざっていますが、このタイミングでブログに書いておかないと、ずっと書かなそうなので…)

なお、Java EEサーバとしてはWildFly 8.1.0.Finalを想定しています(今は8.2.0.Finalにバージョンアップしてますが)。

IDE(Eclipse)周りのトラブル

そもそもNetBeansIntelliJ 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-RSJSONオブジェクトを返す際、オブジェクトの型が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);

その他、OptionalifEmpty() がなかったり、プリミティブ型の OptionalIntmap() がなかったり、細かい所でもどかしさを感じたりしました。

ユニットテスト

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

MDBonMessage() メソッド内の処理を書く際に、 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でチラッとつぶやいたのですが、接続タイムアウト、読み取りタイムアウトを設定する方法がよく分からずじまい。。

おわりに

相変わらず全然内容が整理されていないなぁと思いつつ、以上、中間ふりかえりとして、思いついた点を洗い出してみました。Java SE 8もJava EE 7も、それぞれの新機能についての解説はあるのですが、「じゃあ実際どうなの?」という事例はまだまだ少ない気がするので、参考になれば幸いです。

ここでのまとめは、実際にアプリを開発して、色々と試行錯誤した内容についての記録なので、間違いもあるかと思います。そうした点はご指摘いただけると嬉しいです。

また、こうやって大変だった点を中心にまとめると、Javaにネガティブなイメージを持ってしまうかもしれません。ただ、決してそんなことはなく、実際に開発してみて、それが軌道に乗ってくると(以前と比べて)本当にサクサク開発できるようになって、楽しいなぁと感じました*2

*1:ちなみにインタフェースのデフォルトメソッドを使っている箇所はというと、Java EEと関係ないのですが、ORMとして利用しているDoma 2のDaoインタフェースです。Doma 1からDoma 2にバージョンアップした際、委譲クラスに書いていた処理を、デフォルトメソッドとして書くように変更になったので。

*2:まぁ、一番嬉しかったのはアプリケーションサーバの起動時間が劇的に短縮された点なのですが。

Java EE 5からJava EE 7へのアップデート 〜依存関係の大掃除〜

ちょっと泥臭い話で、Java EE 5からJava EE 7へのアップデートにあたり、利用しているサードパーティ製の追加ライブラリ(依存関係)についても改めてみることにしました。せっかくJava EE 7標準で用意されているのに他の代替ライブラリを使い続けてる、みたいになっても無駄なので。

Java EEフルスタックな標準仕様と言っても、実際の開発では他のライブラリを取捨選択して開発すると思います。 他のプロジェクトでは、どういうポリシーで取捨選択し、採用に至るのかは分かりません(リーダーにお伺いを立てるとか?)が、自分のところはよくも悪くも無法地帯で、好きなものを自由に選んで採用しています*1

前置きはさておき、実際に使っているライブラリについて、検討した一覧がこちら(おかしな部分のツッコミとかは大歓迎です。)

ライブラリ 方針 備考
Joda-Time 使わない Date and Time (Java SE 8)に置き換える。
Quartz 要検討 EJBの@Schedule、Concurrency UtilitiesのManagedScheduledExecutorServiceなどで代用?
QuartzのJobStoreTX(ジョブの登録内容をDBにして永続化する仕組み)相当の仕組みがあれば…
JDBC 使う
jersey-client 使わない JAX-RS 2.0のクライアントAPIに置き換える。
Guava 要検討 使ってる用途の範囲だと、Java SE 8の新APIの部分でほぼカバーできそう。
WIldFlyのmodulesにある(=warには含めなくてよいはず)
Doma 使う 今のアプリはJPA使わずにDoma1使っているので、Doma2にアップデートして使っていきたい。
commons-lang 使う WIldflyのmodulesにある。
commons-exec 使わない Java SE 8/Java EE 7で替わりとなる新APIがあるわけではないですが、なくてもよいという所感。
commons-email 使わない JavaMailに置き換える。
commons-compress 使う
commons-beanutils 使う WildFlyのmodulesにある。
Excel操作系(POI, XLSBeans) 使う (未だにJavaExcel操作のデファクトなんでしょうか…)
JBoss Seam 2.2 使わない JSF, CDIに置き換える。
RichFaces 使わない 結局ほとんど使わずjQueryプラグインを使ってしまっているので…。
ロガー周り(SLF4j + Logback) 要検討 WildFly 8.1.0.Finalでのログ出力の王道に合わせる。

半分近くが使わないことになりそうですね。ただし、使用中のライブラリについては上記の通りですが、アップデートにあたり、Lombok、PicketLinkなどは新たに使っていく予定なので、トータルとしては同じくらいの依存関係の数になりそうです。

プレゼンテーション層にあたるSeam, RichFacesのところは、Java EE 7でどう変更するかについて、別のエントリで書いていきます。

*1:今でこそGradleでビルドしていますが、昔はIvyやMavenすら使わない生Antで、全ての依存jarをlibsフォルダに置いてバージョン管理して、みたいなことをやっていたので、それはもう面倒でした…。

Java EE 7アプリケーション開発のスタートアップ

前日のエントリで予告しておいたJava EE 7アプリケーション開発のプロジェクトひな形ですが、「結合テストとワンクリックデプロイがすぐに使えるJava EE 7開発のためのbuild.gradle」としてQiitaに投稿したので、そちらをご覧ください。

こういうベースになるビルドスクリプトがあると便利、というのは、 id:nekop さんのJava EE / ArquillianのMaven最小設定の影響をかなり受けています。

Eclipseでの開発環境

おまけで、Java EE 7での開発をEclipseで行うための環境構築についての雑多な記録です*1

インストール

  • 利用するEclipseJava EEアプリを開発するので「Eclipse IDE for Java EE Developers」を選びましょう。
  • バージョンはJava8に対応しているEclipse 4.4 Luna以上を選びましょう。
  • インストールしておくとよいプラグイン
    • JBoss Tools: EclipseWildflyを使うならほぼ必須でしょう。
    • Gradle IDE: Gradle Viewを使って、EclipseからGradleタスクを簡単に実行できるようになります。

プロジェクトの設定

プロパティの「Project Facets」で構成をカスタマイズしておきます。

設定されているバージョンはどれもJava EE 6頃のものなので、最新のものに変更します*2

f:id:jappy:20140928040509p:plain

この時、JSFのバージョンを初期値の2.0から2.2(や2.1)に変更しようとするとエラーになり、Eclipse上から変更できませんでした。そこで、(強引な手段ですが)直接プロジェクトの .settings/org.eclipse.wst.common.project.facet.core.xml を編集して対応しました。

before:

   <installed facet="jst.jsf" version="2.0"/>

after:

   <installed facet="jst.jsf" version="2.2"/>

不具合

ところで、少し前までは Eclipse 4.4.0 を使っていたのですが、JSF2.2に対応した最新のWTP(Web Tools Platform)3.5を使うために最新の4.4.1にアップデートしたところ、Gradle IDEプラグインがうまく動かなくなりました(Gradle ViewでGradleプロジェクトが読み込めずエラーになります)。

f:id:jappy:20140928041505p:plain

ちょうどこの記事を書いている前日に不具合報告が上がってました。一緒にGradle IDEプラグインもアップデートした際に問題になったので、修正されるまでは少し前のバージョンを使ったほうがいいかもしれません。

10/1追記 上記URLにいくつかワークアラウンドが上がっていますが、そのうちの1つとして紹介されているGradle (Wrapper)のバージョンを2.1からNightly Build版の2.2-20140930220016+0000に変更する策を試したところ、エラーが解消されました。非安定版ではありますが、今回は検証目的なので、このバージョンで確認を進めていきます。

参考サイト

参考サイトの例だと、アプリはMavenでビルドして、デプロイやWildflyの起動・停止は「Servers」ビューから行うようになっています。

Gradle版では「Gradle」ビューからGradleタスクとしてデプロイやWildflyの起動・停止を行うことができるので「Servers」にWildflyを追加する設定はなくてもよいかもしれません。

*1:正直なところ、EclipseJava EE 7を開発するのはパワー不足も感じるので、NetBeansIntelliJ IDEAの方が良いかなぁと思っていますが…。

*2:キャプチャ上の設定内容は多分穴があります。。

Java EE 5からJava EE 7へのアップデート

注)このエントリは前置きです。

Java EE 5で作ったアプリケーションをJava EE 7にアップデートすることを検討中。

実は、3年近く前に、Java EE 6 (JBoss AS 7)へのアップデートを試みたのだけど、諸々の事情があって保留になっていました。。

その間に、Java EE 7がローンチされたし、せっかく今やるならJava EE 6ではなくて、きちんとJava EE 7(Java SE 8)に対応しておきたいと思います。

というわけで、Wildfly 8.1.0.Final上で動くJava EE 7アプリケーションについて、色々と検証していきます。

アップデートの進め方

移行する際のガイドラインとしては、こちらが参考になります。

この記事を呼んで変更のポイントをつかんでおくことはもちろん重要なのですが、前回の反省点として、既存のアプリをいきなりWildfly 8.1.0.Finalに載せて、動くようになるまでカスタマイズしていく方法は茨の道のように感じてます。

というのも、実際にやるとなると、アプリの作りによって記事に紹介されていないようなカスタマイズが必要になることもあり、「デプロイ→エラー→原因分析・修正」のサイクルをまともに動くようになるまで繰り返す必要があるからです(そもそもアプリの作りが悪い、ということも原因なのですが…)。

というわけで、既存のアプリのことはとりあえず忘れて、小さく動くJava EE 7アプリケーションを作って、きちんと動く状態を保ちつつ、既存のアプリを少しずつ移植していくアプローチを採用したいと思います。

Java EE 7アプリケーションのひな形を作る

まずはWildfly 8.1.0.Finalで動くJava EE 7アプリケーションのひな形を作ります。

やるべき(やりたい)こと:

  • 自動化された結合テストの仕組みを最初の段階で用意する。
  • 普段の開発をさくさく行うために、一発でデプロイ(ホットデプロイ)、アンデプロイができるようにする。
  • ビルドやデプロイは、メンテナンスとカスタマイズのしやすさから、MavenではなくGradleを使う。
    • プロジェクトの構成はMavenやGradleの標準レイアウトに準じる。
    • 公開されているドキュメントやサンプルは基本的にMavenを前提として書かれているので、悩ましいところではあるのですが…。
  • アプリケーションのパッケージングはEARでなくてWAR
    • Java EE 5の時は必然的にEARでパッケージングしてましたが、EJBもwarに含めることができるようになった昨今、あえてEARでパッケージングする必要ってあるのかな…?

次回からのエントリで、詳しい内容について書いていきます。

参考サイト

最近のJava関係イベントの個人的なまとめと感想

デブサミJava Day Tokyoなど、会社の許可をもらってイベントにありがたく行かせてもらっているけど、ちゃんとレポートできていなかったということで、社内勉強会で話すことにしました。

まとめ資料を作ったので、このブログにて先行して公開してしまいます*1少し前のエントリで資料をアップロードしたのはこのための準備でした。

資料の位置づけ

古いJavaの知識で止まっている人(自分含め)やこれから本格的にJavaを学ぼうと思っている人に、最近のJava関連の新機能などをざっくりつかんでもらうための資料です。内容の深さ・広さは、聴く人のJavaスキルに合わせた一応合わせたつもりです。

Java SE 8がリリースされて、その界隈では「for文禁止」などと言われる一方で、会社の新人研修で教えるJavaでは、「ループはforやwhileを使って書きます」みたいに教えるわけで、その辺のギャップを埋める助けになればいいなぁと思っています。

と言いながら、何より、この資料を作るにあたり、色々と調べたり *2NetBeansでせっせと検証コードを書いて試したりしたので、自分の勉強になりました。

2014-06-17 追記

yamadamnさんの指摘を受けて、GlassFishの商用サポートに関する記述、ラムダ式の説明のスライド(と細かいtypo)を修正しました。
yamadamnさん、ありがとうございました。

*1:会社の情報は出ていないし、業務時間に作ったわけではなく休日に個人の活動として作ったものなので、問題はあるまい、うん。

*2:JSRも恥ずかしながら初めてきちんとダウンロードして目を通しました…。

Redmineのカスタムクエリを少し便利にするプラグイン

会社の業務で必要に迫られているものとは別に、Redmineプラグインを作りました。

Redmine Query with Version Plugin

プラグインの概要

カスタムクエリと任意のバージョンの組み合わせによるチケット検索を可能にするプラグインです。

特定のバージョンのチケットだけを抽出するカスタムクエリをよく作るんですが、バージョンごとにカスタムクエリを作るとなると、素のRedmineでは、以下の理由で大変です*1

  • 既存のカスタムクエリをコピーしての作成ができないので、一からカスタムクエリを作成しないとならない*2
  • カスタムクエリの数が増えてくるとサイドバーがゴチャゴチャしてきて、目的のカスタムクエリが探しにくい。

そこで、ベースになるカスタムクエリだけを用意しておき、バージョンに関するフィルタ条件だけは切り出してチケットの検索が行えるようにするのが、このプラグインの用途です。

最低限使える形にはなったので0.1.0として公開しているのですが、もう少し手を加えて、本家に登録したいなぁ…。
Redmineプラグインのライセンスの扱いってどうするのがよいのだろう…?公開されているプラグインを見ると、ライセンスについて明記されているプラグインの中ではGPLとMITが6:4くらいだけど…。

*1:よく使っているRedmineのバージョンが2.3.0なので、新しいバージョンだと事情が違うかも。。

*2:別途Query Duplicate Pluginを導入することでこちらの問題は軽減可能ですが。

Qiitaのブログパーツのカスタマイズ&設置

伊藤さんのブログの「Qiitaのブログパーツをはてなブログのサイドバーに設置する方法」を参考に、便乗してこのブログのサイドバーにもQiitaのブログパーツを貼り付けてみました。

同じく、@suinさんの作られているQiita Widgetをカスタマイズしています。

カスタマイズしたのは以下の点。

  • 表示件数を10件→5件に。
  • フォントのスタイルを変更。font-familyはこのブログのfont-familyをそのまま利用し、非boldにしました。
  • ブログパーツのヘッダ部分のバーを削除。削除した経緯:
    1. font-familyを定義したら、コンパイル後の script.js がエラーになってしまった*1
    2. そこで、 script.js の圧縮は行わないようにした。
    3. 圧縮を行わないので、なるべくスクリプトサイズを抑えるため、バーを削除*2

フォークしたリポジトリのREADMEに、コンパイル手順のメモを書いています。

https://github.com/tq-jappy/qiita-widget

ちなみに、ChromeFirefoxでしか確認していないので、IEでは見られるか不明…。また後で直そう…。

Qiita Widgetを作られた@suinさん、参考にさせていただいた伊藤さん、ありがとうございました。

*1:詳しく見ていないのですが、圧縮後のscript.jsに対して<!--%css%-->を置換するところでポカしてそうなので、単純にlessの書き方が悪いだけの可能性大です。

*2:バーに表示するQiitaのロゴ(Base64での画像埋め込み)が比較的容量を食っていたので。