【问题标题】:Concatenate strings in JSP EL?在 JSP EL 中连接字符串?
【发布时间】:2010-09-22 17:20:38
【问题描述】:

我有一个 bean 列表,每个 bean 都有一个属性,它本身就是一个电子邮件地址列表。

<c:forEach items="${upcomingSchedule}" var="conf">
    <div class='scheduled' title="${conf.subject}" id="scheduled<c:out value="${conf.id}"/>">
    ...
    </div>
</c:forEach>

这会为列表中的每个 bean 呈现一个 &lt;div&gt;

对于子列表,我想做的是连接列表中的每个条目以形成单个字符串,作为&lt;div&gt;title 属性的一部分显示.为什么?因为我们使用了一个javascript库(mootools)来把这个&lt;div&gt;变成一个浮动的工具提示,而这个库把title变成了工具提示的文本。

所以,如果${conf.subject} 是“主题”,最终我希望&lt;div&gt;title 是“主题:blah@blah.com、blah2@blah2.com 等”,包含子列表的所有电子邮件地址。

如何使用 JSP EL 做到这一点?我试图避免将 scriptlet 块放入 jsp 文件中。

【问题讨论】:

标签: java jsp el taglib


【解决方案1】:

执行此操作的“干净”方法是使用函数。由于 JSTL join 函数不适用于 Collection,因此您可以轻松编写自己的函数,并在整个地方重用它,而不是剪切和粘贴大量循环代码。

您需要函数实现和 TLD 来让您的网络应用程序知道在哪里可以找到它。将它们放在一个 JAR 中并将其放入您的 WEB-INF/lib 目录中。

这是一个大纲:

com/x/taglib/core/StringUtil.java

package com.x.taglib.core;

public class StringUtil {

  public static String join(Iterable<?> elements, CharSequence separator) {
    StringBuilder buf = new StringBuilder();
    if (elements != null) {
      if (separator == null)
        separator = " ";
      for (Object o : elements) {
        if (buf.length() > 0)
          buf.append(separator);
        buf.append(o);
      }
    }
    return buf.toString();
  }

}

META-INF/x-c.tld:

<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0">
  <tlib-version>1.0</tlib-version>
  <short-name>x-c</short-name>
  <uri>http://dev.x.com/taglib/core/1.0</uri>
  <function>
    <description>Join elements of an Iterable into a string.</description>
    <display-name>Join</display-name>
    <name>join</name>
    <function-class>com.x.taglib.core.StringUtil</function-class>
    <function-signature>java.lang.String join(java.lang.Iterable, java.lang.CharSequence)</function-signature>
  </function>
</taglib>

虽然 TLD 有点冗长,但了解如何绕过 TLD 对于任何使用 JSP 的开发人员来说都是一项很好的技能。而且,由于您选择了 JSP 之类的标准来进行演示,因此您很有可能拥有可以帮助您的工具。

与向底层模型添加更多方法的替代方法相比,这种方法具有许多优势。此函数可以编写一次,并在任何项目中重复使用。它与封闭源代码的第三方库一起使用。可以在不同的上下文中支持不同的分隔符,而不会使用新方法污染模型 API。

最重要的是,它支持分离视图和模型控制器开发角色。 这两个领域的任务通常由不同的人在不同的时间执行。保持这些层之间的松散耦合可以最大限度地降低复杂性和维护成本。当即使是在演示文稿中使用不同的分隔符等微不足道的更改都需要程序员修改库时,您的系统就会非常昂贵且繁琐。

StringUtil 类无论是否公开为 EL 函数都是相同的。唯一需要的“额外”是TLD,这是微不足道的;一个工具可以很容易地生成它。

【讨论】:

  • 为什么你认为写一个自定义的jstl函数比在backing bean中多一个方法更好?这不是一个挑衅性的问题。在这种情况下,我会在 bean 中编写一个方法来执行此操作,例如 getInviteesAsString()。这有什么问题?
  • 我添加了对我的回答的回复。我会向你提出同样的问题:为什么你认为更换 backing bean 更好?
  • 我认为它更好,因为它支持视图和模型控制器开发角色的分离,并在视图中留下尽可能少的逻辑。但是,随着您的回答,我知道您的方法也支持这种分离,并且还有其他好处。感谢您的补充。
【解决方案2】:

想出了一个有点肮脏的方法来做到这一点:

<c:forEach items="${upcomingSchedule}" var="conf">
    <c:set var="title" value="${conf.subject}: "/>
    <c:forEach items="${conf.invitees}" var="invitee">
        <c:set var="title" value="${title} ${invitee}, "/>
    </c:forEach>
    <div class='scheduled' title="${title}" id="scheduled<c:out value="${conf.id}"/>">
    ...
    </div>
</c:forEach>

我只是重复使用&lt;c:set&gt;,引用它自己的值,来附加/连接字符串。

【讨论】:

  • 我认为这是最好的,无需按照 fn:join 编写自己的函数(这并不难)
【解决方案3】:

你能用这个吗?似乎它想要一个数组而不是一个列表..

${fn:join(array, ";")}

http://java.sun.com/products/jsp/jstl/1.1/docs/tlddocs/fn/join.fn.html

