2016-02-01 12 views
16

die folgenden Schnittstelle vor:„Eigentum nicht auf Art gefunden“, wenn in JSP-Schnittstelle Standard-Methoden unter Verwendung von EL

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

und die implementierende Klasse, die gerade neu verwendet die Standardimplementierung:

public final class C implements I { 
    // empty 
} 

Jedes Mal, wenn eine Instanz von C in JSP EL-Scripting-Kontext verwendet:

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

- Ich erhalte eine 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) 

Meine ursprüngliche Idee Tomcat 6.0 für Java 1.8-Features zu alt war, aber ich war überrascht zu sehen, Tomcat 8.0 ist ebenfalls betroffen. Natürlich kann ich das Problem umgehen, die Standardimplementierung explizit durch den Aufruf:

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

- aber warum auf der Erde könnte ein Standard-Verfahren ein Problem für Tomcat sein?

aktualisieren: weitere Tests zeigt Eigenschaften Standard nicht gefunden werden kann, während Standard-Methoden können, so eine andere Problemumgehung (Tomcat 7+) ist:

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

Meine Vermutung ist, Innerlichkeit nicht mit Schnittstellen Standardmethoden funktioniert? Ich bin wirklich an der Antwort interessiert :) –

+0

Haben Sie versucht, die Annotation @FunctionalInterface hinzuzufügen? – rickz

+0

@rickz: Nein, habe ich nicht, aus zwei Gründen: ** 1 ** IRL, meine Schnittstelle hat mehr als eine Methode (daher kann nicht kommentiert werden), und ** 2 ** '@ FunctionalInterface' hat eine Unterschiedlicher Umfang (fast nie zusammen mit Standardmethoden): normalerweise keine Standardimplementierung und viele anonyme. Ich habe es schon satt, dass IntelliJ IDEA höflich daran erinnert, dass ich jedes Mal eine Schnittstelle mit '@ FunctionalInterface' annotieren sollte, wenn ich eine Schnittstelle mit einer einzigen Methode deklariere =) – Bass

Antwort

3

Sie können durch Erstellen einer benutzerdefinierten dieses Problem umgehen ELResolver Implementierung, die Standardmethoden behandelt. Die Implementierung, die ich hier gemacht habe, erstreckt sich auf SimpleSpringBeanELResolver. Dies ist Springs-Implementierung von ELResolver aber die gleiche Idee sollte die gleiche ohne Spring sein.

Diese Klasse sucht nach Bean-Eigenschaftssignaturen, die auf Schnittstellen der Bean definiert sind, und versucht, sie zu verwenden. Wenn auf einer Schnittstelle keine Bean Prop-Signatur gefunden wurde, wird sie weiterhin in die Kette des Standardverhaltens gesendet.

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

} 

Sie müssen dann Ihre ELResolver in Ihrer Anwendung irgendwo registrieren. In meinem Fall bin ich mit Spring Java-Konfiguration so ich folgendes haben:

@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 nicht 100% sicher auf, wenn das ist der geeignete Ort, um fügen Sie unsere Resolver aber es funktioniert gut. Sie können auch in den ELResolver laden während javax.servlet.ServletContextListener.contextInitialized

Hier ist die ELResolver in Referenz: http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html

+0

Ich habe dies noch nicht versucht, aber wenn die Standardmethode überschrieben wird, gibt dieser Resolver den überschriebenen Wert oder den Standardwert zurück? Ich möchte, dass der überschriebene Wert zurückgegeben wird. – battmanz

+0

@battmanz Meine Vermutung ist, dass es den überschriebenen Wert zurückgeben sollte. Allerdings habe ich keine schnelle Möglichkeit, dies zu testen. Wenn dies nicht der Fall ist, können Sie das Verhalten des Property Resolvers jederzeit ändern. –