1. 概述
Java 平台在 Java 9 之前采用的是单体式架构,所有类库打包为一个整体。
从 Java 9 开始,引入了 Java 平台模块系统(JPMS,Java Platform Module System),简称模块系统。模块成为代码组织和复用的基本单位,模块取代了包成为复用的基本单元。
本文将介绍在将旧有 Java 应用迁移到 Java 9 时可能遇到的一些典型问题,并提供相应的解决方案。
2. 简单示例
我们来看一个 Java 8 的简单应用,它包含四个方法,这些方法在 Java 8 下是合法的,但在 Java 9 中可能引发问题。我们通过这些方法来了解迁移过程中可能遇到的兼容性问题。
第一个方法用于获取 JCE 提供者的名称:
private static void getCrytpographyProviderName() {
LOGGER.info("1. JCE Provider Name: {}\n", new SunJCE().getName());
}
第二个方法列出调用栈中的类名:
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);
}
第三个方法使用 JAXB 将 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);
}
第四个方法使用 sun.misc.BASE64Encoder
对字符串进行 Base64 编码:
private static void getBase64EncodedString(String inputString) {
String encodedString = new BASE64Encoder().encode(inputString.getBytes());
LOGGER.info("4. Base Encoded String: {}", encodedString);
}
我们在 main
方法中调用上述方法:
public static void main(String[] args) throws Exception {
getCrytpographyProviderName();
getCallStackClassNames();
getXmlFromObject(new Book(100, "Java Modules Architecture"));
getBase64EncodedString("Java");
}
运行 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==
Java 通常保证向后兼容性,但 JPMS 的引入改变了这一状况。
3. 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
前两个方法正常运行,后两个方法报错。
我们可以使用 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)
输出中包含三列信息:
- 应用中的包名
- 依赖的类库
- 所属模块或是否为 JDK 内部 API
4. 被弃用的模块
首先看第一个错误:java.lang.NoClassDefFoundError: javax/xml/bind/JAXBContext
。
根据 jdeps
输出,javax.xml.bind
属于 java.xml.bind
模块,该模块在 Java 9 中 被标记为弃用(deprecated),意味着它不会默认加载。
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>
...
✅ 短期解决方案:使用 --add-modules
加载模块。
❌ 不推荐长期使用:因为该模块未来可能被彻底移除。
✅ 推荐长期解决方案:使用 Maven 显式引入 JAXB 依赖:
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.4.0-b180725.0427</version>
</dependency>
5. JDK 内部 API 的问题
第二个错误是:java.lang.NoClassDefFoundError: sun/misc/BASE64Encoder
。
sun.misc
是 JDK 内部 API,Java 9 中已将其标记为不可用。
使用 jdeps
的 --jdk-internals
参数查看建议的替代方案:
>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
✅ 解决方案:替换为 Java 官方支持的 API。
例如,将 BASE64Encoder
替换为:
String encodedString = Base64.getEncoder().encodeToString(inputString.getBytes());
⚠️ 注意:
sun.reflect.Reflection
被移到了jdk.unsupported
模块,Java 9 默认加载,所以不会报错。com.sun.crypto.provider.SunJCE
是特定于某些 JDK 实现的,只要运行在相同实现上就不会出错。
❌ 但总体建议:避免使用内部 API,改用标准 API,以提高兼容性和可维护性。
6. 总结
本文介绍了 Java 9 引入模块系统后可能导致的迁移问题,主要包括:
✅ 被弃用模块的加载问题(如 JAXB)
✅ 使用内部 API(如 sun.misc
)导致的类找不到问题
解决方案包括:
问题类型 | 短期方案 | 长期方案 |
---|---|---|
被弃用模块 | 使用 --add-modules 显式加载 |
添加第三方依赖(如 Maven) |
内部 API | 检查是否默认可用(如 jdk.unsupported) | 替换为标准 API(如 java.util.Base64 ) |
如需完整代码示例,请访问 GitHub。