1. 概要

この記事では、Expression Languageバージョン3.0(EL 3.0)の最新機能、改善点、および互換性の問題について説明します。

これは、この記事の執筆時点での最新バージョンであり、より新しいJavaEEアプリケーションサーバーに同梱されています(JBoss EAP7およびGlassfish4は、このバージョンのサポートを実装した良い例です)。

この記事はEL3.0での開発のみに焦点を当てています。一般的な式言語の詳細については、最初にELバージョン2.2の記事をお読みください。

2. 前提条件

この記事に示されている例は、Tomcat8に対してもテストされています。 EL3.0を使用するには、次の依存関係を追加する必要があります。

<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>3.0.0</version>
</dependency>

このリンクをたどると、Mavenリポジトリで最新の依存関係をいつでも確認できます。

3. ラムダ式

最新のEL反復は、ラムダ式の非常に堅牢なサポートを提供します。 Lambda式はJavaSE8に導入されましたが、ELでのサポートはJavaEE7に付属しています。

ここでの実装はフル機能であり、ELの使用と評価に多くの柔軟性(およびいくつかの暗黙のリスク)を可能にします。

3.1. ラムダEL値式

この機能の基本的な使用法により、EL値式の値型としてラムダ式を指定できます。

<h:outputText id="valueOutput" 
  value="#{(x->x*x*x);(ELBean.pageCounter)}"/>

それを拡張すると、Java SEのラムダ式の場合と同じように、ELでラムダ関数に名前を付けて複合ステートメントで再利用できます。 複合ラムダ式はセミコロン(; )で区切ることができます。

<h:outputText id="valueOutput" 
  value="#{cube=(x->x*x*x);cube(ELBean.pageCounter)}"/>

このスニペットは、関数を cube 識別子に割り当てます。この識別子は、すぐに再利用できます。

3.2. ラムダ式をバッキングBeanに渡す

これをもう少し進めてみましょう。ロジックをEL式に(ラムダとして)カプセル化し、それをJSFバッキングbeanに渡すことで、多くの柔軟性を得ることができます。

<h:outputText id="valueOutput" 
  value="#{ELBean.multiplyValue(x->x*x*x)}"/>

これにより、ラムダ式全体をjavax.el.LambdaExpressionのインスタンスとして処理できるようになりました。

public String multiplyValue(LambdaExpression expr){
    return (String) expr.invoke( 
      FacesContext.getCurrentInstance().getELContext(), pageCounter);
}

これは、次のことを可能にする魅力的な機能です。

  • 非常に柔軟な関数型プログラミングパラダイムを提供する、ロジックをパッケージ化するためのクリーンな方法。 上記のバッキングBeanロジックは、さまざまなソースから取得された値に基づいて条件付きである可能性があります。
  • アップグレードの準備ができていない可能性のあるJDK8より前のコードベースにラムダサポートを導入する簡単な方法。
  • 新しいStreams/CollectionAPIを使用するための強力なツール。

4. コレクションAPIの機能強化

ELの以前のバージョンでのCollectionsAPIのサポートは、やや欠けていました。 EL 3.0は、Javaコレクションのサポートに主要なAPIの改善を導入しました。ラムダ式と同様に、EL3.0はJavaEE7内でJDK8ストリーミングサポートを提供します。

4.1. 動的コレクションの定義

3.0の新機能として、ELでアドホックデータ構造を動的に定義できるようになりました。

  • リスト:
   <h:dataTable var="listItem" value="#{['1','2','3']}">
       <h:column id="nameCol">
           <h:outputText id="name" value="#{listItem}"/>
       </h:column>
   </h:dataTable>
  • セット:
   <h:dataTable var="setResult" value="#{{'1','2','3'}}">
    ....
   </h:dataTable>

注:通常のJava セットと同様に、リストされている場合、要素の順序は予測できません。

  • マップ:
   <h:dataTable var="mapResult" 
     value="#{{'one':'1','two':'2','three':'3'}}">
 

ヒント:動的マップを定義する際の教科書でよくある間違いは、マップキーに一重引用符ではなく二重引用符(“)を使用することです。これにより、ELコンパイルエラーが発生します。

4.2. 高度な収集操作

EL3.0では、ラムダ式の能力、新しいストリーミングAPI、および結合やグループ化などのSQLのような操作を組み合わせた高度なクエリセマンティクスがサポートされています。 これらは高度なトピックであるため、この記事では取り上げません。 その力を実証するためにサンプルを見てみましょう:

