Java EE 8で何が変わったのか?

Java EE Advent Calendar 2017の23日目です。昨日は@suke_masaさんの「Java EEにMVCはなぜ必要なのか」でした


はじめに

今年はJavaOne 2017で1枠、Oracle OpenWorld 2017で1枠、Japan Sessionで1枠と、3枠の公演をしてきました。その共有会を福岡で12月に実施したのですが、そこで取り扱ったJava EE 8での差分について、改めてこちらにまとめておきます。

なお、類似の話は恐らく各所にあると思うので、読み込んでいるプロな方には今更感が満載だと思います。が、Advent Calendarはそういう趣旨では無いと思いますので、まあこういう記事があってもいいじゃないですかね。という感じで。

Java EE 8仕様が出ました

まず、今年のJavaOne 2017 (のちょっと前に)でJava EE 8がリリースされました。Java EEは特定の製品でも何でもなく「アプリケーションサーバーの標準仕様」となります。

※なので軽い気持ちで「Java EEは品質が悪い」とか「遅い」とか放言すると、周りの技術者から冷酷な目で見られるので注意しましょう(笑)

そのため、Java EE 8仕様に準拠したアプリケーションサーバーはこれから登場します。Java EE仕様策定に参加している会社・コミュニティー間で合意が取れ、標準仕様が決まったばかりですので。現時点ではとりあえず参照実装の位置づけであるGlassfish 5がリリースされています。また、商用製品としては、製品版以前の状態としては、Payara 5 Beta 1がリリースされています。

※当然ですが、実稼働システムを検討する場合、Glassfishは絶対に選ばないで下さい。セキュリティーパッチが出ません。 これは旧4系でも同じです。速やかに商用版であるPayaraもしくは別の製品に移行しましょう。

なお、Java EEの開発はこのあとEE4Jというプロジェクトに移管されることになっていますが、この件は本記事とあまり関係ないので飛ばします。Java EE 8より先の話です。

Java EE 8の差分について

Java EE 8仕様は、1999年末に出たJ2EE 1.2仕様から数えて6回目のメジャーバージョンアップとなります。メジャーバージョンアップなので各種の改善と非互換が出ています。本記事の要旨はここになります。

なお、Java EE仕様の暗黙のルールとしては、「過去の技術は二つ前の仕様までサポートする」となります。例えば「Java EE 8仕様に準拠したアプリケーションサーバーは、Java EE 6仕様に準拠したアプリケーションを動かすことが出来る 」というものです。Java EE 6は2009年末に出ていますので、約8年間以上は大丈夫、という実績があります。まあ、古いアプリケーションサーバーを使えばもっと行けるので、10年ちょいは間違いなく大丈夫でしょう。Java EE仕様でアプリケーションを作成する場合は、できるだけ最新仕様を使うようにしましょう。

これは裏を返せば、Java EE 8仕様のアプリケーションサーバーは遂にJava EE 5仕様のアプリケーションが動かなくなります。Java EE 5仕様自体は2010年頃まで普通に使われていましたので、移行を検討しましょう。商用アプリケーションサーバーによっては、ひょっとしたらサポートしてくれるものがあるかも知れませんが分かりません。もしくは、Java EE 5が動くアプリケーションサーバーを後生大事に取っておくか、です。EOLの後でも数百万~数億となる大量のお金を投入すれば可能でしょう。この領域には余り興味がありませんが・・・

※ちなみにWikipediaのJava EEの項目を見たところ壮絶に古い記載で今となっては不適切だったりするので、そのうち書き換えます。

さて、そんなJava EE 8ですが、EE 7との差分がどれだけあるかというと、然程ありません。それだけEE 7仕様が優れていたということでもあるのですが、要は明らかに足りなかった機能を追加しました、という感じのメンテナンスリリースと捉えてもらえれば間違いないかなと思います。なのでエンプラ界隈が「Java EE 8が出た! これからはEE 8で未来が変わる!」というような感じになっていないのは正しい反応です。

ちなみにJava EE 8は本来、去年のJavaOne 2016で発表予定だったのですが、急遽延期になってしまった経緯があります。その影響か、Java EE 8ネタでJavaOne 2016のCall for Proposalに応募していた末席の私はもちろん、その界隈で著名な方々(Oracle社員除く)も丸ごと落とされてしまい、人知れず枕を涙で濡らしたのでした。そんな遺恨もあり、界隈のJava EE 8への視線は更に厳しいものになったのですが、実に余談なのでこれ以上は止めておきましょう。

