Spring MVC: как отображать форматированные значения даты в JSP EL

Здесь простое значение bean, аннотированное с помощью Spring новой (начиная с версии 3.0) удобной @DateTimeFormat аннотации (которая, как я понимаю, заменяет потребность в pre-3.0 для пользовательских PropertyEditor по этот вопрос SO):

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
}

При достижении значений из представления формы этому виджету формат даты работает безупречно. То есть, только строки даты в формате MM/dd/yyyy будут успешно конвертироваться в фактические объекты LocalDate. Отлично, мы на полпути.

Тем не менее, я также хотел бы также показать созданное свойство LocalDate в представлении JSP в том же формате MM/dd/yyyy, используя JSP EL, как это (при условии, что мой контроллер Spring добавил атрибут виджета в модель):

${widget.created}

К сожалению, это будет отображать только формат toString по умолчанию LocalDate (в формате yyyy-MM-dd). Я понимаю, что если я использую теги формы Spring, дата отображается по желанию:

<form:form commandName="widget">
  Widget created: <form:input path="created"/>
</form:form>

Но я бы хотел просто отобразить форматированную строку даты без использования тегов формы Spring. Или даже тег 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"/>

Интересно, что при использовании пользовательского PropertyEditor для форматирования даты этот тег НЕ вызывается этим методом PropertyEditor getAsText и поэтому по умолчанию используется DateFormat.SHORT как описанный в документах. В любом случае, мне все равно хотелось бы узнать, есть ли способ добиться форматирования даты без использования тега - только с использованием стандартного JSP EL.

Ответы

Ответ 1

Мне было неприятно узнать, что spring разработчики решили не интегрировать Unified EL (язык выражений, используемый в JSP 2.1+), с Spring EL:

ни JSP, ни JSF не имеют более сильной позиции с точки зрения нашего развития.

Но, учитывая вдохновение в названном билете JIRA, я создал пользовательский ELResolver, который, если разрешенное значение является java.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;
      }
    }

Зарегистрируйте ELResolver в web.xml:

<listener>
  <listener-class>com.company.el.DateTimeFormatAwareElResolver</listener-class>
</listener>

И теперь, когда у меня есть ${widget.created} в моем jsp, отображаемое значение будет отформатировано в соответствии с аннотацией @DateTimeFormat!

Кроме того, если объект LocalDate или LocalDateTime необходим jsp (а не только форматированное представление String), вы можете получить доступ к самому объекту с помощью прямого вызова метода, например: ${widget.getCreated()}

Ответ 2

Вы можете использовать тег, чтобы предоставить вам такие формы, как деньги, данные, время и многие другие.

Вы можете добавить к вам 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

Ответ 3

Чтобы точно ответить Эдуардо:

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<fmt:formatDate pattern="MM/dd/yyyy" value="${widget.created}" />

Ответ 4

Я также предпочитаю не делать никакого форматирования с помощью тегов. Я понимаю, что это не может быть решение, которое вы ищете, и ищете способ сделать это с помощью аннотаций spring. Тем не менее, в прошлом я использовал следующую работу:

Создайте новый геттер со следующей подписью:

public String getCreatedDateDisplay

(Вы можете изменить имя получателя, если хотите).

Внутри получателя форматируйте атрибут даты created по желанию с помощью форматирования, такого как SimpleDateFormat.

Затем вы можете вызвать следующее из своего JSP

${widget.createDateDisplay}