java - test - c out variable
Spring MVC: cómo mostrar valores de fecha formateados en JSP EL (4)
Me sentí decepcionado por saber que los desarrolladores de Spring han decidido no integrar Unified EL (el lenguaje de expresión usado en JSP 2.1+) con Spring EL declarando:
Ni JSP ni JSF tienen una posición sólida en términos de nuestro enfoque de desarrollo.
Pero inspirándome en el ticket de JIRA citado, creé un ELResolver personalizado que, si el valor resuelto es un java.time.LocalDate
o java.time.LocalDateTime
, intentará extraer el valor del patrón @DateTimeFormat
para formatear la String
devuelta valor.
Aquí está ELResolver
(junto con el ServletContextListener
usado para arrancarlo):
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;
}
}
Registre ELResolver
en web.xml:
<listener>
<listener-class>com.company.el.DateTimeFormatAwareElResolver</listener-class>
</listener>
¡Y ahora, cuando tenga ${widget.created}
en mi jsp, el valor mostrado se formateará de acuerdo con la anotación @DateTimeFormat
!
Además, si jsp (y no solo la representación de cadena formateada) necesita el objeto LocalDate
o LocalDateTime
, aún puede acceder al objeto utilizando la invocación directa del método como: ${widget.getCreated()}
Aquí hay un bean de valor simple anotado con la nueva anotación @DateTimeFormat
Spring (a partir de 3.0) (que, según tengo entendido, reemplaza la necesidad pre-3.0 de PropertyEditor
s personalizado según esta pregunta 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
}
Al vincular los valores de un envío de formulario a este widget, el formato de fecha funciona perfectamente. Es decir, solo las cadenas de fecha en el formato MM/dd/yyyy
se convertirán satisfactoriamente en objetos LocalDate
reales. Genial, estamos a mitad de camino.
Sin embargo, también me gustaría poder mostrar la propiedad LocalDate
creada en una vista JSP en el mismo formato MM/dd/yyyy
utilizando JSP EL así (asumiendo que mi controlador Spring incorporó un atributo de widget al modelo):
${widget.created}
Desafortunadamente, esto solo mostrará el formato de LocalDate
predeterminado de LocalDate
(en formato yyyy-MM-dd
). Entiendo que si uso las etiquetas de formulario de Spring, la fecha se muestra como se desea:
<form:form commandName="widget">
Widget created: <form:input path="created"/>
</form:form>
Pero me gustaría simplemente mostrar la cadena de fecha formateada sin usar las etiquetas de formulario de primavera. O incluso la etiqueta fmt:formatDate
JSTL.
Proveniente de Struts2, el HttpServletRequest
se envolvió en un StrutsRequestWrapper
que permitía que las expresiones EL de este tipo interrogaran la pila de valores OGNL. ¿Entonces me pregunto si el resorte proporciona algo similar a esto para permitir que los convertidores ejecuten?
EDITAR
También me doy cuenta de que al usar la etiqueta eval
de spring, la fecha se mostrará de acuerdo con el patrón definido en la anotación @DateTimeFormat
:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<spring:eval expression="widget.created"/>
Curiosamente, cuando se utiliza un PropertyEditor
personalizado para formatear la fecha, esta etiqueta NO invoca el método getAsText
y, por lo tanto, el valor predeterminado es DateFormat.SHORT
como se describe en los documentos . En cualquier caso, todavía me gustaría saber si hay una manera de lograr el formato de fecha sin tener que usar una etiqueta, solo usando el estándar JSP EL.
Para precisar la respuesta de Eduardo:
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<fmt:formatDate pattern="MM/dd/yyyy" value="${widget.created}" />
Puede usar la etiqueta para proporcionarle este tipo de formateo, como dinero, datos, tiempo y muchos otros.
Puede agregar en su JSP la referencia: <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
Y use el formato como: <fmt:formatDate pattern="yyyy-MM-dd" value="${now}" />
Sigue a continuación una referencia:
http://www.tutorialspoint.com/jsp/jstl_format_formatdate_tag.htm
También prefiero no hacer ningún formato a través de etiquetas. Me doy cuenta de que esta puede no ser la solución que está buscando y está buscando una manera de hacerlo a través de las anotaciones de primavera. Sin embargo, en el pasado he usado el siguiente trabajo alrededor:
Crea un nuevo getter con la siguiente firma:
public String getCreatedDateDisplay
(Puedes modificar el nombre del captador si lo prefieres).
Dentro del captador, formatee el atributo de fecha created
como se desee utilizando un formateador como SimpleDateFormat.
Entonces puedes llamar al siguiente desde tu JSP.
${widget.createDateDisplay}