Java 8中接口的默认方法和Bean Info Introspector(Java Bean信息内省器)

21

我在接口和BeanInfo Introspector的默认方法方面遇到了一些小问题。在这个例子中,有一个叫做Interface的接口:

public static interface Interface {
    default public String getLetter() {
        return "A";
    }
}

还有两个类ClassA和ClassB:

public static class ClassA implements Interface {
}

public static class ClassB implements Interface {
    public String getLetter() {
        return "B";
    }
}
在主方法中,应用程序打印来自BeanInfo的属性描述符:
public static String formatData(PropertyDescriptor[] pds) {
    return Arrays.asList(pds).stream()
            .map((pd) -> pd.getName()).collect(Collectors.joining(", "));

}

public static void main(String[] args) {


    try {
        System.out.println(
                formatData(Introspector.getBeanInfo(ClassA.class)
                        .getPropertyDescriptors()));
        System.out.println(
                formatData(Introspector.getBeanInfo(ClassB.class)
                        .getPropertyDescriptors()));
    } catch (IntrospectionException e) {
        e.printStackTrace();
    }

}

结果是:

class
class, letter
为什么ClassA中默认方法“letter”不可见作为属性?这是一个错误还是一个特性?

我删除了我的答案,因为它是一时糊涂。我的看法是这是一个 bug。如果 ClassC 扩展 ClassB 并且没有提供任何代码,则会显示 "class, letter"。因此,这不仅仅是报告有关直接类的情况。 - Devon_C_Miller
5个回答

7
我猜测,Introspector不会处理interface继承链,即使使用Java 8虚拟扩展方法(又称为defenders、默认方法),接口也可以具有类似于属性方法的东西。这是一个相当简单的内省器,声称它能够做到: BeanIntrospector 这是否可以被认为是一个bug还存在一些灰色地带,以下是我认为的原因。
显然,现在一个类可以从一个接口“继承”一个具有所有getter/setter/mutator特性的方法。但同时,整个事情违反了接口的目的——接口不可能提供任何可以被认为是属性的东西,因为它是无状态和无行为的,它只是用来描述行为的。即使是defender方法也基本上是静态的,除非它们访问一个具体实现的真正属性。
另一方面,如果我们假设defenders是官方继承的(而不是提供模糊定义的默认实现),它们应该导致在实现类中创建合成方法,并且这些方法属于类并作为PropertyDescriptor查找的一部分进行遍历。显然,这不是它的工作方式,否则整个事情就会起作用。 :) 似乎defender方法在这里得到了某种特殊处理。

3

调试显示,该方法在 Introspector#getPublicDeclaredMethods() 中被过滤:

if (!method.getDeclaringClass().equals(clz)) {
    result[i] = null; // ignore methods declared elsewhere
}

其中clz是所涉及类的完全限定名称。

由于ClassB对该方法进行了自定义实现,因此它成功通过了检查,而ClassA没有。


继承的方法呢?它们也会被过滤掉吗? - Sotirios Delimanolis
1
@SotiriosDelimanolis 是的,它也被过滤掉了。但是在几乎有200行长的processPropertyDescriptors()方法的范围内,它又被添加回来了 :) - Andrew Logvinov

2

我认为这也是一个bug。您可以通过为您的类提供专用的BeanInfo,并提供以下内容来解决此问题:

/* (non-Javadoc)
 * @see java.beans.SimpleBeanInfo#getAdditionalBeanInfo()
 */
@Override
public BeanInfo[] getAdditionalBeanInfo()
{
    Class<?> superclass = Interface.class;
    BeanInfo info = null;

    try
    {
        info = Introspector.getBeanInfo(superclass);
    }
    catch (IntrospectionException e)
    {
        //nothing to do
    }

    if (info != null)
        return new BeanInfo[] { info };

    return null;
}

2

我们注意到相同的问题,特别是在Java EL和JPA中。这可能会影响使用Introspector发现遵循JavaBean约定的属性的多个框架。

已经开放了一个官方Bug。我认为我应该添加这个参考,因为它正在为多个框架创建问题。

官方修复版本为21。我问过是否可能将其回溯到17。

编辑

有关EL特定问题的更多信息:https://github.com/jakartaee/expression-language/issues/43

在Jakarta EE 10中可能至少有部分修复。

但是,对于EE&lt; 10和JDK&lt; 21,有一个完全可行的解决方法。您只需创建一个描述从接口继承的属性的BeanInfo类即可。在这里,MyClass从一个接口继承了aProperty getter / setter。要将这些暴露给JDK Introspector,只需创建一个BeanInfo类:

public class MyClassBeanInfo extends SimpleBeanInfo {

    @Override
    public BeanInfo[] getAdditionalBeanInfo() {
        return new BeanInfo[] { new SimpleBeanInfo() {
            @Override
            public PropertyDescriptor[] getPropertyDescriptors() {
                try {
                    return new PropertyDescriptor[] { new PropertyDescriptor("aProperty", MyClass.class, "getAProperty", "setAProperty") };
                } catch (final IntrospectionException e) {
                    throw new RuntimeException(e);
                }
            }
        } };
    }
}

参考资料:https://docs.oracle.com/javase/8/docs/api/java/beans/BeanInfo.html


1
这是因为你只在接口和ClassB上有你的方法,而不是直接在ClassA上。然而,对我来说,这听起来像一个错误,因为我希望该属性出现在列表上。我怀疑Inrospector还没有跟上Java 8的特性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接