home / blog

Java look and feel class loading with OSGI

First some background… Java look and feels can be specified on the command line using the property “swing.defaultlaf”. This property gives the fully qualified classname of the look and feel to load.

java -Dswing.defaultlaf=com.example.foo.FooLookAndFeel -jar app.jar 

This class is loaded by javax.swing.UIManager. The loading is lazy, in that it is loaded the first time UIManager is called. This is typically by the creation of the first Swing component in your application.

The private method UIManager.maybeInitialize() ultimately calls the lesser-known method SwingUtilities.loadSystemClass(String classname).

    static Class loadSystemClass(String className) throws ClassNotFoundException {
        return Class.forName(className, true, Thread.currentThread().
                             getContextClassLoader());
    }

It doesn’t end there…. ComponentUIs, the classes that draw individual components such as MetalLabelUI are also loaded via string class-names. These are supplied by the look and feel and loaded into the UIManager. For example.

"LabelUI" => javax.swing.plaf.metal.MetalLabelUI
"RadioButtonUI " => javax.swing.plaf.metal.MetalRadioButtonUI

The ComponentUIs are lazy-loaded as required by UIManager.getUI(JComponent target). Luckily the class-loader in this case is configurable. It delegates to UIDefaults.getUI which easier uses the current class loader or the one specified by UIManager.getDefaults(“ClassLoader”). This can be configured via UIManger.getDefaults().put(“ClassLoader”, classloader);

Snippet of UIDefaults.getUI()

    public ComponentUI getUI(JComponent target) {

        Object cl = get("ClassLoader");
        ClassLoader uiClassLoader =
            (cl != null) ? (ClassLoader)cl : target.getClass().getClassLoader();
        Class uiClass = getUIClass(target.getUIClassID(), uiClassLoader);

So what? Well because UIManager uses the class-loader of the current thread this will not work well in a managed class-loading environment such as OSGI.

OSGI uses a sandbox class-loader which can only see packages/bundles of classes which you’ve explicitly configured to be visible.

So if your swing user interfaces are in bundles they’ll die at startup with ClassNotFoundException unless they have specific imports . What’s wrong with having specific imports? – well it violates modularity and plug-ability.

So what options do we have? One way forward is to put your look and feel in a bundle, then have the bundle activator trigger the loading of the look and feel. The only way I’ve found to do this so far is to call UIManager.getDefaults()

public class Activator implements BundleActivator {
  public void start(BundleContext context) throws Exception {
    // force look and feel to be instantiated.
    UIManager.getDefaults();

    /* set classloader for ComponentUI loading to this one 
     * (which can see the component UI classes. 
     */
    UIManager.getDefaults().put("ClassLoader", 
             this.getClass().getClassLoader());
  }

Another variation on this would be to have the bundle also set the look and feel to the one it owned. This would allow the look and feel to be configured simply by deploying the application with a given look and feel bundle, bypassing the -Dswing.defaultlaf property. However, this would not cater for the case where the application requires multiple look and feels.

This entry was posted in geek and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published.