services, take 1

Jesse Glick jesse.glick at oracle.com
Tue Dec 20 07:10:00 PST 2011


On 12/20/2011 06:00 AM, Neil Bartlett wrote:
> provide hooks that will allow "findability" of services in non-flat classloading models.

On this topic, I filed an API RFE [1] some time ago that ClassLoader.getResource{,s} returns just URL but this is insufficient for the caller to determine the originating 
loader, which can lead to problems in the presence of multiply loaded classes. Anyone trying to make something akin to ServiceLoader based on an unspecified ClassLoader 
(such as Thread.contextClassLoader) might need it. For a loader tied to a particular module system such as Jigsaw I suppose you would rather directly inspect the module 
hierarchy (especially if an index of available services by module is already available).


That reminds me that some libraries, such as Xerces if I recall correctly, have the following curious antipattern:

package org.apache.xerces.twiddling;
// part of an "advanced users" API:
public interface InternalDocumentTwiddler {
   void twiddle(Document doc);
}
package org.apache.xerces.twiddling;
// registered in META-INF/services/org.apache.xerces.twiddling.InternalDocumentTwiddler
public class StandardDocumentTwiddler implements InternalDocumentTwiddler {...}
package org.apache.xerces.twiddling;
public class TwiddleUtils {
   public static void twiddle(Document doc) {
     for (InternalDocumentTwiddler impl : ServiceLoader.load(InternalDocumentTwiddler.class)) {
       impl.twiddle(doc);
     }
   }
}

Of course they typically reimplement ServiceLoader so as to avoid a JDK 6 dependency, and may have some other configuration mechanism such as system properties etc., and 
may hardcode the class name of the standard impl rather than using META-INF/services. At any rate, this is a well-meaning attempt to allow components of the library to be 
replaced (or augmented) with custom services, yet still fall back to the standard implementation by default. And it works fine until you have a module system and try to 
load both Xerces 1.8.0 and Xerces 1.9.0 in parallel. Suddenly the context loader when asked for "org.apache.xerces.twiddling.StandardDocumentTwiddler" might load the 
1.8.0 version, or the 1.9.0 version, or neither. If it loads neither, the library will fail to find its own service registered in the very same JAR. And if it loads the 
1.8.0 version of StandardDocumentTwiddler when called from the 1.9.0 version of TwiddleUtils, or vice-versa, you get a mysterious ClassCastException (which ServiceLoader 
would wrap in a ServiceConfigurationError). The easiest fix in the library is to instantiate the known standard impl directly, reserving reflection for custom 
implementations; on the caller side, the workaround is

ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(TwiddleUtils.class.getClassLoader());
try {
   TwiddleUtils.twiddle(doc);
} finally {
   Thread.currentThread().setContextClassLoader(old);
}

The analogous code using org.openide.util.Lookup.getDefault().lookupAll(InternalDocumentTwiddler.class) works inside the NetBeans module system because there is a 
workaround for #6865375, and the META-INF/services handler knows to ignore service implementations assignable to the wrong copy of the service interface.


[1] http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6865375



More information about the jigsaw-dev mailing list