[英]Getter in an interface with default method JSF
我有一个具有以下默认方法的接口:
default Integer getCurrentYear() {return DateUtil.getYear();}
我也有一个实现这个接口的控制器,但它没有覆盖这个方法。
public class NotifyController implements INotifyController
我正在尝试从我的 xhtml 访问此方法,如下所示:
#{notifyController.currentYear}
但是当我打开屏幕时出现以下错误:
The class 'br.com.viasoft.controller.notify.notifyController' does not have the property 'anoAtual'
如果我从控制器的实例访问此方法,它会返回正确的值,但是当我尝试从我的 xhtml 作为“属性”访问它时,会发生此错误。
有没有办法从我的控制器的引用访问这个接口属性而不必实现该方法?
这可能被视为一个错误,或者有人可能会争辩说这是不支持默认方法作为属性的决定。
见JDK8 java.beans.Introspector.getPublicDeclaredMethods(Class<?>)
或在 JDK13 com.sun.beans.introspect.MethodInfo.get(Class<?>)
在行if (!method.getDeclaringClass().equals(clz))
并且只添加了超类(递归到Object
,而不是接口) java.beans.Introspector.Introspector(Class<?>, Class<?>, int)
在设置superBeanInfo
时请参阅java.beans.Introspector.Introspector(Class<?>, Class<?>, int)
。
解决方案:
在您的情况下使用 EL 方法调用语法(即不是属性访问): #{notifyController.getCurrentYear()}
。
缺点:您必须更改 JSF 代码,并且必须在每次使用时考虑它是否可能是默认方法。 重构也强制编译器无法识别的更改,仅在运行时。
创建一个 EL-Resolver 来普遍支持默认方法。 但这应该使用像标准java.beans.Introspector
这样的良好内部缓存来不减慢 EL 解析速度。
在 JSP EL 中使用接口默认方法时,请参阅“未在类型上找到属性”以获取基本示例(无缓存)。
如果只有少数类/接口受到影响,只需创建小的BeanInfo
类。
下面的代码示例显示了这一点(基于您的示例)。
缺点:必须为实现此类接口的每个类(在 JSF/EL 中使用)创建一个单独的类。
另请参阅: Java 8 接口中的默认方法和 Bean Info Introspector
=> 带有默认方法的接口中的静态getBeanInfo()
=> 扩展接口的每个类的简单+短BeanInfo
类
interface INotifyController {
default Integer getCurrentYear() { ... }
default boolean isAHappyYear() { ... }
default void setSomething(String param) { ... }
/** Support for JSF-EL/Beans to get default methods. */
static java.beans.BeanInfo[] getBeanInfo() {
try {
java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(INotifyController.class);
if (info != null) return new java.beans.BeanInfo[] { info };
} catch (java.beans.IntrospectionException e) {
//nothing to do
}
return null;
}
}
public class NotifyController implements INotifyController {
// your class implementation
...
}
// must be a public class and thus in its own file
public class NotifyControllerBeanInfo extends java.beans.SimpleBeanInfo {
@Override
public java.beans.BeanInfo[] getAdditionalBeanInfo() {
return INotifyController.getBeanInfo();
}
}
由于此错误与 JDK 相关,因此您必须在需要该属性的类中创建一个委托方法。
我发现它将在 Jakarta EE 10 中修复。https://github.com/eclipse-ee4j/el-ri/issues/43
在 Jakarta EE 10 之前,您可以使用自定义 EL Resolver。
package ru.example.el;
import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ELResolver;
import java.beans.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class DefaultMethodELResolver extends ELResolver {
private static final Map<Class<?>, BeanProperties> properties = new ConcurrentHashMap<>();
@Override
public Object getValue(ELContext context, Object base, Object property) {
if (base == null || property == null) {
return null;
}
BeanProperty beanProperty = getBeanProperty(base, property);
if (beanProperty != null) {
Method method = beanProperty.getReadMethod();
if (method == null) {
throw new ELException(String.format("Read method for property '%s' not found", property));
}
Object value;
try {
value = method.invoke(base);
context.setPropertyResolved(base, property);
} catch (Exception e) {
throw new ELException(String.format("Read error for property '%s' in class '%s'", property, base.getClass()), e);
}
return value;
}
return null;
}
@Override
public Class<?> getType(ELContext context, Object base, Object property) {
if (base == null || property == null) {
return null;
}
BeanProperty beanProperty = getBeanProperty(base, property);
if (beanProperty != null) {
context.setPropertyResolved(true);
return beanProperty.getPropertyType();
}
return null;
}
@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
if (base == null || property == null) {
return;
}
BeanProperty beanProperty = getBeanProperty(base, property);
if (beanProperty != null) {
Method method = beanProperty.getWriteMethod();
if (method == null) {
throw new ELException(String.format("Write method for property '%s' not found", property));
}
try {
method.invoke(base, value);
context.setPropertyResolved(base, property);
} catch (Exception e) {
throw new ELException(String.format("Write error for property '%s' in class '%s'", property, base.getClass()), e);
}
}
}
@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
if (base == null || property == null) {
return false;
}
BeanProperty beanProperty = getBeanProperty(base, property);
if (beanProperty != null) {
context.setPropertyResolved(true);
return beanProperty.isReadOnly();
}
return false;
}
@Override
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
return null;
}
@Override
public Class<?> getCommonPropertyType(ELContext context, Object base) {
return Object.class;
}
private BeanProperty getBeanProperty(Object base, Object property) {
return properties.computeIfAbsent(base.getClass(), BeanProperties::new)
.getBeanProperty(property);
}
private static final class BeanProperties {
private final Map<String, BeanProperty> propertyByName = new HashMap<>();
public BeanProperties(Class<?> cls) {
try {
scanInterfaces(cls);
} catch (IntrospectionException e) {
throw new ELException(e);
}
}
private void scanInterfaces(Class<?> cls) throws IntrospectionException {
for (Class<?> ifc : cls.getInterfaces()) {
BeanInfo info = Introspector.getBeanInfo(ifc);
for (PropertyDescriptor propertyDescriptor : info.getPropertyDescriptors()) {
String propertyName = propertyDescriptor.getName();
if (!propertyByName.containsKey(propertyName)) {
propertyByName.put(propertyName, new BeanProperty(propertyDescriptor));
}
}
}
Class<?> superclass = cls.getSuperclass();
if (superclass != null) {
scanInterfaces(superclass);
}
}
public BeanProperty getBeanProperty(Object property) {
return propertyByName.get(property.toString());
}
}
private static final class BeanProperty {
private final PropertyDescriptor descriptor;
public BeanProperty(PropertyDescriptor descriptor) {
this.descriptor = descriptor;
}
public Class<?> getPropertyType() {
return descriptor.getPropertyType();
}
public boolean isReadOnly() {
return getWriteMethod() == null;
}
public Method getReadMethod() {
return descriptor.getReadMethod();
}
public Method getWriteMethod() {
return descriptor.getWriteMethod();
}
}
}
您应该在 faces-config.xml 中注册 EL Resolver。
<?xml version="1.0" encoding="utf-8"?>
<faces-config version="2.3" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd">
<name>el_resolver</name>
<application>
<el-resolver>ru.example.el.DefaultMethodELResolver</el-resolver>
</application>
</faces-config>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.