Skip to main content

Dealing with multiple serial forms of the same class

Posted by emcmanus on July 19, 2005 at 6:38 AM PDT

One of the changes we made in version 1.1 of the JMX API, way back in early 2002, was to modify the serialization of certain classes. Because remote access was not part of the API at this time, this aspect had been a bit neglected in version 1.0, with the result that certain classes had underspecified or inefficient serial forms. Other implementations of the API could and did have incompatible serial forms as a result.

In version 1.1, we revisited all serializable classes and updated their serial forms where necessary. In order not to break interoperation with the previous version, we provided a system property jmx.serial.form that you could set to "1.0" to specify that you wanted to continue using the serial forms from version 1.0 of the Reference Implementation, imperfect as they were.

This worked reasonably well, but not perfectly. One problem is that a class can only have one serial form. So, even though we provided the jmx.serial.form property, you had to decide whether it was going to be 1.0 or 1.1. If you set it to 1.0, the compatibility code kicked in, but then you could not handle the new serial form. This is a fundamental constraint of the way serialization works.

However, all is not lost. Although a serializable class such as javax.management.ObjectName can only have one serial form, you can deal with more than one version of the class in the same Virtual Machine by using class loaders. Tom Ball recently blogged about this, and I would encourage you to check out what he wrote if you are interested in this topic.

Suppose in the same application you need to interact with the old and new JMX serial forms. For example, suppose you have a management client that interacts with some servers using the JMX Remote API, which requires version 1.2 of the JMX API, and some other servers using the proprietary connectors that were typically shipped with older app servers, based on version 1.0 of the JMX API. The techniques in Tom's blog can be applied here.

Let's suppose that you're running on J2SE 5.0, which means you have JMX API 1.2 by default. Then you can connect to the servers that use the JMX Remote API without doing anything special. But for the servers that use JMX API 1.0, you'll need some magic.

Because you're running on J2SE 5.0, you'll need a class loader that does not automatically delegate to its parent class loader. As a reminder, the usual behaviour of a class loader when loading a class is to first ask the parent class loader to find it, then look for it yourself with ClassLoader.findClass. But we don't want this because the parent class loader will find the 1.2 versions of the JMX classes. This will also be true when running on J2SE 1.4 with the JMX classes in the classpath.

The technique in Tom's blog is to create a class loader that has an explicit list of class name prefixes for which we don't delegate to the parent. We could do this with a list that includes "javax.management" and maybe "com.proprietary.someappserver" or whatever. However, it's usually enough not to delegate any classes to the parent class loader initially, but instead try to load the class yourself, and only then delegate if you can't. That's the idea behind this variant of Tomas Hurka's GJASTClassLoader from Tom's blog:

public class NonDelegatingClassLoader extends URLClassLoader {
    private final PermissionCollection permissions = new Permissions();
    {
        permissions.add(new AllPermission());
    }

    public NonDelegatingClassLoader(URL[] urls) {
        super(urls);
    }
   
    protected Class loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        try {
            Class c = findClass(name);
            if (resolve)
                resolveClass(c);
            return c;
        } catch (ClassNotFoundException e) {
            // Delegate to parent
            return super.loadClass(name, resolve);
        }
    }

    protected PermissionCollection getPermissions(CodeSource codesource) {
        return permissions;
    }
}

As shown in Tom's blog, this class loader is an isolated world, and the only way to execute code in that world from outside is through reflection. You could for example create a class called com.example.oldclient.Client that references the proprietary connector com.proprietary.someappserver.Connector. You package your class in /some/path/oldclient.jar and you have the proprietary stuff in /some/path/proprietary.jar, so you'll want to instantiate the NonDelegatingClassLoader something like this:

URL[] urls = {new URL("file:/some/path/oldclient.jar"),
              new URL("file:/some/path/proprietary.jar")};
ClassLoader cl = new NonDelegatingClassLoader(urls);
Class client = Class.forName("com.example.oldclient.Client", true, cl);
Method connect = client.getMethod("connect", new Class[] {String.class});
connect.invoke(null, new Object[] {"...address..."});

Or you could invoke a constructor rather than a static method, as was shown in Tom's blog.

This isn't exactly simple, but I've tested it against Sun's Java Dynamic Management Kit 4.0, which included JMX API 1.0, and I was able to connect to it from J2SE 5.0 without any jmx.serial.form tricks. It works!

Related Topics >>