Why does the Java Extension Mechanism not check th

2019-07-10 05:27发布

问题:

I have created a maven project L and written a Java extension (i.e. an optional package) implementing (i.e. extending) the (abstract) service providers that implement (i.e. extend) LocaleServiceProvider, to support a dialect (let's call it xy) that isn't normally supported by the JRE. (I do not want to use the CLDR extension that came with Java 8, even though I'm running 8.141.)

The project compiles, and produces a jar with a META-INF/services folder that contains the provider-configuration files in UTF-8 with the qualified provider class names being on a line that ends with a line feed (\n).

I have then declared a maven dependency in my project P on the locale project L, and I thought that that would work, because the tutorial states

The extension framework makes use of the class-loading delegation mechanism. When the runtime environment needs to load a new class for an application, it looks for the class in the following locations, in order:

[...]

  1. The class path: classes, including classes in JAR files, on paths specified by the system property java.class.path. If a JAR file on the class path has a manifest with the Class-Path attribute, JAR files specified by the Class-Path attribute will be searched also. By default, the java.class.path property's value is ., the current directory. You can change the value by using the -classpath or -cp command-line options, or setting the CLASSPATH environment variable. The command-line options override the setting of the CLASSPATH environment variable.

Maven puts all dependencies on the classpath, I believe.

Yet when I run my unit test in P (in IntelliJ; L is on the classpath), it fails:

@Test
public void xyLocalePresent() {
    Locale xy = new Locale("xy");
    assertEquals("P not on classpath", xy, com.example.l.Locales.XY); // access constant in my Locale project L; should be equals to locale defined here
    SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, xy);
    assertEquals("dd/MM/yy", df.toPattern()); // fails; L specifies the short date pattern as dd/MM/yy
}

I have to start it with -Djava.locale.providers=SPI,JRE -Djava.ext.dirs=/path/to/project/L/target. If I do that, it works, indicating that L's service providers were loaded successfully (indicating the jar's structure is ok).

NB: the Java 8 technotes say that the order SPI,JRE is the default.

Why, oh why does it not work when I just put L on the classpath? Why do I have to point to it explicitly?

Update: After going through the JavaDoc again, I just saw this (emphasis mine):

Implementations of these locale sensitive services are packaged using the Java Extension Mechanism as installed extensions.

That explains things. :(

Is there any way to make this work by just putting L on the classpath when P runs, i.e. without having to install L (or having to use -D system properties)? (P uses maven, Struts2 and Spring, if that helps...)

回答1:

In more complex applications, such as web servers (e.g. Tomcat), there are multiple ClassLoaders, so each WebApp served by the web server can be kept independent.

The extension mechanism is for extending the core Java functionality, i.e. features available globally within the running JVM (the web server). As such, they must be loaded by the System ClassLoader.

The standard way to add an extension to the code Java runtime, is to either

  • add the Jar file to the JRE_HOME/lib/ext folder
  • add extra folders to be searched by specifying the java.ext.dirs system property

You could also just add it to the Bootstrap ClassPath yourself, but that might cause problems if the Security Manager is activated. Not sure about that part. So it's best to do it the official way.

Note that the classpath defined by the CLASSPATH environment variable, or the -cp command-line option, does not define the Bootstrap ClassPath.

To learn more, read the Java documentation "How Classes are Found".