java - programa - lenguaje jsp
"Propiedad no encontrada en el tipo" cuando se usan métodos predeterminados de interfaz en JSP EL (1)
Considere la siguiente interfaz:
public interface I {
default String getProperty() {
return "...";
}
}
y la clase de implementación que solo reutiliza la implementación predeterminada:
public final class C implements I {
// empty
}
Cada vez que se utiliza una instancia de C
en el contexto de scripts JSP EL:
<jsp:useBean id = "c" class = "com.example.C" scope = "request"/>
${c.property}
- Recibo una PropertyNotFoundException
:
javax.el.PropertyNotFoundException: Property ''property'' not found on type com.example.C
javax.el.BeanELResolver$BeanProperties.get(BeanELResolver.java:268)
javax.el.BeanELResolver$BeanProperties.access$300(BeanELResolver.java:221)
javax.el.BeanELResolver.property(BeanELResolver.java:355)
javax.el.BeanELResolver.getValue(BeanELResolver.java:95)
org.apache.jasper.el.JasperELResolver.getValue(JasperELResolver.java:110)
org.apache.el.parser.AstValue.getValue(AstValue.java:169)
org.apache.el.ValueExpressionImpl.getValue(ValueExpressionImpl.java:184)
org.apache.jasper.runtime.PageContextImpl.proprietaryEvaluate(PageContextImpl.java:943)
org.apache.jsp.index_jsp._jspService(index_jsp.java:225)
org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:438)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:396)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:340)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
Mi idea inicial de Tomcat 6.0 era demasiado antigua para las características de Java 1.8, pero me sorprendió ver que Tomcat 8.0 también se ve afectado. Por supuesto, puedo solucionar el problema llamando explícitamente a la implementación predeterminada:
@Override
public String getProperty() {
return I.super.getProperty();
}
- ¿Pero por qué en la tierra un método predeterminado podría ser un problema para Tomcat?
Actualización : nuevas pruebas revelan que no se pueden encontrar las propiedades predeterminadas, mientras que los métodos predeterminados sí pueden, por lo que otra solución alternativa (Tomcat 7+) es:
<jsp:useBean id = "c" class = "com.example.C" scope = "request"/>
<%-- ${c.property} --%>
${c.getProperty()}
Puede ELResolver
esto creando una implementación personalizada de ELResolver
que maneje los métodos predeterminados. La implementación que he hecho aquí extiende SimpleSpringBeanELResolver
. Esta es la implementación de ELResolver
de ELResolver
pero la misma idea debería ser la misma sin Spring.
Esta clase busca firmas de propiedades de bean definidas en las interfaces del bean e intenta usarlas. Si no se encontró ninguna firma de apoyo de bean en una interfaz, continúa enviándola por la cadena de comportamiento predeterminado.
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.access.el.SimpleSpringBeanELResolver;
import javax.el.ELContext;
import javax.el.ELException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.Optional;
import java.util.stream.Stream;
/**
* Resolves bean properties defined as default interface methods for the ELResolver.
* Retains default SimpleSpringBeanELResolver for anything which isn''t a default method.
*
* Created by nstuart on 12/2/2016.
*/
public class DefaultMethodELResolver extends SimpleSpringBeanELResolver {
/**
* @param beanFactory the Spring BeanFactory to delegate to
*/
public DefaultMethodELResolver(BeanFactory beanFactory) {
super(beanFactory);
}
@Override
public Object getValue(ELContext elContext, Object base, Object property) throws ELException {
if(base != null && property != null) {
String propStr = property.toString();
if(propStr != null) {
Optional<Object> ret = attemptDefaultMethodInvoke(base, propStr);
if (ret != null) {
// notify the ELContext that our prop was resolved and return it.
elContext.setPropertyResolved(true);
return ret.get();
}
}
}
// delegate to super
return super.getValue(elContext, base, property);
}
/**
* Attempts to find the given bean property on our base object which is defined as a default method on an interface.
* @param base base object to look on
* @param property property name to look for (bean name)
* @return null if no property could be located, Optional of bean value if found.
*/
private Optional<Object> attemptDefaultMethodInvoke(Object base, String property) {
try {
// look through interfaces and try to find the method
for(Class<?> intf : base.getClass().getInterfaces()) {
// find property descriptor for interface which matches our property
Optional<PropertyDescriptor> desc = Stream.of(PropertyUtils.getPropertyDescriptors(intf))
.filter(d->d.getName().equals(property))
.findFirst();
// ONLY handle default methods, if its not default we dont handle it
if(desc.isPresent() && desc.get().getReadMethod() != null && desc.get().getReadMethod().isDefault()) {
// found read method, invoke it on our object.
return Optional.ofNullable(desc.get().getReadMethod().invoke(base));
}
}
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException("Unable to access default method using reflection", e);
}
// no value found, return null
return null;
}
}
Luego deberá registrar su ELResolver
en su aplicación en algún lugar. En mi caso, estoy usando la configuración java de Spring, así que tengo lo siguiente:
@Configuration
...
public class SpringConfig extends WebMvcConfigurationSupport {
...
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
...
// add our default method resolver to our ELResolver list.
JspApplicationContext jspContext = JspFactory.getDefaultFactory().getJspApplicationContext(getServletContext());
jspContext.addELResolver(new DefaultMethodELResolver(getApplicationContext()));
}
}
No estoy 100% seguro de si ese es el lugar adecuado para agregar nuestro sistema de resolución, pero funciona bien. También puede cargar ELResolver durante javax.servlet.ServletContextListener.contextInitialized
Aquí está ELResolver
en referencia: http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html