さて、次からは技術記事らしく、中身を見ていきましょう。

Java EE 8の変更点1: Deployment Descriptorの変更

Deployment Descriptor (DD)と書くとEJB 2系以前に必須であったDDであるejb-jar.xmlを思い出して拒絶反応を示す人が未だに多いですが、それだけではなくWARの中にあるweb.xmlも、JPAで使うpersistence.xmlも立派なDDなので、混同しないようにしましょう。ちなみに、ejb-jar.xmlはEJB用としてまだ使えますが、アノテーションで代替出来るため不要となりました。もしあれば、アノテーションより優先して扱われるものとなっています。

さてこのDDですが、XMLであるため冒頭にXML SchemaのXSDを指定するようになっています。こんなのどうでもいいじゃん、と思いがちですが、実はアプリケーションサーバーはこのXSDを参照して「このアプリケーションはどのJava EE仕様に則って作られているのか」を判断しています。例えばweb.xmlを古いまま使い回しているWARであれば、古いままのJava EE仕様で動作してしまうということです。新機能を使おうと思ったんだけど呼び出されない、等々で何日も悩む羽目に陥らないよう、まず最初に書き換えましょう。

例:  web.xml

<web-app xmlns=http://xmlns.jcp.org/xml/ns/javaee 
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance 
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" 
version="4.0" metadata-complete="true">
...
</web-app>

例えば上記のような感じです。一覧はこちらにありますのでどうぞ。 http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/javaee/index.html

Java EE 8の変更点2: Security APIの追加

これは大きい変更です。Webシステムでログオン有無の管理は従来自前で処理するものでしたが、他のプラットフォームではあって当たり前な感じにもなっています。何で無いのよ、と訝しげに思う人も居たと思いますが、遂に追加されましたよ!

UsernamePasswordCredential cred 
        = new UsernamePasswordCredential(
		                entity.getUserName(), entity.getPassword());
AuthenticationParameters params
        = AuthenticationParameters.withParams().credential(cred);
AuthenticationStatus status 
        = securityContext.authenticate(httpServletRequest,
		                httpServletResponse,params);

プログラム上のAPIを呼び出すコードは上記の感じで簡単ですが、問題はこのコードがどう動くべきか、をカスタマイズなり設定なりが必要となります。そりゃぁ、企業システムのID管理なんて千差万別な訳ですよ。Active Directoryだったり生LDAPだったりデータベース+JSONだったりと。対象が社員だったり顧客だったりするとまあ更にバリエーション豊かになります。そういうのを吸収するAPIとして制定されているようです。

例えばデータベースへ問い合わせする場合は、このようなアノテーションをAppliationConfig.java辺りに書く感じです。これなら分かりやすいかな?

@DatabaseIdentityStoreDefinition(
    dataSourceLookup="java:global/MyDS", 
    callerQuery="select password from user where user_name = ?",
    groupsQuery="select group_name from user_groups where user_name = ?"
)
@ApplicationScoped
@Named
public class ApplicationConfig {
    ...
}

なお、標準機能としてはLDAP接続とデータベース接続辺りがサポートされていて、更にカスタムで接続を実装することが可能となっています。なのでどんな認証でもこのAPIで吸収することが理論的に可能と考えられます。恐らくこのあと実装例が多々出てくると思いますので、それを待っててもいいかもですね。

詳しくは@kodukiさんの「Java EE Security APIが街にやってきた!」でもっと詳しくありますので、気になる方はそちらへどうぞ。

Java EE 8の変更点3: JSON-Bの追加

つぎ、JSONとJavaプログラムを相互に変換するAPI、通称JSON-Bが追加されました。これ、慣れてる人だと、Java EE 7でCDIがREST対応した際にもJSONは使えたので、何を今更? という感じではあるかもですが、正式な単独仕様として追加されました。JavaオブジェクトからJSONへの変換は特にRESTの呼出し元側で必要となったりするので、この仕様は特にJavaFXや単独Javaアプリケーション辺りで特に使えるのではと感じます。

たとえばこんなJavaのクラスに値が詰まってたとして

public class Dog {
     public String name;
     public int age;
     public boolean bitable;
}

これをこんなJSONに変換したい、となると、

{
"name": "Falco",
"age": 4,
"bitable": false
}

こんなコードだけで行けちゃいます。

// Serialize. Java to JSON
Jsonb jsonb = JsonbBuilder.create();
String dogJsonResult = jsonb.toJson(dog);