【讨论】:

    【解决方案4】:

    如果您的子列表是 ArrayList 并且您这样做:

    <div class='scheduled' title="${conf.subject}: ${conf.invitees}" id="scheduled${conf.id}">
    

    你几乎得到你需要的东西。

    唯一的区别是标题将是: “主题:[blah@blah.com、blah2@blah2.com 等]”。

    也许对你来说已经足够好了。

    【讨论】:

    • 如果底层列表是一个 ArrayList,这确实可以工作,但我不想冒险它是没有相同 toString() 实现的其他列表实现。
    【解决方案5】:

    我想这就是你想要的:

    <c:forEach var="tab" items="${tabs}">
     <c:set var="tabAttrs" value='${tabAttrs} ${tab.key}="${tab.value}"'/>
    </c:forEach>
    

    在这种情况下,我有一个带有标签 ID(键)和 URL(值)的哈希图。在此之前未设置 tabAttrs 变量。所以它只是将 value 设置为 tabAttrs ('' to start) 的当前值加上 key/value 表达式。

    【讨论】:

    • OP 已经回答了他自己的问题 :) 检查带有绿色大勾号的消息。
    【解决方案6】:

    只需将字符串放在服务器的 var 旁边,如下所示:

    <c:forEach items="${upcomingSchedule}" var="conf">
        <div class='scheduled' title="${conf.subject}" 
    
             id="scheduled${conf.id}">
    
        ...
        </div>
    </c:forEach>
    

    太晚了!!!

    【讨论】:

      【解决方案7】:

      自从最初发布此答案以来,标签库的实现方式似乎已经发生了很大变化,因此我最终做出了一些重大更改以使事情正常进行。我的最终结果是:

      标签库文件:

      <?xml version="1.0" encoding="UTF-8"?>
      <taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
        <tlib-version>1.0</tlib-version>
        <short-name>string_util</short-name>
        <uri>/WEB-INF/tlds/string_util</uri>
        <info>String Utilities</info>
        <tag>
          <name>join</name>
          <info>Join the contents of any iterable using a separator</info>
          <tag-class>XXX.taglib.JoinObjects</tag-class>
          <body-content>tagdependent</body-content>
          <attribute>
            <name>iterable</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
            <type>java.lang.Iterable</type>
          </attribute>
          <attribute>
            <name>separator</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <type>java.lang.String</type>
          </attribute>
        </tag>
      
        <tag>
          <name>joinints</name>
          <info>Join the contents of an integer array using a separator</info>
          <tag-class>XXX.taglib.JoinInts</tag-class>
          <body-content>tagdependent</body-content>
          <attribute>
            <name>integers</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
            <type>java.lang.Integer[]</type>
          </attribute>
          <attribute>
            <name>separator</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
            <type>java.lang.String</type>
          </attribute>
        </tag>
      </taglib>
      

      JoinInts.java

      public class JoinInts extends TagSupport {
      
          int[] integers;
          String separator = ",";
      
          @Override
          public int doStartTag() throws JspException {
              if (integers != null) {
                  StringBuilder buf = new StringBuilder();
                  if (separator == null) {
                      separator = " ";
                  }
                  for (int i: integers) {
                      if (buf.length() > 0) {
                          buf.append(separator);
                      }
                      buf.append(i);
                  }
                  try {
                      pageContext.getOut().print(buf);
                  } catch (IOException ex) {
                      Logger.getLogger(JoinInts.class.getName()).log(Level.SEVERE, null, ex);
                  }
              }
              return SKIP_BODY;
          }
      
          @Override
          public int doEndTag() throws JspException {
              return EVAL_PAGE;
          }
      
          public int[] getIntegers() {
              return integers;
          }
      
          public void setIntegers(int[] integers) {
              this.integers = integers;
          }
      
          public String getSeparator() {
              return separator;
          }
      
          public void setSeparator(String separator) {
              this.separator = separator;
          }
      }
      

      使用它:

      <%@ taglib prefix="su" uri="/WEB-INF/tlds/string_util.tld" %>
      
      [new Date(${row.key}), <su:joinints integers="${row.value}" separator="," />],
      

      【讨论】:

        【解决方案8】:

        您可以使用 EL 3.0 Stream API。例如,如果您有一个字符串列表,

        <div>${stringList.stream().reduce(",", (n,p)->p.concat(n))}</div>
        

        如果您有 ex 的对象列表。 Person(firstName, lastName) 并且您希望仅连接其中一个属性(例如 firstName),您可以使用 map,

        <div>${personList.stream().map(p->p.getFirstName()).reduce(",", (n,p)->p.concat(n))}</div>
        

        在您的情况下,您可以使用类似的东西(也删除最后一个“,”),

        <c:forEach items="${upcomingSchedule}" var="conf">
            <c:set var="separator" value=","/>
            <c:set var="titleFront" value="${conf.subject}: "/>
            <c:set var="titleEnd" value="${conf.invitees.stream().reduce(separator, (n,p)->p.concat(n))}"/>
            <div class='scheduled' title="${titleFront} ${titleEnd.isEmpty() ? "" : titleEnd.substring(0, titleEnd.length()-1)}" id="scheduled<c:out value="${conf.id}"/>">
            ...
            </div>
        </c:forEach>
        

        小心! EL 3.0 Stream API 是在 Java 8 Stream API 之前完成的,它与此不同。他们不能 sunc 两个 api,因为这会破坏向后兼容性。

        【讨论】:

          猜你喜欢
          • 2011-03-12
          • 2011-01-12
          • 2012-07-03
          • 1970-01-01
          • 1970-01-01
          • 2011-09-11
          • 2011-04-08
          • 1970-01-01
          • 2015-07-10
          相关资源
          最近更新 更多