Spring MVC:如何在JSP EL中显示格式化的日期值

Bri*_*ace 18 java spring jsp el spring-mvc

这是一个简单的值bean,使用Spring的新版本(3.0版本) @DateTimeFormat注释(根据我的理解PropertyEditor,根据此SO问题替换了3.0之前的自定义需求):

import java.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;

public class Widget {
  private String name;

  @DateTimeFormat(pattern = "MM/dd/yyyy")
  private LocalDate created;

  // getters/setters excluded
}
Run Code Online (Sandbox Code Playgroud)

将表单提交中的值与此窗口小部件结合使用时,日期格式可以正常运行.也就是说,只有MM/dd/yyyy格式中的日期字符串才能成功转换为实际LocalDate对象.太棒了,我们就在那里.

然而,我也想能够还显示所创建的LocalDate在同一个JSP视图属性MM/dd/yyyy格式使用JSP EL像这样(假设我的弹簧控制器添加小窗口属性的模型):

${widget.created}
Run Code Online (Sandbox Code Playgroud)

不幸的是,这只会显示默认toString格式LocalDate(yyyy-MM-dd格式).我知道如果我使用spring的表单标签,日期会根据需要显示:

<form:form commandName="widget">
  Widget created: <form:input path="created"/>
</form:form>
Run Code Online (Sandbox Code Playgroud)

但我想在不使用spring form标签的情况下显示格式化的日期字符串.甚至是JSTL的fmt:formatDate标签.

来自Struts2,HttpServletRequest包含在一个StrutsRequestWrapper启用这样的EL表达式实际上询问OGNL值栈.所以我想知道spring是否提供类似的东西以允许转换器执行?

编辑

我也意识到,当使用spring的eval标签时,日期将根据@DateTimeFormat注释中定义的模式显示:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<spring:eval expression="widget.created"/>
Run Code Online (Sandbox Code Playgroud)

有趣的是,使用自定义当PropertyEditor格式化日期,该标签不会调用该PropertyEditor的getAsText方法,因此默认为DateFormat.SHORT如在该文档中描述.无论如何,我仍然想知道是否有办法实现日期格式化而不必使用标记 - 仅使用标准JSP EL.

小智 8

您可以使用标记为您提供这些格式,例如金钱,数据,时间和许多其他格式.

您可以在JSP上添加引用: <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

并使用格式为: <fmt:formatDate pattern="yyyy-MM-dd" value="${now}" />

以下是参考:

http://www.tutorialspoint.com/jsp/jstl_format_formatdate_tag.htm

  • `<%@ taglib prefix ="fmt"uri ="http://java.sun.com/jsp/jstl/fmt"%>`taglib不适用于`java.time.LocalDate` (2认同)

Bri*_*ace 3

当我得知Spring 开发人员决定不将 Unified EL(JSP 2.1+ 中使用的表达式语言)与 Spring EL集成时,我感到很沮丧,声明如下:

JSP 和 JSF 在我们的开发重点方面都不再占据强势地位。

但从引用的 JIRA 票证中汲取灵感,我创建了一个自定义ELResolver,如果解析的值为 ajava.time.LocalDate或java.time.LocalDateTime,它将尝试拉取@DateTimeFormat模式值以格式化返回String值。

这是ELResolver(以及ServletContextListener用于引导它的):

    public class DateTimeFormatAwareElResolver extends ELResolver implements ServletContextListener {
      private final ThreadLocal<Boolean> isGetValueInProgress = new ThreadLocal<>();

      @Override
      public void contextInitialized(ServletContextEvent event) {
        JspFactory.getDefaultFactory().getJspApplicationContext(event.getServletContext()).addELResolver(this);
      }

      @Override
      public void contextDestroyed(ServletContextEvent sce) {}

      @Override
      public Object getValue(ELContext context, Object base, Object property) {
        try {
          if (Boolean.TRUE.equals(isGetValueInProgress.get())) {
            return null;
          }

          isGetValueInProgress.set(Boolean.TRUE);
          Object value = context.getELResolver().getValue(context, base, property);
          if (value != null && isFormattableDate(value)) {
            String pattern = getDateTimeFormatPatternOrNull(base, property.toString());
            if (pattern != null) {
              return format(value, DateTimeFormatter.ofPattern(pattern));
            }
          }
          return value;
        }
        finally {
          isGetValueInProgress.remove();
        }
      }

      private boolean isFormattableDate(Object value) {
        return value instanceof LocalDate || value instanceof LocalDateTime;
      }

      private String format(Object localDateOrLocalDateTime, DateTimeFormatter formatter) {
        if (localDateOrLocalDateTime instanceof LocalDate) {
          return ((LocalDate)localDateOrLocalDateTime).format(formatter);
        }
        return ((LocalDateTime)localDateOrLocalDateTime).format(formatter);
      }

      private String getDateTimeFormatPatternOrNull(Object base, String property) {
        DateTimeFormat dateTimeFormat = getDateTimeFormatAnnotation(base, property);
        if (dateTimeFormat != null) {
          return dateTimeFormat.pattern();
        }

        return null;
      }

      private DateTimeFormat getDateTimeFormatAnnotation(Object base, String property) {
        DateTimeFormat dtf = getDateTimeFormatFieldAnnotation(base, property);
        return dtf != null ? dtf : getDateTimeFormatMethodAnnotation(base, property);
      }

      private DateTimeFormat getDateTimeFormatFieldAnnotation(Object base, String property) {
        try {
          if (base != null && property != null) {
            Field field = base.getClass().getDeclaredField(property);
            return field.getAnnotation(DateTimeFormat.class);
          }
        }
        catch (NoSuchFieldException | SecurityException ignore) {
        }
        return null;
      }

      private DateTimeFormat getDateTimeFormatMethodAnnotation(Object base, String property) {
        try {
          if (base != null && property != null) {
            Method method = base.getClass().getMethod("get" + StringUtils.capitalize(property));
            return method.getAnnotation(DateTimeFormat.class);
          }
        }
        catch (NoSuchMethodException ignore) {
        }
        return null;
      }

      @Override
      public Class<?> getType(ELContext context, Object base, Object property) {
        return null;
      }

      @Override
      public void setValue(ELContext context, Object base, Object property, Object value) {
      }

      @Override
      public boolean isReadOnly(ELContext context, Object base, Object property) {
        return true;
      }

      @Override
      public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
        return null;
      }

      @Override
      public Class<?> getCommonPropertyType(ELContext context, Object base) {
        return null;
      }
    }
Run Code Online (Sandbox Code Playgroud)

在 web.xml 中注册ELResolver:

<listener>
  <listener-class>com.company.el.DateTimeFormatAwareElResolver</listener-class>
</listener>
Run Code Online (Sandbox Code Playgroud)

现在,当我${widget.created}在 jsp 中时,显示的值将根据@DateTimeFormat注释进行格式化!

此外,如果jsp 需要LocalDateorLocalDateTime对象(而不仅仅是格式化的字符串表示形式),您仍然可以使用直接方法调用来访问对象本身,例如:${widget.getCreated()}