2016-02-01 17 views
16

Rozważmy następujący interfejs:„Własność Nie znaleziono typu” przy użyciu metody Interfejsu w JSP EL

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

i klasę wykonawczą, która po prostu ponownie wykorzystuje realizację domyślnie:

public final class C implements I { 
    // empty 
} 

Gdy wystąpi instancja C w kontekście skryptowania JSP EL:

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

- Otrzymuję 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) 

Mój początkowy pomysł Tomcat 6.0 był zbyt stary na Java 1.8 funkcje, ale byłem zaskoczony, aby zobaczyć Tomcat 8.0 jest również ograniczona. Oczywiście mogę pracować kwestię wokół dzwoniąc domyślna implementacja wyraźnie:

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

- ale dlaczego na ziemi metoda domyślna może być problem dla Tomcat?

Aktualizuj: dalsze badania ujawnia właściwości domyślnych nie można znaleźć, podczas gdy metody domyślne mogą, więc kolejny obejście (Tomcat 7+) wynosi:

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

Moje przypuszczenie jest introspekcja nie działa z metod standardowych interfejsów? Naprawdę interesuje mnie odpowiedź :) –

+0

Czy próbowałeś dodać adnotację @FunctionalInterface? – rickz

+0

@rickz: nie, nie zrobiłem tego, z dwóch powodów: ** 1 ** IRL, mój interfejs ma więcej niż jedną metodę (w związku z tym nie kwalifikuje się do adnotacji), a ** 2 ** '@ FunctionalInterface' ma inny zakres (prawie nigdy nie używany razem z "domyślnymi" metodami): zwykle brak domyślnej implementacji i wiele anonimowych. Mam już dość IntelliJ IDEA grzecznie przypominając, że powinienem opisywać interfejs z '@ FunctionalInterface' za każdym razem, gdy zdarza mi się deklarować jednokierunkowy interfejs =) – Bass

Odpowiedz

3

Można obejść ten problem poprzez tworzenie niestandardowych Implementacja ELResolver, która obsługuje domyślne metody. Implementacja, którą tu wykonałem, rozszerza się o SimpleSpringBeanELResolver. Jest to implementacja Sprężyny ELResolver, ale ten sam pomysł powinien być taki sam bez Spring.

Ta klasa szuka sygnatur właściwości fasoli zdefiniowanych na interfejsach komponentu bean i próbuje z nich korzystać. Jeśli w interfejsie nie znaleziono podpisu komponentu bean, kontynuuje wysyłanie go w dół łańcucha domyślnego zachowania.

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

} 

Musisz następnie zarejestrować swój numer ELResolver w swoim wniosku. W moim przypadku używam konfigurację z użyciem Java Spring, tak mam następujące:

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

Nie jestem pewien na 100%, czy to jest to odpowiednie miejsce, aby dodać naszą resolverowi ale działa dobrze. Można również załadować ELResolver w trakcie javax.servlet.ServletContextListener.contextInitialized

Oto ELResolver w odnośniku: http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html

+0

Nie próbowałem jeszcze tego, ale jeśli domyślna metoda zostanie nadpisana, czy ten resolver zwróci nadpisaną wartość lub wartość domyślną? Chciałbym, żeby zwrócił przesłoniętą wartość. – battmanz

+0

@battmanz Przypuszczam, że powinien on zwrócić nadpisaną wartość. Jednak nie mam szybki sposób, aby to przetestować. Jeśli tak się stanie, zawsze możesz zmienić zachowanie resolwera właściwości. –

Powiązane problemy