2016-02-01 3 views
16

Рассмотрим следующий интерфейс:«Свойство не найдено по типу» при использовании методов интерфейса по умолчанию в JSP EL

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

и реализующий класс, который просто повторно использует реализацию по умолчанию:

public final class C implements I { 
    // empty 
} 

Всякий раз, когда экземпляр C используется в контексте JSP EL сценариев:

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

- я получаю 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) 

Моя первоначальная идея Tomcat 6.0 была слишком стара для Java 1.8 функции, но я был удивлен увидеть Tomcat 8.0 также затронуты. Конечно, я могу работать вопрос вокруг вызова реализации по умолчанию в явном виде:

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

- но почему на земле методы по умолчанию может быть проблемой для Tomcat?

Update: дальнейшее тестирование показывает свойства по умолчанию не могут быть найдены, в то время как стандартные методы могут, так что еще один обходной путь (Tomcat 7+) является:

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

Моя догадка самоанализ не работает с методами интерфейса по умолчанию? Я действительно интересуюсь ответом :) –

+0

Вы пытались добавить аннотацию @FunctionalInterface? – rickz

+0

@rickz: нет Я не сделал это по двум причинам: ** 1 ** IRL, мой интерфейс имеет более одного метода (таким образом, он не может быть аннотирован) и ** 2 ** '@ FunctionalInterface' имеет (почти никогда не используется вместе с методами 'default'): обычно нет реализации по умолчанию и много анонимных. Я уже устал от IntelliJ IDEA вежливо напоминаю, что я должен аннотировать интерфейс с '@ FunctionalInterface' каждый раз, когда я случайно объявляю интерфейс с одним методом =) – Bass

ответ

3

Вы можете обойти это путем создания пользовательского ELResolver реализация, которая обрабатывает методы по умолчанию. Реализация, которую я здесь сделал, охватывает SimpleSpringBeanELResolver. Это реализация Springs ELResolver, но такая же идея должна быть такой же без весны.

Этот класс ищет сигнатуры свойств bean, определенные на интерфейсах компонента и пытается их использовать. Если на интерфейсе не обнаружена сигнатура подписи bean prop, она продолжает отправлять ее по цепочке поведения по умолчанию.

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

} 

Вам будет необходимо зарегистрировать ELResolver в приложении где-то. В моем случае я использую конфигурацию Java Spring, чтобы я иметь следующее:

@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 не 100% уверен, что на если то подходящее место, чтобы добавить наш распознаватель, но он работает просто отлично. Вы можете также загрузить ELResolver в течение javax.servlet.ServletContextListener.contextInitialized

Здесь является ELResolver ссылкой: http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html

+0

Я еще не пробовал, но если метод по умолчанию переопределен, будет ли этот преобразователь возвращать переопределенное значение или значение по умолчанию? Я бы хотел, чтобы он возвращал переопределенное значение. – battmanz

+0

@battmanz Я предполагаю, что он должен вернуть перезаписанное значение. Однако у меня нет быстрого теста. Если это не так, вы всегда можете изменить поведение свойства resolver. –

Смежные вопросы