关于 serviceloader 的 Java 9 依赖问题

标签 java reflection dependencies java-9 serviceloader

我有一个关于 Java 9 中的服务加载器如何根据此场景进行更改的问题


场景

项目gertMain

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);
    }
}

项目gertClassesDef

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;
}

项目gert pom.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>

项目cristinaMain

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();.");
    }
}

项目cristina pom.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 克里斯蒂娜.Main

输出

在 gert.Main.test(); 之前。 在 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

以上 2 步是直接从命令行完成的。
但是
当我取消注释 cristina 项目的 pom.xml 中的依赖项并进行 Maven 安装并从 Eclipse 运行该项目时,java 9作品。看来eclipse在运行项目时也考虑了maven依赖。

问题

当依赖项位于 gert 项目中且仅由 gert 项目使用时,为什么 cristina 项目在运行时会抛出异常使用 Java 9?

最佳答案

异常的原因可能是 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 与提供的要绑定(bind)的类 (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

关于关于 serviceloader 的 Java 9 依赖问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47437785/

相关文章:

java - Hibernate 整数的跨字段验证?

c# - 关于c#委托(delegate)创建的性能

java - 反射-EasyMock-ClassCastException

android - Gradle 错误 Cannot invoke method dependencies() on null object

.net - 如何获得解决方案的依赖链和成员 Hook 的可视化表示?

java - 如何持续检查IP是否可达?

java - SortedArrayList 中的 BinarySearch

java - 如何更改 Spring SAML 目标端点?

javascript - Angular2 使用 npm reflect-metadata 或 core-js/es7/reflect

java - Maven 和 java 插件框架