1. 概述

本文将深入探讨表达式语言 3.0(EL 3.0)的最新特性、改进和兼容性问题。这是截至本文发布时的最新版本,已集成在较新的 JavaEE 应用服务器中(例如 JBoss EAP 7 和 Glassfish 4 均已实现支持)。

本文聚焦 EL 3.0 的新发展——若需了解表达式语言的基础知识,建议先阅读 EL 2.2 版本 的文章。

2. 前置条件

本文示例已在 Tomcat 8 环境下测试通过。要使用 EL 3.0,需添加以下依赖:

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

可通过此 链接 查找 Maven 仓库中的最新依赖版本。

3. Lambda 表达式

最新版 EL 对 Lambda 表达式提供了强大支持。虽然 Lambda 是 Java SE 8 引入的,但 EL 中的支持随 Java EE 7 一起到来。其实现功能完备,为 EL 的使用和评估提供了极大的灵活性(但也隐含风险)。

3.1. Lambda EL 值表达式

基础用法允许在 EL 值表达式中指定 Lambda 表达式作为值类型:

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

进一步扩展,可在 EL 中命名 Lambda 函数以便在复合语句中复用,类似 Java SE 中的 Lambda 表达式。复合 Lambda 表达式可用分号(;)分隔:

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

此代码片段将函数赋给 cube 标识符,使其立即可复用。

3.2. 将 Lambda 表达式传递到后端 Bean

更进一步:通过将逻辑封装为 EL 表达式(Lambda 形式)并传递给 JSF 后端 Bean,可获得极大灵活性:

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

这允许我们将整个 Lambda 表达式作为 javax.el.LambdaExpression 实例处理:

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

此特性极具价值,支持:

  • 逻辑封装:提供灵活的函数式编程范式。后端 Bean 逻辑可根据不同来源的值动态调整
  • 兼容性方案:为尚未升级到 JDK 8 的旧代码库引入 Lambda 支持
  • 流式处理:结合新的 Streams/Collections API 实现强大功能

4. 集合 API 增强

早期 EL 版本对集合 API 的支持较为薄弱。EL 3.0 在 Java 集合支持方面进行了重大改进,并像 Lambda 表达式一样,在 Java EE 7 中提供了 JDK 8 流式处理支持。

4.1. 动态集合定义

3.0 新特性:可在 EL 中动态定义临时数据结构:

  • 列表

     <h:dataTable var="listItem" value="#{['1','2','3']}">
         <h:column id="nameCol">
             <h:outputText id="name" value="#{listItem}"/>
         </hcolumn>
     </h:dataTable>
    
  • 集合

     <h:dataTable var="setResult" value="#{{'1','2','3'}}">
      ....
     </h:dataTable>
    

    ⚠️ 注意:与普通 Java Set 一样,元素顺序不可预测

  • 映射

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

    💡 技巧:定义动态映射时常见错误是使用双引号(")而非单引号作为键,这会导致 EL 编译错误

4.2. 高级集合操作

EL 3.0 支持高级查询语义,结合了 Lambda 表达式、新流式 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>

此表格使用传入的 Lambda 表达式过滤后端列表

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

输出文本 avg 将计算列表中数字的平均值。这些操作通过新的 Optional API 实现空安全(相比旧版本的改进)。

⚠️ 重要:此功能无需 JDK 8,仅需 JavaEE 7/EL 3.0。这意味着你可以在 EL 中执行大部分 JDK 8 的 Stream 操作,但在后端 Bean 的 Java 代码中不行。

💡 技巧:使用 JSTL 的 <c:set/> 标签将数据结构声明为页面级变量,便于在整个 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 常量字段(或枚举):

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 运行时初始化的一部分执行。

需注意:

  • 语法要求:字段和方法必须是 publicstatic(方法还需 final
  • 过时语法:EL 3.0 规范初稿与最终版的语法不同。某些教材可能仍使用:
    T(YourClass).yourStaticVariableOrMethod
    
    此语法在实际环境中无效(实现周期后期决定简化语法)
  • ⚠️ 版本问题:最终发布的语法仍存在 bug,请确保运行最新版本

6. 总结

我们探讨了最新 EL 实现的亮点。主要改进为 API 带来了 Lambda 和流式处理等强大新特性。

随着 EL 灵活性的提升,需牢记 JSF 框架的设计目标之一:通过 MVC 模式实现关注点清晰分离。因此需注意,API 的最新改进可能导致 JSF 中出现反模式——因为 EL 现在能处理实际业务逻辑(能力远超以往)。在实际实现中,务必确保职责清晰分离。

本文示例可在 GitHub 获取。


原始标题:Guide to JSF Expression Language 3.0