DesignForJDK9
From APIDesign
(→The Solution) |
|||
Line 45: | Line 45: | ||
</source> | </source> | ||
the class can be instantiated, one can call its method ''say'' and (surprisingly) it is even possible to call the ''addPropertyChangeListener'' method with '''null''' parameter! [[JDK]] class verifier is fine with that. | the class can be instantiated, one can call its method ''say'' and (surprisingly) it is even possible to call the ''addPropertyChangeListener'' method with '''null''' parameter! [[JDK]] class verifier is fine with that. | ||
+ | |||
+ | === Implementing the Listener Support === | ||
+ | |||
+ | It is certainly not good idea to use {{JDK|java/beans|PropertyChangeSupport}} directly: | ||
+ | |||
+ | <source lang="java"> | ||
+ | import java.beans.PropertyChangeListener; | ||
+ | import java.beans.PropertyChangeSupport; | ||
+ | |||
+ | public final class Bean9 { | ||
+ | private final PropertyChangeSupport ps = new PropertyChangeSupport(this); | ||
+ | |||
+ | public void addPropertyChangeListener(PropertyChangeListener pcl) { | ||
+ | ps.addPropertyChangeListener(pcl); | ||
+ | } | ||
+ | |||
+ | public void removePropertyChangeListener(PropertyChangeListener pcl) { | ||
+ | ps.removePropertyChangeListener(pcl); | ||
+ | } | ||
+ | |||
+ | public void say(String msg) { | ||
+ | System.out.println(msg); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </source> | ||
+ | |||
+ | This time the [[JVM]] will complain and raise a {{JDK|java/lang|NoClassDefFoundError}}: | ||
+ | |||
+ | <source lang="bash"> | ||
+ | starting | ||
+ | Exception in thread "main" java.lang.NoClassDefFoundError: java/beans/PropertyChangeSupport | ||
+ | at beans9.Bean9.<init>(Bean9.java:7) | ||
+ | at beans9.Main.main(Main.java:6) | ||
+ | Caused by: java.lang.ClassNotFoundException: java.beans.PropertyChangeSupport | ||
+ | at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582) | ||
+ | at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:185) | ||
+ | at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496) | ||
+ | </source> | ||
+ | |||
+ | however making the listener support lazy seems to work find: | ||
+ | |||
+ | <source lang="bash"> | ||
+ | import java.beans.PropertyChangeListener; | ||
+ | import java.beans.PropertyChangeSupport; | ||
+ | |||
+ | public final class Bean9 { | ||
+ | private PropertyChangeSupport ps; | ||
+ | |||
+ | public void addPropertyChangeListener(PropertyChangeListener pcl) { | ||
+ | if (pcl == null) { | ||
+ | return; | ||
+ | } | ||
+ | if (ps == null) { | ||
+ | ps = new PropertyChangeSupport(this); | ||
+ | } | ||
+ | ps.addPropertyChangeListener(pcl); | ||
+ | } | ||
+ | |||
+ | public void removePropertyChangeListener(PropertyChangeListener pcl) { | ||
+ | ps.removePropertyChangeListener(pcl); | ||
+ | } | ||
+ | |||
+ | public void say(String msg) { | ||
+ | System.out.println(msg); | ||
+ | if (ps != null) { | ||
+ | ps.firePropertyChange("msg", null, msg); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | Does not work with reflection. Once one tries to obtain the list of methods, an error is generated | ||
+ | |||
+ | <source lang="java"> | ||
+ | System.err.println("methods: " + Arrays.toString(Bean9.class.getMethods())); | ||
+ | |||
+ | Exception in thread "main" java.lang.NoClassDefFoundError: java/beans/PropertyChangeListener | ||
+ | at java.base/java.lang.Class.getDeclaredMethods0(Native Method) | ||
+ | at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3139) | ||
+ | at java.base/java.lang.Class.privateGetPublicMethods(Class.java:3164) | ||
+ | at java.base/java.lang.Class.getMethods(Class.java:1861) | ||
+ | at beans9.Main.main(Main.java:14) | ||
+ | Caused by: java.lang.ClassNotFoundException: java.beans.PropertyChangeListener | ||
+ | at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582) | ||
+ | at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:185) | ||
+ | at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496) | ||
+ | ... 5 more | ||
+ | </source> | ||
+ | |||
+ | but who'd be doing reflection when the {{JDK|java/beans|Introspector}} isn't present, right? | ||
+ | |||
+ | == Designing new [[API]] == | ||
+ | |||
+ | Listener for a purpose. Design new dedicated one. | ||
+ | |||
[[TBD]]. | [[TBD]]. |
Revision as of 14:30, 11 August 2017
Looks like Jigsaw - e.g. JDK9 is unstoppable. The release will happen soon and projects are slowly starting to use the new JDK. This applies to Graal compiler project I am working on as well. We want and need it to run on JDK9 and be good modular citizen. For a while we produce JDK9 builds and test against (whole) JDK9, but last week I got a simple, but very important question:
Does Graal run on java.base only JDK9?
Simple question with many consequences and outcome that may be interesting for everyone who wants to port their application or library to JDK9 and/or make it run on slimmed down version of the JDK.
Contents |
Modular Java SE
jdk-9/bin/jlink --output jdk-9-base --add-modules java.base,jdk.internal.vm.ci --module-path jdk-9/jmods/
Want PropertyChangeListener? Get Swing with it!
JavaBean specification, circular dependency, other projects JAXB or Spring
The Solution
require static
Having PropertyChangeListener in API signatures is OK. That means methods like
public final class Bean9 { public void addPropertyChangeListener(PropertyChangeListener l); public void removePropertyChangeListener(PropertyChangeListener l); public void say(String msg) { System.out.println(msg); } }
is fine. The Bean9 class can still be loaded on the JDK containing just the java.base module. Following code can be used without any issues:
final class Main { public static void main(String[] args) { System.err.println("starting"); Bean9 b = new Bean9(); b.say("Hello"); b.addPropertyChangeListener(null); b.say("world"); }
the class can be instantiated, one can call its method say and (surprisingly) it is even possible to call the addPropertyChangeListener method with null parameter! JDK class verifier is fine with that.
Implementing the Listener Support
It is certainly not good idea to use PropertyChangeSupport directly:
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; public final class Bean9 { private final PropertyChangeSupport ps = new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener pcl) { ps.addPropertyChangeListener(pcl); } public void removePropertyChangeListener(PropertyChangeListener pcl) { ps.removePropertyChangeListener(pcl); } public void say(String msg) { System.out.println(msg); } }
This time the JVM will complain and raise a NoClassDefFoundError:
starting Exception in thread "main" java.lang.NoClassDefFoundError: java/beans/PropertyChangeSupport at beans9.Bean9.<init>(Bean9.java:7) at beans9.Main.main(Main.java:6) Caused by: java.lang.ClassNotFoundException: java.beans.PropertyChangeSupport at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:185) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496)
however making the listener support lazy seems to work find:
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; public final class Bean9 { private PropertyChangeSupport ps; public void addPropertyChangeListener(PropertyChangeListener pcl) { if (pcl == null) { return; } if (ps == null) { ps = new PropertyChangeSupport(this); } ps.addPropertyChangeListener(pcl); } public void removePropertyChangeListener(PropertyChangeListener pcl) { ps.removePropertyChangeListener(pcl); } public void say(String msg) { System.out.println(msg); if (ps != null) { ps.firePropertyChange("msg", null, msg); } } }
Does not work with reflection. Once one tries to obtain the list of methods, an error is generated
System.err.println("methods: " + Arrays.toString(Bean9.class.getMethods())); Exception in thread "main" java.lang.NoClassDefFoundError: java/beans/PropertyChangeListener at java.base/java.lang.Class.getDeclaredMethods0(Native Method) at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3139) at java.base/java.lang.Class.privateGetPublicMethods(Class.java:3164) at java.base/java.lang.Class.getMethods(Class.java:1861) at beans9.Main.main(Main.java:14) Caused by: java.lang.ClassNotFoundException: java.beans.PropertyChangeListener at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:185) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496) ... 5 more
but who'd be doing reflection when the Introspector isn't present, right?
Designing new API
Listener for a purpose. Design new dedicated one.
TBD.