使用默认方法 JSF 的接口中的 getter



我有一个具有以下默认方法的接口:

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 作为"属性"访问它时,会发生此错误。

有没有办法从我的控制器的引用访问此接口属性,而无需实现该方法?

这可能被认为是一个错误,或者有人可能会争辩说,这是一个不支持默认方法作为属性的决定。
请参阅JDK8java.beans.Introspector.getPublicDeclaredMethods(Class<?>)
或在 JDK13com.sun.beans.introspect.MethodInfo.get(Class<?>)
在第if (!method.getDeclaringClass().equals(clz))行 并且只添加超类(递归最多Object,但不添加接口(,请参阅设置superBeanInfo时的java.beans.Introspector.Introspector(Class<?>, Class<?>, int)

解决 方案:

  • 使用 EL 方法调用语法(即不是属性访问(:在您的情况下#{notifyController.getCurrentYear()}
    缺点:您必须更改 JSF 代码,并且必须考虑每次使用是否可能是默认方法。此外,重构还会强制编译器仅在运行时识别的更改。

  • 创建 EL 解析程序以常规支持默认方法。但这应该使用良好的内部缓存,如标准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();
}
}

我发现它将在雅加达EE 10中修复。 https://github.com/eclipse-ee4j/el-ri/issues/43

在 Jakarta EE 10 之前,您可以使用自定义 EL 解析程序。

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()) {
processInterface(ifc);
}
Class<?> superclass = cls.getSuperclass();
if (superclass != null) {
scanInterfaces(superclass);
}
}
private void processInterface(Class<?> ifc) throws IntrospectionException {
BeanInfo info = Introspector.getBeanInfo(ifc);
for (PropertyDescriptor propertyDescriptor : info.getPropertyDescriptors()) {
String propertyName = propertyDescriptor.getName();
BeanProperty beanProperty = propertyByName
.computeIfAbsent(propertyName, key -> new BeanProperty(propertyDescriptor.getPropertyType()));
if (beanProperty.getReadMethod() == null && propertyDescriptor.getReadMethod() != null) {
beanProperty.setReadMethod(propertyDescriptor.getReadMethod());
}
if (beanProperty.getWriteMethod() == null && propertyDescriptor.getWriteMethod() != null) {
beanProperty.setWriteMethod(propertyDescriptor.getWriteMethod());
}
}
for (Class<?> parentIfc : ifc.getInterfaces()) {
processInterface(parentIfc);
}
}
public BeanProperty getBeanProperty(Object property) {
return propertyByName.get(property.toString());
}
}
private static final class BeanProperty {
private final Class<?> propertyType;
private Method readMethod;
private Method writeMethod;
public BeanProperty(Class<?> propertyType) {
this.propertyType = propertyType;
}
public Class<?> getPropertyType() {
return propertyType;
}
public boolean isReadOnly() {
return getWriteMethod() == null;
}
public Method getReadMethod() {
return readMethod;
}
public void setReadMethod(Method readMethod) {
this.readMethod = readMethod;
}
public Method getWriteMethod() {
return writeMethod;
}
public void setWriteMethod(Method writeMethod) {
this.writeMethod = writeMethod;
}
}
}

您应该在 faces-config.xml 中注册 EL 解析器。

<?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>

由于此错误与JDK相关,因此必须在需要该属性的类中创建委托方法。

最新更新