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