本文介绍了“未在类型上找到属性”在JSP EL中使用接口默认方法时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下界面:

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

以及刚刚重新使用默认值的实现类实施:

and the implementing class which just re-uses the default implementation:

public final class C implements I {
    // empty
}

每当在JSP EL脚本上下文中使用 C 的实例时:

Whenever an instance of C is used in JSP EL scripting context:

<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也受到了影响。当然,我可以通过显式调用默认实现来解决这个问题:

My initial idea Tomcat 6.0 was too old for Java 1.8 features, but I was surprised to see Tomcat 8.0 is also affected. Of course I can work the issue around by calling the default implementation explicitly:

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

- 但为什么默认方法可能是Tomcat的问题?

-- but why on earth a default method could be a problem for Tomcat?

更新:进一步测试显示无法找到默认属性,而默认方法可以,因此另一种解决方法(Tomcat 7+)是:

Update: further testing reveals default properties can't be found, while default methods can, so another workaround (Tomcat 7+) is:

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


推荐答案

您可以通过创建处理默认方法的自定义 ELResolver 实现来解决此问题。我在这里实现的扩展扩展了 SimpleSpringBeanELResolver 。这是 ELResolver 的Springs实现,但没有Spring,同样的想法应该是相同的。

You can work around this by creating a custom ELResolver implementation which handles default methods. The implementation I have made here extends SimpleSpringBeanELResolver. This is Springs implementation of ELResolver but the same idea should be the same without Spring.

这个类查找bean在bean的接口上定义的属性签名并尝试使用它们。如果在接口上没有找到bean prop签名,它会继续向下发送默认行为链。

This class looks for bean property signatures defined on interfaces of the bean and attempts to use them. If no bean prop signature was found on an interface it continues on sending it down the chain of default behavior.

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 。在我的情况下,我使用Spring的java配置,所以我有以下内容:

You will then need to register your ELResolver in your application somewhere. In my case I am using Spring's java configuration so I have the following:

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

我不是100%肯定,如果那是合适的地方添加我们的解析器,但它确实工作得很好。您还可以在 javax.servlet.ServletContextListener.contextInitialized

Im not 100% sure on if thats the appropriate place to add our resolver but it does work just fine. You can also load the ELResolver in during javax.servlet.ServletContextListener.contextInitialized

中加载ELResolver这是 ELResolver 参考:

Here is the ELResolver in reference: http://docs.oracle.com/javaee/7/api/javax/el/ELResolver.html

这篇关于“未在类型上找到属性”在JSP EL中使用接口默认方法时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-20 22:46