DesignForJDK9

From APIDesign

Revision as of 15:41, 14 August 2017 by JaroslavTulach (Talk | contribs)
Jump to: navigation, search

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

Modularity is good. As the original NetBeans Platform architect, I can't say anything else, right? Modularity is the sole reason we can practice DistributedDevelopment and release enormously large software products on a flexible schedule. On a schedule which each (distributed) team can choose by itself.

It is great that JDK has finally made its steps towards Modular Java SE. That means we no longer need to download CORBA and Swing when running our headless applications. The modules introduced in JDK9 allow us to do that properly and not just an ad-hoc solution as JDK8 profiles. Since now all library and framework architects have a new target to aim at as a runtime platform - the java.base module. How do you create one?

jdk-9/bin/jlink --output jdk-9-base --add-modules java.base --module-path jdk-9/jmods/

The above command will create a new JDK structure in the jdk-9-base directory and will pick only the java.base module up. The base module contains just the core Java packages and is relatively (the module still contains about three thousand classes, while Bck2Brwsr VM minimal profile - see its javadoc - can do with less than 150) small - the java.base only JDK size shrinks to less than 50MB.

Want PropertyChangeListener? Get Swing with it!

Modularization of a large monolithic application has to be associated with compromises. I experienced that while working on Modularization of NetBeans Platform. It comes with no surprise the Jigsaw modularization of JDK comes with compromises too. However one of them is really nasty, I'd say!

Do you follow the JavaBean specification? Probably you do: if you don't know you probably do that unconsciously. If you name getters and setter of your properties as setPropName and getPropName, then you follow the [JavaBean]] spec.

Properties are one essential part of the JavaBeans. The other important part is about observing their changes. This is done by listeners. The JavaBean spec allows anybody to create their own listeners, but in addition to that it defines two general purpose ones: the PropertyChangeListener and VetoableChangeListener. Especially the first one is generally useful and often used in many APIs that just expose:

public void addPropertyChangeListener(PropertyChangeListener l);
public void removePropertyChangeListener(PropertyChangeListener l);

and allow anyone to observe changes in their properties. NetBeans APIs use that a lot. For example FileSystem uses both - property change as well as and vetoable listeners - and the filesystems API doesn't have anything with UI. The same can be said about Spring framework - it also uses a lot of classes from java.beans package and has no relation to (Swing UI) at all.

Alas, the java.beans package is going to be exposed from java.desktop JDK9 module! Try to use JavaBeans and get Swing with that!

I know why the java.beans package ended up in the desktop Jigsaw module. Some of its classes (like Applet reference AWT classes. Moreover there is a cyclic dependency - a lot of classes from Swing & co. reference PropertyChangeListener (obviously, as that is useful and adviced by JavaBean specification. As such it would be hard to separate java.beans package into own module. On the other hand we don't want all our API that use PropertyChangeListener to depend on Swing - the JDK team could have done a better job.

Anyway, it looks like that the PropertyChangeListener are going to be available only from java.desktop module. What can our projects (Spring, Graal, NetBeans) do with that?

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.

Personal tools
buy