2016-02-01 24 views
16

Si consideri il seguente interfaccia:"Proprietà non trovata del tipo" quando si utilizzano metodi di interfaccia di default in JSP EL

public interface I { 
    default String getProperty() { 
     return "..."; 
    } 
} 

e la classe che implementa che ha appena ri-utilizza l'implementazione predefinita:

public final class C implements I { 
    // empty 
} 

Ogni volta che un'istanza di C è utilizzato in JSP contesto EL scripting:

<jsp:useBean id = "c" class = "com.example.C" scope = "request"/> 
${c.property} 

- ricevo un 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) 

mia idea iniziale Tomcat 6.0 era troppo vecchio per Java 1.8 caratteristiche, ma sono rimasto sorpreso di vedere Tomcat 8.0 è anche influenzata. Certo che posso lavorare la questione intorno chiamando l'implementazione di default in modo esplicito:

@Override 
    public String getProperty() { 
     return I.super.getProperty(); 
    } 

- ma perché mai un metodo predefinito potrebbe essere un problema per Tomcat?

Aggiornamento: ulteriori test rivela le proprietà di default non può essere trovato, mentre i metodi di default possono, quindi un'altra soluzione (Tomcat 7+) è:

<jsp:useBean id = "c" class = "com.example.C" scope = "request"/> 
<%-- ${c.property} --%> 
${c.getProperty()} 
+1

La mia ipotesi è l'introspezione non funziona con i metodi di interfacce predefinite? Sono davvero interessato alla risposta :) –

+0

Hai provato ad aggiungere l'annotazione @FunctionalInterface? – rickz

+0

@rickz: no non l'ho fatto, per due motivi: ** 1 ** IRL, la mia interfaccia ha più di un metodo (quindi non è idoneo per essere annotato), e ** 2 ** '@ FunctionalInterface' ha un ambito diverso (quasi mai usato insieme ai metodi 'default'): normalmente nessuna implementazione predefinita e molti anonimi. Sono già stufo di IntelliJ IDEA che ricorda gentilmente che dovrei annotare un'interfaccia con '@ FunctionalInterface' ogni volta che mi capita di dichiarare un'interfaccia a metodo singolo =) – Bass

risposta

3

È possibile aggirare questo creando un custom Implementazione ELResolver che gestisce i metodi predefiniti. L'implementazione che ho realizzato qui si estende a SimpleSpringBeanELResolver. Questa è l'implementazione di Springs di ELResolver ma la stessa idea dovrebbe essere la stessa senza Spring.

Questa classe ricerca le firme delle proprietà dei bean definite sulle interfacce del bean e tenta di utilizzarle. Se non è stata trovata la firma di un bean bean su un'interfaccia, continua a inviarla alla catena di comportamento di default.

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

} 

Sarà quindi necessario registrare il ELResolver nell'applicazione da qualche parte. Nel mio caso io sto usando la configurazione di Java Spring così ho il seguente:

@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())); 
    } 
} 

Im non sicuro al 100% sul se questo è il luogo adatto per aggiungere il nostro resolver ma funziona bene. È anche possibile caricare l'ELResolver durante javax.servlet.ServletContextListener.contextInitialized

Ecco il ELResolver riferimento: http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html

+0

Non ho ancora provato questo, ma se il metodo predefinito è sovrascritto, questo resolver restituirà il valore sottoposto a override o il valore predefinito? Vorrei che restituisse il valore sottoposto a override. – battmanz

+0

@battmanz La mia ipotesi è che dovrebbe restituire il valore sovrascritto. Tuttavia non ho un modo rapido per testare questo. Se non lo fa, puoi sempre cambiare il comportamento del risolutore di proprietà. –