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 PropertyEditor
s 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.
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()}
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
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}" />
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}