Default method in interface in Java 8 and Bean Info Introspector
Asked Answered
P

5

21

I have a little problem with default methods in Interface and BeanInfo Introspector. In this example, there is interface: Interface

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

and two classes ClassA and ClassB:

public static class ClassA implements Interface {
}

public static class ClassB implements Interface {
    public String getLetter() {
        return "B";
    }
}

In main method app prints PropertyDescriptors from 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();
    }

}

And the result is:

class
class, letter

Why default method "letter" is not visible as property in ClassA? Is it bug or feature?

Predecease answered 22/4, 2014 at 12:5 Comment(1)
I deleted my answer as it was a brain-fart. My 2 cents is it's a bug. If ClassC extends ClassB and provides no code, it shows class, letter. So it's not a case of only reporting about the immediate class.Ewers
P
2

We noticed the same problem, particularly with Java EL and JPA. This is likely to affect multiple frameworks that use Introspector to discover properties that follow the JavaBean convention.

There is an official Bug opened. I thought I would add this for reference since it is creating problems for multiple frameworks.

The official Fix version is 21. I asked if it could possibly be backported to 17.

EDIT

More Info for EL specific issues: https://github.com/jakartaee/expression-language/issues/43

There may be at least a partial fix in Jakarta EE 10.

However, there is a fully working workaround for EE < 10 and JDK < 21. You can simply make a BeanInfo class that describes the properties inherited from the interface. Here, MyClass is inheriting aProperty getter/setters from an interface. To expose these to the JDK Introspector, simply create a BeanInfo class:

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

Reference: https://docs.oracle.com/javase/8/docs/api/java/beans/BeanInfo.html

Procora answered 9/5, 2023 at 20:7 Comment(0)
W
7

I guess, Introspector does not process interface hierarchy chains, even though with Java 8 virtual extention methods (aka defenders, default methods) interfaces can have something that kinda sorta looks like property methods. Here's a rather simplistic introspector that claims it does: BeanIntrospector

Whether this can be considered a bug is somewhat of a gray area, here's why I think so.

Obviously, now a class can "inherit" from an interface a method that has all the qualities of what's oficially considered a getter/setter/mutator. But at the same time, this whole thing is against interface's purpose -- an interface can not possibly provide anything that can be considered a property, since it's stateless and behaviorless, it's only meant to describe behavior. Even defender methods are basically static unless they access real properties of a concrete implementation.

On the other hand, if we assume defenders are officially inherited (as opposed to providing default implementation which is a rather ambiguous definition), they should result in synthetic methods being created in the implementing class, and those belong to the class and are traversed as part of PropertyDescriptor lookup. Obviously this is not the way it is though, otherwise the whole thing would be working. :) It seems that defender methods are getting some kind of special treatment here.

Wolcott answered 22/4, 2014 at 20:33 Comment(0)
M
3

Debugging reveals that this method is filtered out at Introspector#getPublicDeclaredMethods():

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

where clz is a fully-qualified name of the class in question.

Since ClassB has custom implementation of this method, it passes the check successfully while ClassA doesn't.

Mandie answered 22/4, 2014 at 20:31 Comment(2)
What about inherited methods? Wouldn't those get filtered out as well?Warram
@SotiriosDelimanolis Yes, it gets filtered out as well. But then in scope of processPropertyDescriptors() method which is almost 200 lines long it is added back :)Mandie
C
2

I think also that it is a bug. You can solve this using a dedicated BeanInfo for your class, and by providing somthing like that :

/* (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;
}
Crank answered 27/4, 2018 at 12:35 Comment(0)
P
2

We noticed the same problem, particularly with Java EL and JPA. This is likely to affect multiple frameworks that use Introspector to discover properties that follow the JavaBean convention.

There is an official Bug opened. I thought I would add this for reference since it is creating problems for multiple frameworks.

The official Fix version is 21. I asked if it could possibly be backported to 17.

EDIT

More Info for EL specific issues: https://github.com/jakartaee/expression-language/issues/43

There may be at least a partial fix in Jakarta EE 10.

However, there is a fully working workaround for EE < 10 and JDK < 21. You can simply make a BeanInfo class that describes the properties inherited from the interface. Here, MyClass is inheriting aProperty getter/setters from an interface. To expose these to the JDK Introspector, simply create a BeanInfo class:

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

Reference: https://docs.oracle.com/javase/8/docs/api/java/beans/BeanInfo.html

Procora answered 9/5, 2023 at 20:7 Comment(0)
S
1

This is because you only have your method on Interface and ClassB, not on ClassA directly. However it sounds to me like a bug since I'd expect that property to showup on the list. I suspect Inrospector did not catch up with Java 8 features yet.

Strap answered 22/4, 2014 at 19:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.