RFR: 8347826: Introspector shows wrong method list after 8071693 [v9]

Sergey Bylokhov serb at openjdk.org
Thu Mar 27 02:29:17 UTC 2025


On Wed, 26 Mar 2025 20:00:21 GMT, Sergey Bylokhov <serb at openjdk.org> wrote:

>> Also, in the code above, it is not necessary to filter out private methods since iface.getMethods() returns only public
>
> The last question I have is about the module system when the interface is not exported.

I have tried to check various cases using the script below:

#!/bin/bash

rm -rf app/ bean/ classes/ bean.jar app.jar
mkdir -p bean/closed bean/exported app/app

cat <<EOF > bean/module-info.java
module bean {
    exports bean.exported;
}
EOF

########################
# non-exported classes
########################
cat <<EOF > bean/closed/ClosedI.java
package bean.closed;

public interface ClosedI {
    default int getBoo() { return 0; }
    default void setBoo(int a) { }
}
EOF

cat <<EOF > bean/closed/ClosedP.java
package bean.closed;

public class ClosedP {
    public int getToo() { return 0; }
    public void setToo(int a) { }
}
EOF

cat <<EOF > bean/closed/ClosedNonPublic.java
package bean.closed;

class ClosedNonPublic {
    public int getNoo() { return 0; }
    public void setNoo(int a) { }
}
EOF

########################
# exported classes
########################
cat <<EOF > bean/exported/OpenClass.java
package bean.exported;

import bean.closed.ClosedI;

public class OpenClass implements OpenI, ClosedI {
}
EOF

cat <<EOF > bean/exported/ClassCP.java
package bean.exported;

import bean.closed.ClosedP;

public class ClassCP extends ClosedP {
}
EOF

cat <<EOF > bean/exported/OpenP.java
package bean.exported;

public class OpenP {
    public int getZoo() { return 0; }
    public void setZoo(int a) { }
}
EOF

cat <<EOF > bean/exported/ClassOP.java
package bean.exported;

import bean.exported.OpenP;

public class ClassOP extends OpenP {
}
EOF

cat <<EOF > bean/exported/OpenI.java
package bean.exported;

public interface OpenI {
    default int getFoo() { return 0; }
    default void setFoo(int a) { }
}
EOF

########################
# main app
########################
cat <<EOF > app/module-info.java
module app {
    requires bean;
    requires java.desktop;
    exports app;
}
EOF


cat <<EOF > app/app/TestBean.java
package app;

import bean.exported.ClassCP;
import bean.exported.ClassOP;
import bean.exported.OpenClass;

import java.beans.*;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;

public final class TestBean {
    public static void main(String[] args) throws Exception {
        System.out.println("\n*** Check exported class implementing two interfaces: exported and closed ***");
        test(OpenClass.class);
        
        System.out.println("\n*** Check exported class extended from an exported class ***");
        test(ClassOP.class);
        
        System.out.println("\n*** Check exported class extended from a closed class ***");
        test(ClassCP.class);

        System.out.println("\n*** Check exported class via reflection ***");
        test(Class.forName("bean.exported.OpenP"));

        System.out.println("\n*** Check closed class via reflection ***");
        test(Class.forName("bean.closed.ClosedP"));

        System.out.println("\n*** Check closed non-public class via reflection ***");
        test(Class.forName("bean.closed.ClosedNonPublic"));
        
        
        System.out.println("\n*** Check exported bean implementing two interfaces: exported and closed ***");
        testBean(new OpenClass());

        System.out.println("\n*** Check exported bean extended from an exported class ***");
        testBean(new ClassOP());

        System.out.println("\n*** Check exported bean extended from a closed class ***");
        testBean(new ClassCP());

        System.out.println("\n*** Check exported bean via JavaBeans instantiation ***");
        testBean(createBean("bean.exported.OpenP"));

        System.out.println("\n*** Check closed bean via JavaBeans instantiation ***");
        testBean(createBean("bean.closed.ClosedP"));

        System.out.println("\n*** Check closed non-public bean via JavaBeans instantiation ***");
        testBean(createBean("bean.closed.ClosedNonPublic"));        


        System.out.println("\n*** Check exported class via reflection ***");
        testBean(Class.forName("bean.exported.OpenP").getDeclaredConstructor().newInstance());

        //System.out.println("\n*** Check closed class via reflection ***");
        //testBean(Class.forName("bean.closed.ClosedP").getDeclaredConstructor().newInstance());

        //System.out.println("\n*** Check closed non-public class via reflection ***");
        //testBean(Class.forName("bean.closed.ClosedNonPublic").getDeclaredConstructor().newInstance());
    }

