Java &引用;在类型“上找不到属性”;在JSP EL中使用接口默认方法时
考虑以下接口:Java &引用;在类型“上找不到属性”;在JSP EL中使用接口默认方法时,java,jsp,java-8,el,default-method,Java,Jsp,Java 8,El,Default Method,考虑以下接口: public interface I { default String getProperty() { return "..."; } } 以及仅重新使用默认实现的实现类: public final class C implements I { // empty } 每当在JSP EL脚本上下文中使用C的实例时: <jsp:useBean id = "c" class = "com.example.C" scope = "requ
public interface I {
default String getProperty() {
return "...";
}
}
以及仅重新使用默认实现的实现类:
public final class C implements I {
// empty
}
每当在JSP EL脚本上下文中使用C
的实例时:
<jsp:useBean id = "c" class = "com.example.C" scope = "request"/>
${c.property}
我最初的想法Tomcat6.0对于Java1.8功能来说太旧了,但我惊讶地看到Tomcat8.0也受到了影响。当然,我可以通过显式调用默认实现来解决这个问题:
@Override
public String getProperty() {
return I.super.getProperty();
}
--但为什么默认方法对Tomcat来说是个问题呢
更新:进一步的测试显示无法找到默认属性,而默认方法可以,因此另一个解决方法(Tomcat 7+)是:
${c.getProperty()}
您可以通过创建处理默认方法的自定义ELResolver
实现来解决此问题。我在这里所做的实现扩展了SimpleSpringBeanELResolver
。这是ELResolver的Springs实现,但是没有Spring,同样的想法应该是一样的
此类查找在bean接口上定义的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;
}
}
我不是100%确定,如果这是适当的地方添加我们的解析器,但它确实工作得很好。您还可以在javax.servlet.ServletContextListener.contextInitialized
以下是参考资料中的
ELResolver
:我猜内省不适用于接口默认方法?我对答案很感兴趣:)你试过添加注释@FunctionInterface吗?@rickz:没有,原因有两个:1 IRL,我的接口有不止一个方法(因此没有资格被注释),2@FunctionInterface
有不同的范围(几乎从未与默认方法一起使用):通常没有默认实现,并且有很多匿名实现。我已经厌倦了IntelliJ IDEA礼貌地提醒我,每次我碰巧声明一个方法接口时,我都应该用@functioninterface
为接口添加注释(参见相关内容):/我还没有尝试过这个方法,但是如果覆盖了默认方法,这个解析器会返回覆盖的值还是默认值?我希望它返回被覆盖的值。@battmanz我猜它应该返回被覆盖的值。然而,我没有一个快速的方法来测试这一点。如果没有,那么您可以随时更改属性解析器的行为。
<jsp:useBean id = "c" class = "com.example.C" scope = "request"/>
<%-- ${c.property} --%>
${c.getProperty()}
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;
}
}
@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()));
}
}