“Property not found on type” when using interface default methods in JSP EL

社会主义新天地 提交于 2019-12-02 19:12:31

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.

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

}

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

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

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!