1. Overview
1.概述
The Java platform used to have a monolithic architecture, bundling all packages as a single unit.
Java平台曾经有一个单一的架构,将所有的包捆绑成一个单元。
In Java 9, this was streamlined with the introduction of the Java Platform Module System (JPMS), or Modules for short. Related packages were grouped under modules, and modules replaced packages to become the basic unit of reuse.
在Java 9中,随着Java平台模块系统(JPMS),或简称Modules的引入,这种情况得到了简化。相关的包被归入模块,而模块取代了包,成为重用的基本单位。
In this quick tutorial, we’ll go through some of the issues related to modules that we may face when migrating an existing application to Java 9.
在这个快速教程中,我们将了解在将现有应用程序迁移到Java 9时可能面临的与模块有关的一些问题。
2. Simple Example
2.简单的例子
Let’s take a look at a simple Java 8 application that contains four methods, which are valid under Java 8 but challenging in future versions. We’ll use these methods to understand the impact of migration to Java 9.
让我们看看一个简单的Java 8应用程序,它包含四个方法,这些方法在Java 8下有效,但在未来的版本中具有挑战性。我们将用这些方法来理解迁移到Java 9的影响。
The first method fetches the name of the JCE provider referenced within the application:
第一个方法获取应用程序中引用的JCE提供者的名称。
private static void getCrytpographyProviderName() {
LOGGER.info("1. JCE Provider Name: {}\n", new SunJCE().getName());
}
The second method lists the names of the classes in a stack trace:
第二个方法在堆栈跟踪中列出了类的名称。
private static void getCallStackClassNames() {
StringBuffer sbStack = new StringBuffer();
int i = 0;
Class<?> caller = Reflection.getCallerClass(i++);
do {
sbStack.append(i + ".").append(caller.getName())
.append("\n");
caller = Reflection.getCallerClass(i++);
} while (caller != null);
LOGGER.info("2. Call Stack:\n{}", sbStack);
}
The third method converts a Java object into XML:
第三个方法将一个Java对象转换为XML。
private static void getXmlFromObject(Book book) throws JAXBException {
Marshaller marshallerObj = JAXBContext.newInstance(Book.class).createMarshaller();
marshallerObj.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter sw = new StringWriter();
marshallerObj.marshal(book, sw);
LOGGER.info("3. Xml for Book object:\n{}", sw);
}
And the final method encodes a string to Base 64 using sun.misc.BASE64Encoder, from the JDK internal libraries:
而最后一个方法使用JDK内部库的sun.misc.BASE64Encoder将字符串编码为Base 64。
private static void getBase64EncodedString(String inputString) {
String encodedString = new BASE64Encoder().encode(inputString.getBytes());
LOGGER.info("4. Base Encoded String: {}", encodedString);
}
Let’s invoke all the methods from the main method:
让我们从主方法中调用所有的方法。
public static void main(String[] args) throws Exception {
getCrytpographyProviderName();
getCallStackClassNames();
getXmlFromObject(new Book(100, "Java Modules Architecture"));
getBase64EncodedString("Java");
}
When we run this application in Java 8 we get the following:
当我们在Java 8中运行这个应用程序时,我们得到如下结果。
> java -jar target\pre-jpms.jar
[INFO] 1. JCE Provider Name: SunJCE
[INFO] 2. Call Stack:
1.sun.reflect.Reflection
2.com.baeldung.prejpms.App
3.com.baeldung.prejpms.App
[INFO] 3. Xml for Book object:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<book id="100">
<title>Java Modules Architecture</title>
</book>
[INFO] 4. Base Encoded String: SmF2YQ==
Normally, Java versions guarantee backward compatibility, but the JPMS changes some of this.
通常情况下,Java版本保证向后兼容,但JPMS改变了其中的一些情况。
3. Execution in Java 9
3.在Java 9中执行
Now, let’s run this application in Java 9:
现在,让我们在Java 9中运行这个应用程序。
>java -jar target\pre-jpms.jar
[INFO] 1. JCE Provider Name: SunJCE
[INFO] 2. Call Stack:
1.sun.reflect.Reflection
2.com.baeldung.prejpms.App
3.com.baeldung.prejpms.App
[ERROR] java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext
[ERROR] java.lang.NoClassDefFoundError: sun/misc/BASE64Encoder
We can see that the first two methods run fine, while the last two failed. Let’s investigate the cause of failure by analyzing the dependencies of our application. We’ll use the jdeps tool that shipped with Java 9:
我们可以看到,前两个方法运行良好,而后两个方法则失败了。让我们通过分析我们应用程序的依赖关系来调查失败的原因。我们将使用随Java 9提供的jdeps工具。
>jdeps target\pre-jpms.jar
com.baeldung.prejpms -> com.sun.crypto.provider JDK internal API (java.base)
com.baeldung.prejpms -> java.io java.base
com.baeldung.prejpms -> java.lang java.base
com.baeldung.prejpms -> javax.xml.bind java.xml.bind
com.baeldung.prejpms -> javax.xml.bind.annotation java.xml.bind
com.baeldung.prejpms -> org.slf4j not found
com.baeldung.prejpms -> sun.misc JDK internal API (JDK removed internal API)
com.baeldung.prejpms -> sun.reflect JDK internal API (jdk.unsupported)
The output from the command gives:
命令的输出结果是:。
- the list of all packages inside our application in the first column
- the list of all dependencies within our application in the second column
- the location of the dependencies in the Java 9 platform – this can be a module name, or an internal JDK API, or none for third-party libraries
4. Deprecated Modules
4.废弃的模块
Let’s now try to solve the first error java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext.
现在让我们试着解决第一个错误 java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext。
As per the dependencies list, we know that java.xml.bind package belongs to java.xml.bind module which seems to be a valid module. So, let’s take a look at the official documentation for this module.
根据依赖列表,我们知道java.xml.bind包属于java.xml.bind模块,它似乎是一个有效的模块。因此,让我们看看该模块的官方文档。
The official documentation says that java.xml.bind module is deprecated for removal in a future release. Consequently, this module is not loaded by default into the classpath.
官方文档说,java.xml.bind模块已被废弃,将在未来的版本中删除。因此,该模块默认不被加载到classpath中。
However, Java provides a method to load modules on demand by using the –add-modules option. So, let’s go ahead and try it:
然而,Java提供了一种方法,通过使用-add-modules选项来按需加载模块。所以,让我们去试试吧。
>java --add-modules java.xml.bind -jar target\pre-jpms.jar
...
INFO 3. Xml for Book object:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<book id="100">
<title>Java Modules Architecture</title>
</book>
...
We can see that the execution was successful. This solution is quick and easy but not the best solution.
我们可以看到,执行是成功的。这个解决方案是快速和简单的,但不是最好的解决方案。
As long term solution, we should add the dependency as a third party library using Maven:
作为长期解决方案,我们应该使用Maven将依赖项添加为第三方库。
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
5. JDK Internal APIs
5.JDK内部API
Let’s now look into the second error java.lang.NoClassDefFoundError: sun/misc/BASE64Encoder.
现在我们来看看第二个错误java.lang.NoClassDefFoundError: sun/misc/BASE64Encoder.。
From the dependencies list, we can see that sun.misc package is a JDK internal API.
从依赖关系列表中,我们可以看到sun.misc包是一个JDK内部API。
Internal APIs, as the name suggests, are private code, used internally in the JDK.
内部API,顾名思义,是私人代码,在JDK内部使用。
In our example, the internal API appears to have been removed from the JDK. Let’s check what the alternative API for this is by using the –jdk-internals option:
在我们的例子中,内部API似乎已经从JDK中删除了.让我们通过使用-jdk-internals选项来检查这个的替代API是什么。
>jdeps --jdk-internals target\pre-jpms.jar
...
JDK Internal API Suggested Replacement
---------------- ---------------------
com.sun.crypto.provider.SunJCE Use java.security.Security.getProvider(provider-name) @since 1.3
sun.misc.BASE64Encoder Use java.util.Base64 @since 1.8
sun.reflect.Reflection Use java.lang.StackWalker @since 9
We can see that Java 9 recommends using java.util.Base64 instead of sun.misc.Base64Encoder. Consequently, a code change is mandatory for our application to run in Java 9.
我们可以看到,Java 9建议使用java.util.Base64,而不是sun.misc.Base64Encoder.,因此,为了让我们的应用程序在Java 9中运行,必须改变代码。
Notice that there are two other internal APIs we’re using in our application for which Java platform has suggested replacements, but we didn’t get any error for these:
请注意,在我们的应用程序中,还有两个内部API,Java平台建议用它们来替代,但我们没有得到任何错误。
- Some internal APIs like sun.reflect.Reflection were considered critical to the platform and so were added to a JDK-specific jdk.unsupported module. This module is available by default on the classpath in Java 9.
- Internal APIs like com.sun.crypto.provider.SunJCE are provided only on certain Java implementations. As long as code using them is run on the same implementation, it will not throw any errors.
In all the cases in this example, we’re using internal APIs, which is not a recommended practice. Therefore, the long term solution is to replace them with suitable public APIs provided by the platform.
在这个例子中的所有情况下,我们都在使用内部API,这不是一个值得推荐的做法。因此,长期的解决方案是用平台提供的合适的公共API来取代它们。
6. Conclusion
6.结语
In this article, we saw how the module system introduced in Java 9 can cause migration problems for some older applications using deprecated or internal APIs.
在这篇文章中,我们看到了在Java 9中引入的模块系统如何对一些使用已废弃或内部API的旧应用程序造成迁移问题。
We also saw how to apply short term and long term fixes for these errors.
我们还看到如何对这些错误进行短期和长期的修复。
As always, the examples from this article are available over on GitHub.
一如既往,本文中的例子可以在GitHub上找到。