// Deserialize. JSON to Java
String dogJson = "{\"name\":\"Falco\",\"age\":4,\"bitable\":false}";
Dog deserializedDog = jsonb.fromJson(dogJson, Dog.class);}

便利ですねぇ。便利すぎてアホになっちゃいそうです。入出力の細かい制御をしたければ更に色々出来ますが、通信相手側のの謎仕様JSONに合わせねばならない場合以外にはあまり使い道が無さそうではあります。

Java EE 8の変更点4: CDI 2.0

CDI (Contexts and Dependency Injection)がメジャーバージョンアップしました。要は昔流行ったDI(依存性注入)やアスペクト指向です。まあ、バズワードの一種ではありましたが、一部便利になったことは間違いないでしょう。Java EE内の位置づけとしてはポストEJBという感じで徐々にEJBの機能を絶賛移植中です。もっとドバーと一気に移植しちゃえばいいのにと思わなくも無いですが、そこまで重量級のものも要らないよね、ってことなのかも知れません。

まず追加されたのが、非同期イベントが使えるようになりました。EJBの@Asynchronous相当と思えばいいですが、もうちょっとややこしい感じです。EJBのように呼ばれ側で書くんじゃなくて、呼び元で書くという感じで、逆になってます。event.fireAsync() というメソッドを使います。

次に、EJBTimerに相当するスケジューラーをCDIからも使えるようになりました。ついでにEJBの@Startupも使えるようになりました。これはCDIコンテナーが初期化するタイミングで最初に呼ばれるメソッドです。初期化処理はここでやりましょう。

最後に、これが一番大きいのですが、Java SE環境でCDIが使えるよう仕様として公式にサポートされました。これまでも実装側で対応してたと思いますが、公式サポートは大きい。これで大手を振ってJava SE環境、特にJava SE環境で自己起動するような軽量なバッチ処理や、クライアント系アプリケーション、例えばJavaFX環境などでCDIを思う存分使いこなせるでしょう。

とりあえず動かしたい場合は、Mavenにこれを追加して・・・

        <dependency>
            <groupId>org.jboss.weld.se</groupId>
            <artifactId>weld-se-core</artifactId>
            <version>3.0.2.Final</version>
        </dependency>

こんな空のMETA-INF/beans.xmlを追加して・・・

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
       bean-discovery-mode="annotated">
</beans>

こんなコードで動きます。

public static void main(String... args) { 
    try (SeContainer container
             = SeContainerInitializer.newInstance().initialize()) {
        MyBean myBean = container.select(MyBean.class).get();
    
        myBean.doWork(); // DO SOMETHING.
    }
}

一回CDIを呼び出しちゃえばその先はCDIワールドなので@Injectでインジェクションしまくっちゃって良い感じかと。上記のコードだと、myBean.doWork()の先からめくるめくCDIワールドです。CDIにはとりあえず@Dependentもしくはスコープのアノテーションを必ず付けましょう(CDI 1.1以降の約束)。

Java EE 8の変更点5: Servlet 4.0

これは純粋にベース技術の更新であるHTTP/2にやっと追随しました、というものですね。他のプラットフォームは早々と対応していたので、「今頃!?」という感じでもあります。周回遅れ感が辛いところではありますが、もっと早く仕様が決まるような新体制になってくれればなぁと祈念致します。

@WebServlet(value = {"/http2"}) 
public class Http2Servlet extends HttpServlet {
    @Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        req.newPushBuilder()
            .path("images/file.png")
            .addHeader("content-type", "image/png")
            .push();
        try (PrintWriter respWriter = resp.getWriter()) {
	            respWriter.write("<html><img src='images/file.png'></html>");
        }
    }
}

Java EE 8の変更点6: JSF 2.3

これは最もデカい更新です。書くのが多すぎてどれから書くべきか悩むくらいありますが、順番に。

まず、Java EE 5時代の懐かしのJSF Managed Beanが遂にDeprecatedになりました。実にめでたい話です。今や何のメリットも無いので、それらはCDIに書き換えましょう。一対一で書き換えられるので、特に悩むこともないですし、一気にスクリプトで書き換えるのもありかも知れません。ただ、JSFのイベント内やFacesServletの前にFilterを置いて、仕様に無いようなハッキングに近い処理をやっていた場合は、その部分の全面書き直しになります。いずれにせよ、移行には充分なテストを行うようにしましょう。

次に、<f:event type=”postRenderView/> が追加され、遂に最後のイベントが拾えるようになりました。これは画面描画が終わった後のイベントなので、何か後処理が必要だったりする場合限定です。なくても困らないですが、ないより有る方が良いですね。

