Java 9 个关于 serviceloader 的依赖问题

Java 9 dependency issues regarding serviceloader

我有一个问题,关于 Java9 中服务加载器是如何根据这种情况发生变化的


场景

项目gertClassMain

package gert;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

public class Main { 
    static public void test() throws JAXBException {
        InputStream is = new ByteArrayInputStream("<Classes RUNTIME_INCLUDE_JARS=\"\"><Class></Class></Classes>".getBytes(StandardCharsets.UTF_8)); 
        JAXBContext jaxbContext = JAXBContext.newInstance(ClassesDef.class);
        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
        unmarshaller.unmarshal(is);
    }
}

项目gertClassClassesDef

package gert;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name="Classes")
public class ClassesDef {
    @XmlAttribute(name="RUNTIME_INCLUDE_JARS")
    public String jars=null;

    @XmlElement(name="Class")
    public String classes;
}

项目gertpom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>gert</groupId>
    <artifactId>gert</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <archive>
                        <manifest>
                            <mainClass>gert.Main</mainClass>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency><!-- org.eclipse.persistence.jaxb.JAXBContextFactory comes with this dependency-->
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-moxy</artifactId>
            <version>2.26</version>
        </dependency>
    </dependencies>
</project>

项目cristinaClassMain

package cristina;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;

public class Main {
    public static void main(String[] args) throws Exception {
        System.setProperty("javax.xml.bind.JAXBContextFactory", "org.eclipse.persistence.jaxb.JAXBContextFactory");
        String bWithDep = "C:\Users\gert\eclipse-workspace91java\gert\target\gert-0.0.1-SNAPSHOT-jar-with-dependencies.jar";
        List<URL> jars = new java.util.ArrayList<URL>();
        File f;
        f = new File(bWithDep);
        jars.add(f.toURL());
        URL[] urls = (URL[])jars.toArray(new URL[jars.size()]);
        URLClassLoader urlloader = URLClassLoader.newInstance(urls, ClassLoader.getSystemClassLoader());
        System.out.println("Before\tgert.Main.test();.");
        Class<?> c= Class.forName("gert.Main", true, urlloader);
        Object gert = c.newInstance();
        Method m = c.getMethod("test");
        m.invoke(gert);
        System.out.println("After\tgert.Main.test();.");
    }
}

项目cristinapom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cristina</groupId>
  <artifactId>cristina</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <build>
    <sourceDirectory>src</sourceDirectory>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.7.0</version>
        <configuration>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <!-- <dependency>
      <groupId>javax.xml.bind</groupId>
         <artifactId>jaxb-api</artifactId>
         <version>2.3.0</version>
      </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-moxy</artifactId>
        <version>2.26</version>
    </dependency> -->
  </dependencies>
</project>

于是cristina main加载了gert项目并执行了一个名为test()

的gert方法

测试

Java 8
当项目是 运行 且 java 8 时,它可以工作

命令

"C:\Program Files\Java\jre1.8.0_151\bin\java.exe" -cp C:\Users\gert\eclipse-workspace91java\cristina\target\cristina-0.0.1-SNAPSHOT.jar cristina.Main

输出

Before gert.Main.test();. After gert.Main.test();.
Java 9
当用 java 9 做同样的事情时,它不会

命令

"C:\Program Files\Java\jre-9.0.1\bin\java.exe" -cp C:\Users\gert\eclipse-workspace91java\cristina\target\cristina-0.0.1-SNAPSHOT.jar cristina.Main

输出

Before  gert.Main.test();.
Exception in thread "main" java.lang.reflect.InvocationTargetException
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.base/java.lang.reflect.Method.invoke(Unknown Source)
        at cristina.Main.main(Main.java:23)
Caused by: javax.xml.bind.JAXBException: Implementation of JAXB-API has not been found on module path or classpath.
 - with linked exception:
[java.lang.ClassNotFoundException: org.eclipse.persistence.jaxb.JAXBContextFactory]
        at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:278)
        at javax.xml.bind.ContextFinder.find(ContextFinder.java:397)
        at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:721)
        at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:662)
        at gert.Main.test(Main.java:14)
        ... 5 more
Caused by: java.lang.ClassNotFoundException: org.eclipse.persistence.jaxb.JAXBContextFactory
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source)
        at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
        at javax.xml.bind.ServiceLoaderUtil.nullSafeLoadClass(ServiceLoaderUtil.java:122)
        at javax.xml.bind.ServiceLoaderUtil.safeLoadClass(ServiceLoaderUtil.java:155)
        at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:276)
        ... 9 more

以上两个是直接从命令行完成的。
但是
当我取消注释 cristina 项目的 pom.xml 中的依赖项并执行 maven 安装和 运行 来自 eclipse 的项目时,[=101] =] 9 个作品。因此,在 运行 构建项目时,eclipse 似乎也考虑了 Maven 依赖项。

问题

当依赖项在 gert 项目中并且仅由 gert 项目使用时,为什么 cristina 项目在 运行 将其与 Java9?

异常的原因可能是 java.xml.bind 是一个可升级的模块。

正如模块的风险和假设中的 JEP 261: Module System 所述:

If a package is defined in both a named module and on the class path then the package on the class path will be ignored. The class path can, therefore, no longer be used to augment packages that are built into the environment.

因此包 org.eclipse.persistence.jaxb 似乎被忽略了。最终,代码行 ContextFinder.newInstance 调用 JAXBContextFactory.createContext 并提供要绑定的 类 (ClassDef)。该文档进一步指出

throws JAXBException - if an error was encountered while creating the JAXBContext, such as (but not limited to) : ...

  • classesToBeBound are not open to java.xml.bind module

您可以尝试做的是 运行 应用程序利用

--upgrade-module-path /path/to/jaxb-api/dependency...

A : separated list of directories, each directory is a directory of modules that replace upgradeable modules in the runtime image