<h:dataTable var="streamResult" 
  value="#{['1','2','3'].stream().filter(x-> x>1).toList()}">
    <h:column id="nameCol">
        <h:outputText id="name" value="#{streamResult}"/>
    </h:column>
</h:dataTable>

上記の表は、渡されたラムダ式を使用してバッキングリストをフィルタリングします

 <h:outputLabel id="avgLabel" for="avg" 
   value="Average of integer list value"/>
 <h:outputText id="avg" 
   value="#{['1','2','3'].stream().average().get()}"/>

出力テキストavgは、リスト内の数値の平均を計算します。 これらの操作は両方とも、新しいオプションのAPI (以前のバージョンのもう1つの改善)によってヌルセーフです。

これをサポートするためにJDK8は必要なく、JavaEE 7/EL3.0だけが必要であることに注意してください。 つまり、ほとんどのJDK 8 Stream 操作はELで実行できますが、バッキングbeanJavaコードでは実行できません。

ヒント: JSTLを使用できますタグを使用してデータ構造をページレベルの変数として宣言し、代わりにJSFページ全体でそれを操作します。

 <c:set var='pageLevelNumberList' value="#{[1,2,3]}"/>

これで、正真正銘のJSFコンポーネントまたはBeanのように、ページ全体で「#{pageLevelNumberList}」を参照できます。 これにより、ページ全体でかなりの量の再利用が可能になります

<h:outputText id="avg" 
  value="#{pageLevelNumberList.stream().average().get()}"/>

5. 静的フィールドとメソッド

以前のバージョンのELでは、静的フィールド、メソッド、または列挙型アクセスはサポートされていませんでした。 世の中変わったんだよ。

まず、定数を含むクラスをELコンテキストに手動でインポートする必要があります。 これは理想的にはできるだけ早く行われます。 ここでは、JSF管理のbeanの @PostConstruct イニシャライザーで実行しています( ServletContextListener も実行可能な候補です)。

 @PostConstruct
 public void init() {
     FacesContext.getCurrentInstance()
       .getApplication().addELContextListener(new ELContextListener() {
         @Override
         public void contextCreated(ELContextEvent evt) {
             evt.getELContext().getImportHandler()
              .importClass("com.baeldung.el.controllers.ELSampleBean");
         }
     });
 }

次に、目的のクラスで String constantフィールド(または Enum を選択した場合)を定義します。

public static final String constantField 
  = "THIS_IS_NOT_CHANGING_ANYTIME_SOON";

その後、ELの変数にアクセスできるようになります。

 <h:outputLabel id="staticLabel" 
   for="staticFieldOutput" value="Constant field access: "/>
 <h:outputText id="staticFieldOutput" 
   value="#{ELSampleBean.constantField}"/>

EL 3.0仕様に従い、java.lang。*以外のクラスは、図のように手動でインポートする必要があります。 これを行って初めて、クラスで定義された定数がELで使用可能になります。 インポートは、JSFランタイムの初期化の一部として実行されるのが理想的です。

ここでいくつかの注意が必要です。

  • 構文では、フィールドとメソッドが public、static (およびメソッドの場合は final )である必要があります。
  • EL3.0仕様の初期ドラフトとリリースバージョンの間で構文が変更されました。 そのため、一部の教科書では、次のようなものがまだ見つかる場合があります。
    T(YourClass).yourStaticVariableOrMethod

    これは実際には機能しません(構文を単純化するための設計変更は、実装サイクルの後半で決定されました)

  • リリースされた最終的な構文にはまだバグがあります。これらの最新バージョンを実行することが重要です。

6. 結論

最新のEL実装のハイライトのいくつかを検討しました。 ラムダやストリームの柔軟性などのクールな新機能をAPIにもたらすために、主要な改善が行われました。

ELに現在ある柔軟性により、JSFフレームワークの設計目標の1つであるMVCパターンの使用による関心の分離を覚えておくことが重要です。

したがって、APIの最新の改善により、JSFのアンチパターンが可能になる可能性があることに注意してください。これは、ELが以前よりも実際のビジネスロジックを実行できるようになったためです。 したがって、実際の実装では、責任が適切に分離されていることを確認するために、このことを念頭に置くことが重要です。

そしてもちろん、記事の例はGitHubにあります。