細かな改善系だと、セッション周りとの相性が最悪だったAjax関連機能の改善があります。壮絶に頭の悪い仕様だったラジオボタンもレイアウティングに対応し、やっと使えるようになってくれました。他にもInjectできる数が増えたという嬉しい話があります。特に使用頻度の高いと思われるExternalContext辺りがこんなコードで拾えるようになりました。遅いよ・・・

@Inject private ExternalContext context;

 

Artefact EL name Qualifier Type
Application #{application} java.lang.Object (javax.servlet.ServletContext)
ApplicationMap #{applicationScope} @ApplicationMap java.util.Map<String, Object>
CompositeComponent #{cc} (Not injectable) javax.faces.component.UIComponent
Component #{component} (Not injectable) javax.faces.component.UIComponent
RequestCookieMap #{cookie} @RequestCookieMap java.util.Map<String, Object>
FacesContext #{facesContext} javax.faces.context.FacesContext
Flash #{flash} javax.faces.context.Flash
FlowMap #{flowScope} @FlowMap java.util.Map<Object, Object>
HeaderMap #{header} @HeaderMap java.util.Map<String, String>
HeaderValuesMap #{headerValues} @HeaderValuesMap java.util.Map<String, String[]>
InitParameterMap #{initParam} @InitParameterMap java.util.Map<String, String>
RequestParameterMap #{param} @RequestParameterMap java.util.Map<String, String>
RequestParameterValuesMap #{paramValues} @RequestParameterValuesMap java.util.Map<String, String[]>
Request #{request} (Not injectable) java.lang.Object (javax.servlet.http.HttpServletRequest)
RequestMap #{requestScope} @RequestMap java.util.Map<String, Object>
ResourceHandler #{“resource”} javax.faces.application.ResourceHandler
Session #{session} (Not injectable) java.lang.Object (javax.servlet.http.HttpSession)
SessionMap #{sessionScope} @SessionMap java.util.Map<String, Object>
View #{view} javax.faces.component.UIViewRoot
ViewMap #{viewScope} @ViewMap java.util.Map<String, Object>
ExternalContext #{externalContext} (new) javax.faces.context.ExternalContext

あと、新しい技術への対応としては、WebSocket対応 (<f:websocket>、PushContextあたり)もあります。

また、基礎技術面ではJava SE 8にもようやく対応しました。例えばNew date time APIに対応した、何故か対応していなかったExternalContext#getInitialParameterMapやらConverterやらValidatorやら。

そんなこんなで大量改善があったJSFで、これだけで1冊本が書けそうです。Web系は動きが速いので、この調子でガンガン更新していって欲しいものです。もっと激しくてもいいくらい。

Java EE 8の変更点7: JPA 2.2

さて、最後にJava Persistence API 2.2、つまりデータベースとのやりとりの場所です。ちょっとした改良に止まっています。

まず、Java SE 8対応、それに伴ってNew date time APIに対応しました。ので、やっとJDK 1.1~1.2頃に整備されたカビの生えたようなjava.sql.Dateやjava.util.Timestamp辺りともおさらばです。新たにLocalDate, LocalTime, LocalDateTime, OffsetTime, OffsetDateTime辺りが日付型カラムとの自動マッピング先として使えるようになりました。

また、Java SE 8関連として、Repeatable annotationに対応しています。これはひとつのメソッドに同じアノテーションを複数書けるようにしました、というちょっとした小改良に追随したものです。プチ改良。

あとはAttribute ConverterでCDIがインジェクト出来るようになったとか、ちょっとしたメソッドが追加されたとか、その程度です。

まとめ

さて、ここまでざーっと見てきました。華々しいJSFの改良と比較して、それ以外は実質的に小改良に留まっています。要は今Java EE 7で開発を進めている方はそんなに心配せずとも、後でゆるりとEE 8対応、でもいいんじゃないかな、と思います。そもそもまだ商用アプリケーションサーバーがひとつもEE 8対応していません。代表的なJava EE対応IDEであるNetBeansも本稿記述時点で未対応です(Githubからの要自前clone & ビルド)。

元々、Java EEの最新版はリリース後1年くらい経過してから採用を、という流れですので、ここは慌てず、Glassfish 5やPayara 5 Beta辺りを使って、じっくりとバージョンアップのためのプロトタイピングを進めておくのが得策でしょう。来年のJavaOne 2018 (があれば) で商用サーバーがひととおり揃うのではないかと推測します。楽しみですね。

 

明日は@khasunumaさんの「これから Java EE を始める方へ」です。