web-dev-qa-db-fra.com

Spring MVC: comment afficher les valeurs de date formatées dans JSP EL

Voici un simple haricot de valeur annoté avec la nouvelle annotation @DateTimeFormat de commodité de Spring (à compter de la version 3.0) (qui, si je comprends bien, remplace le besoin antérieur à la version 3.0 personnalisée PropertyEditors conformément à la question cette 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
}

Lorsque vous associez les valeurs d'une soumission de formulaire à ce widget, le format de date fonctionne parfaitement. Autrement dit, seules les chaînes de date au format MM/dd/yyyy seront converties avec succès en objets LocalDate réels. Génial, nous sommes à mi-chemin.

Toutefois, j'aimerais également pouvoir également afficher la propriété LocalDate créée dans une vue JSP au même format MM/dd/yyyy à l'aide de JSP EL de la manière suivante (en supposant que mon contrôleur Spring ait ajouté un attribut de widget au modèle):

${widget.created}

Malheureusement, cela n'affichera que le format toString par défaut de LocalDate (au format yyyy-MM-dd). Je comprends que si j'utilise les balises de formulaire de spring, la date s'affiche comme vous le souhaitez:

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

Mais j'aimerais simplement afficher la chaîne de date formatée sans utiliser les balises de formulaire printanières. Ou même la balise fmt:formatDate de JSTL.

Venant de Struts2, la HttpServletRequest était encapsulée dans un StrutsRequestWrapper qui permettait aux expressions EL comme celle-ci d'interroger la pile de valeurs OGNL. Je me demande donc si spring fournit quelque chose de similaire à ceci pour permettre aux convertisseurs d’exécuter?

MODIFIER

Je me rends également compte qu'en utilisant la balise eval de spring, la date s'affichera conformément au modèle défini dans l'annotation @DateTimeFormat:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<spring:eval expression="widget.created"/>

Il est intéressant de noter que lorsque vous utilisez une variable PropertyEditor personnalisée pour formater la date, cette balise n’appelle PAS cette méthode PropertyEditor's getAsText et est donc définie par défaut sur DateFormat.SHORT sous la forme décrit dans la documentation . Quoi qu'il en soit, j'aimerais quand même savoir s'il existe un moyen de réaliser le formatage de la date sans avoir à utiliser une balise - uniquement à l'aide de la norme JSP EL.

17
Brice Roncace

J'étais désemparé d'apprendre que les développeurs de Spring ont décidé de ne pas intégrer Unified EL (le langage d'expression utilisé dans JSP 2.1+) avec Spring EL déclarant:

ni JSP ni JSF n’ont plus une position forte en termes de développement.

Mais en m'inspirant du ticket JIRA cité, j'ai créé un ELResolver personnalisé qui, si la valeur résolue est un Java.time.LocalDate ou Java.time.LocalDateTime, tentera d'extraire la valeur du modèle @DateTimeFormat afin de formater la valeur String renvoyée.

Voici la ELResolver (avec la ServletContextListener utilisée pour l'amorcer):

    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;
      }
    }

Enregistrez la ELResolver dans web.xml:

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

Et maintenant, quand j’ai ${widget.created} dans mon jsp, la valeur affichée sera formatée selon l’annotation @DateTimeFormat!

De plus, si l'objet LocalDate ou LocalDateTime est nécessaire à jsp (et pas uniquement à la représentation String formatée), vous pouvez toujours accéder à l'objet lui-même à l'aide de l'appel de méthode direct, comme suit: ${widget.getCreated()}

2
Brice Roncace

Vous pouvez utiliser l’étiquette pour vous fournir ce type de formatage, tel que de l’argent, des données, du temps, etc.

Vous pouvez ajouter sur votre JSP la référence: <%@ taglib prefix="fmt" uri="http://Java.Sun.com/jsp/jstl/fmt" %>

Et utilisez le formatage comme suit: <fmt:formatDate pattern="yyyy-MM-dd" value="${now}" />

Suit ci-dessous une référence:

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

7
Eduardo Mioto

Pour préciser Eduardo répondre:

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

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

Je préfère aussi ne pas faire de formatage via des tags. Je me rends compte que ce n'est peut-être pas la solution que vous recherchez et cherchez un moyen de le faire via des annotations printanières. Néanmoins, dans le passé, j'ai utilisé les travaux suivants:

Créez un nouveau getter avec la signature suivante:

public String getCreatedDateDisplay

(Vous pouvez modifier le nom du getter si vous préférez.)

Dans le getter, formatez l'attribut created date comme vous le souhaitez à l'aide d'un outil de formatage tel que SimpleDateFormat.

Ensuite, vous pouvez appeler le suivant depuis votre JSP

${widget.createDateDisplay}
1
applejack42