    private static void test(Class<?> beanClass) throws Exception {
        var info = java.beans.Introspector.getBeanInfo(beanClass, Object.class);
        System.out.println(info.getBeanDescriptor());
        System.out.println("--- properties")
        for (var desc : info.getPropertyDescriptors()) {
            System.out.println(desc.getName());
            System.out.println("\tRead: " + desc.getReadMethod());    
            System.out.println("\tWrite: " + desc.getWriteMethod());
        }
    }
    
    private static void testBean(Object bean) throws Exception {
        if (bean == null) {
            return;
        }
        Class<?> beanClass = bean.getClass();
        var info = java.beans.Introspector.getBeanInfo(beanClass, Object.class);    
        System.out.println(info.getBeanDescriptor());
        System.out.println("--- properties");
        for (var desc : info.getPropertyDescriptors()) {
            System.out.println(desc.getName());
            Method readMethod = desc.getReadMethod();            
            System.out.println("\tRead: " + readMethod);    
            System.out.println("\tWrite: " + desc.getWriteMethod());
            try {
                Object value = readMethod.invoke(bean);
                System.out.println("\tRead: " + readMethod.getName() + " -> " + value);
            } catch (Exception e) {
                System.out.println("\tRead: " + readMethod.getName() + " -> ERROR: " + e);
            }
        }
    }    
    
    private static Object createBean(String className) {
        try {
            return Beans.instantiate(ClassLoader.getSystemClassLoader(), className);
        } catch (IOException | ClassNotFoundException e) {
            System.out.println("ERROR: Failed to instantiate bean: " + className);
            return null;
        }
    }
}
EOF

javac -d classes bean/module-info.java bean/closed/*.java bean/exported/*.java
jar --create --file bean.jar -C classes .
rm -rf classes

javac -d classes --module-path bean.jar app/module-info.java app/app/TestBean.java
jar --create --file app.jar -C classes .
rm -rf classes

if [[ "$OSTYPE" == "cygwin" ]]; then
    SEP=";"
else
    SEP=":"
fi

echo "Start..."
java --module-path "bean.jar${SEP}app.jar" --module app/app.TestBean


The new implementation of default methods in JavaBeans for non-exported interfaces behaves similarly to that for non-exported classes:
For example, in the script above, we create an `OpenClass` that implements both exported and non-exported interfaces. The Introspector reports properties for "both interfaces", but if we attempt to use the property from the non-exported interface, we will get an exception.
This is similar to the situation where we have a non-exported parent class for an exported class: `ClassCP`: the Introspector will report the property for the parent class, but it will not be possible to use it due to an exception at runtime.

@AlanBateman I found that you worked on updating JavaBeans for modules, see [this commit](https://hg.openjdk.org/jigsaw/jake/jdk/rev/e8703be87031). Do you remember if the work was completed and if the current behavior described above in this message is correct? I'm curious because that commit aimed to skip introspecting non-exported classes/fields/methods, [link ](https://github.com/openjdk/jdk/blob/24833403b6b93ca464720f00de0e8bd5e1c140be/src/java.desktop/share/classes/com/sun/beans/finder/MethodFinder.java#L136)to the current code.

@aivanov-jdk please take a look, probably I am missing something?

-------------

PR Review Comment: https://git.openjdk.org/jdk/pull/23443#discussion_r2015416746


More information about the client-libs-dev mailing list