Java 9 揭秘(8. JDK 9重大改变下)

Tips
做一个终身学习的人。

Java 9 揭秘(8. JDK 9重大改变下)_第1张图片
Java 9

六. 资源命名语法

资源使用由斜线分隔的字符串序列命名,例如com/jdojo/states.png,/com/jdojo/words.png和logo.png。 如果资源名称以斜线开头,则被视为绝对资源名称。

使用以下规则从资源名称中估算包(package)的名称:

  • 如果资源名称以斜线开头,删除第一个斜线。 例如,对于资源名称/com/jdojo/words.png,此步骤将导致com/jdojo/words.png。
  • 从最后一个斜线开始删除资源名称中的所有字符。 在这个例子中,com/jdojo/words.png导致com/jdojo。
  • 用点号(.)替换名称中的每个剩余的斜线。 所以,com/jdojo被转换成com.jdojo。 生成的字符串是包名称。

有些情况下使用这些步骤会导致一个未命名的包或一个无效的包名称。 包名称(如果存在)必须由有效的Java标识符组成。 如果没有包名称,它被称为未命名的包。 例如,将META-INF/resource /logo.png视为资源名称。 应用上一组规则,其包名称将被计算为“META-INF.resources”,它不是有效的包名,但它是资源的有效路径。

七. 查找资源的规则

由于向后兼容性和对模块系统的强封装的承诺,JDK 9中查找资源的新规则是复杂的,基于以下几个因素:

  • 包含资源的模块类型:命名的,开放的,未命名的或自动命名的模块;
  • 正在访问资源的模块:它是同一个模块还是另一个模块?
  • 正在被访问的资源的包名称:它是否是有效Java包? 这是一个未命名的包?
  • 封装包含资源的包:将包含资源的包导出,打开或封装到访问资源的模块?
  • 正在访问的资源的文件扩展名:资源是.class文件还是其他类型的文件?
  • 正在使用哪种类的方法来访问资源:ClassClassLoaderModule类?

以下规则适用于包含资源的命名模块:

  • 如果资源名称以.class结尾,则可以通过任何模块中的代码访问资源。 也就是说,任何模块都可以访问任何命名模块中的类文件。
  • 如果从资源名称计算的包名称不是有效的Java包名称,例如META-INF.resources,则可以通过任何模块中的代码访问该资源。
  • 如果从资源名称计算的包名称是未命名的包,例如对于资源名称(如word.png),则可以通过任何模块中的代码访问该资源。
  • 如果包含该资源的软件包对访问该资源的模块开放,则资源可以通过该模块中的代码访问。 一个包对模块开放,因为定义包的模块是一个开放的模块,或者模块打开所有其他模块的包,或者模块只使用一个限定的打开语句打开包。 如果没有以任何这些方式打开包,则该包中的资源不能被该模块外的代码访问。
  • 这个规则是上一个规则的分支。 打开未命名,自动或开放模块中的每个包,因此所有其他模块中的代码都可以访问这些模块中的所有资源。

Tips
命名模块中的包必须打开,而不是导出,以访问其资源。 导出一个模块的包允许其他模块访问该包中的公共类型(而不是资源)。

在访问命名模块中的资源时,ModuleClassClassLoader类中的各种资源查找方法的行为有所不同:

  • 可以使用Module类的getResourceAsStream()方法来访问模块中的资源。 此方法是调用方敏感的。 如果调用者模块不同,则此方法将应用所有资源可访问性规则,如上所述。
  • 在指定模块中定义的类的Class类中的getResource *()方法仅在该命名模块中定位资源。 也就是说,不能使用这些方法来定位定义调用这些方法的类的命名模块之外的类。
  • ClassLoader类中的getResource *()方法基于前面描述的规则列表来定位命名模块中的资源。 这些方法不是调用者敏感的。 在尝试查找资源本身之前,类加载器将资源搜索委托给其父类。 这些方法有两个例外:1)它们仅在无条件打开的包中定位资源。 如果使用限定的打开语句打开包,则这些方法将不会在这些包中找到资源。 2)它们搜索在类加载器中定义的模块。

Class对象将仅在它所属的模块中找到资源。 它还支持以斜线开头的绝对资源名称,以及不以斜线开头的相对资源名称。 以下是使用Class对象的几个示例:

// Will find the resource
URL url1 = Test.class.getResource("Test.class");
// Will not find the resource because the Test and Object classes are in different modules
URL url2 = Test.class.getResource("/java/lang/Object.class");
// Will find the resource because the Object and Class classes are in the same module, java.base
URL url3 = Object.class.getResource("/java/lang/Class.class");
// Will not find the resource because the Object class is in the java.base module whereas
// the Driver class is in the java.sql module
URL url4 = Object.class.getResource("/java/sql/Driver.class");

使用Module类定位资源需要具有该模块的引用。 如果可以访问该模块中的类,则在该Class对象上使用getModule()方法给出了模块引用。 这是获取模块引用的最简单方法。 有时候,你把模块名称作为字符串,而不是该模块中的类的引用。 可以从模块名称中找到模块引用。 模块被组织成由java.lang包中的ModuleLayer类的实例表示的层。 JVM至少包含一个boot 层。 boot层中的模块映射到内置的类加载器 —— 引导类加载器,平台类加载器和应用程序类加载器。 可以使用ModuleLayer类的boot()静态方法获取boot层的引用:

// Get the boot layer
ModuleLayer bootLayer = ModuleLayer.boot();

一旦获得boot层的引用,可以使用其findModule(String moduleName)方法获取模块的引用:

// Find the module named com.jdojo.resource in the boot layer
Optional m = bootLayer.findModule("com.jdojo.resource");
// If the module was found, find a resource in the module
if(m.isPresent()) {
    Module testModule = m.get();
    String resource = "com/jdojo/resource/opened/opened.properties";
    InputStream input = module.getResourceAsStream(resource);
    if (input != null) {
        System.out.println(resource + " found.");
    } else {
        System.out.println(resource + " not found.”);
    }
} else {
    System.out.println("Module com.jdojo.resource does not exist");
}

八. 访问命名模块中的资源的示例

在本部分中,将看到资源查找规则的具体过程。 在com.jdojo.resource的模块中打包资源,其声明如下所示。

// module-info.java
module com.jdojo.resource {
    exports com.jdojo.exported;
    opens com.jdojo.opened;
}

该模块导出com.jdojo.exported包,并打开com.jdojo.opened包。

以下是com.jdojo.resource模块中所有文件的列表:

  • module-info.class
  • unnamed.properties
  • META-INF\invalid_pkg.properties
  • com\jdojo\encapsulated\encapsulated.properties
  • com\jdojo\encapsulated\EncapsulatedTest.class
  • com\jdojo\exported\AppResource.class
  • com\jdojo\exported\exported.properties
  • com\jdojo\opened\opened.properties
  • com\jdojo\opened\OpenedTest.class

有四个类文件。 在这个例子中,只有module-info.class文件很重要。 其他类文件定义一个没有任何细节的同名的类。 具有.properties扩展名的所有文件都是资源文件,其内容在此示例中不重要。 源代码包含Java9Revealed\com.jdojo.resource目录中这些文件的内容。

Tips
源代码在com.jdojo.resource

unnamed.properties文件在未命名的包中,因此可以通过任何其他模块中的代码来定位。 invalid_pkg.properties文件位于META-INF目录中,它不是有效的Java包名称,因此该文件也可以通过任何其他模块中的代码来定位。 com.jdojo.encapsulated包没有打开,所以encapsulated.properties文件不能通过其他模块中的代码来找到。 com.jdojo.exported包未打开,所以export.properties文件不能通过其他模块中的代码来找到。 com.jdojo.opened包是打开的,所以opened.properties文件可以通过其他模块中的代码来定位。该模块中的所有类文件可以通过其他模块中的代码来定位。

下面清单包含com.jdojo.resource.test模块的模块声明。本模块中的代码将尝试访问com.jdojo.resource模块中的资源以及本模块中的资源。你需要将com.jdojo.resource模块添加到此模块路径以进行编译。 在 NetBean IDE中com.jdojo.resource.test项目的属性对话框如下图所示。它将com.jdojo.resource模块添加到其模块路径。

Java 9 揭秘(8. JDK 9重大改变下)_第2张图片
Adding module to the module path
// module-info.java
module com.jdojo.resource.test {
    requires com.jdojo.resource;
    exports com.jdojo.resource.test;
}

com.jdojo.resource.test模块中的文件按如下方式排列:

  • module-info.class
  • com\jdojo\resource\test\own.properties
  • com\jdojo\resource\test\ResourceTest.class

该模块包含名为own.properties的资源文件,该文件位于com.jdojo.resource.test包中。 own.properties文件为空。 下面包含ResourceTest类的代码。

// ResourceTest
package com.jdojo.resource.test;
import com.jdojo.exported.AppResource;
import java.io.IOException;
import java.io.InputStream;
public class ResourceTest {
    public static void main(String[] args) {
        // A list of resources
        String[] resources = {
            "java/lang/Object.class",
            "com/jdojo/resource/test/own.properties",
            "com/jdojo/resource/test/ResourceTest.class",
            "unnamed.properties",
            "META-INF/invalid_pkg.properties",
            "com/jdojo/opened/opened.properties",
            "com/jdojo/exported/AppResource.class",
            "com/jdojo/resource/exported.properties",
            "com/jdojo/encapsulated/EncapsulatedTest.class",
            "com/jdojo/encapsulated/encapsulated.properties"
        };
        System.out.println("Using a Module:");
        Module otherModule = AppResource.class.getModule();
        for (String resource : resources) {
            lookupResource(otherModule, resource);
        }
        System.out.println("\nUsing a Class:");
        Class cls = ResourceTest.class;
        for (String resource : resources) {
            // Prepend a / to all resource names to make them absolute names
            lookupResource(cls, "/" + resource);
        }
        System.out.println("\nUsing the System ClassLoader:");
        ClassLoader clSystem = ClassLoader.getSystemClassLoader();
        for (String resource : resources) {
            lookupResource(clSystem, resource);
        }
        System.out.println("\nUsing the Platform ClassLoader:");
        ClassLoader clPlatform = ClassLoader.getPlatformClassLoader();
        for (String resource : resources) {
            lookupResource(clPlatform, resource);
        }
    }
    public static void lookupResource(Module m, String resource) {
        try {
            InputStream in = m.getResourceAsStream(resource);
            print(resource, in);
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
    public static void lookupResource(Class cls, String resource) {
        InputStream in = cls.getResourceAsStream(resource);
        print(resource, in);
    }
    public static void lookupResource(ClassLoader cl, String resource) {
        InputStream in = cl.getResourceAsStream(resource);
        print(resource, in);
    }
    private static void print(String resource, InputStream in) {
        if (in != null) {
            System.out.println("Found: " + resource);
        } else {
            System.out.println("Not Found: " + resource);
        }
    }
}

下面是具体的输出:

Using a Module:
Not Found: java/lang/Object.class
Not Found: com/jdojo/resource/test/own.properties
Not Found: com/jdojo/resource/test/ResourceTest.class
Found: unnamed.properties
Found: META-INF/invalid_pkg.properties
Found: com/jdojo/opened/opened.properties
Found: com/jdojo/exported/AppResource.class
Not Found: com/jdojo/resource/exported.properties
Found: com/jdojo/encapsulated/EncapsulatedTest.class
Not Found: com/jdojo/encapsulated/encapsulated.properties
Using a Class:
Not Found: /java/lang/Object.class
Found: /com/jdojo/resource/test/own.properties
Found: /com/jdojo/resource/test/ResourceTest.class
Not Found: /unnamed.properties
Not Found: /META-INF/invalid_pkg.properties
Not Found: /com/jdojo/opened/opened.properties
Not Found: /com/jdojo/exported/AppResource.class
Not Found: /com/jdojo/resource/exported.properties
Not Found: /com/jdojo/encapsulated/EncapsulatedTest.class
Not Found: /com/jdojo/encapsulated/encapsulated.properties
Using the System ClassLoader:
Found: java/lang/Object.class
Found: com/jdojo/resource/test/own.properties
Found: com/jdojo/resource/test/ResourceTest.class
Found: unnamed.properties
Found: META-INF/invalid_pkg.properties
Found: com/jdojo/opened/opened.properties
Found: com/jdojo/exported/AppResource.class
Not Found: com/jdojo/resource/exported.properties
Found: com/jdojo/encapsulated/EncapsulatedTest.class
Not Found: com/jdojo/encapsulated/encapsulated.properties
Using the Platform ClassLoader:
Found: java/lang/Object.class
Not Found: com/jdojo/resource/test/own.properties
Not Found: com/jdojo/resource/test/ResourceTest.class
Not Found: unnamed.properties
Not Found: META-INF/invalid_pkg.properties
Not Found: com/jdojo/opened/opened.properties
Not Found: com/jdojo/exported/AppResource.class
Not Found: com/jdojo/resource/exported.properties
Not Found: com/jdojo/encapsulated/EncapsulatedTest.class
Not Found: com/jdojo/encapsulated/encapsulated.properties

lookupResource()方法重载。 它们使用三个类来定位资源:ModuleClassClassLoader。 这些方法将资源名称和资源引用传递给print()方法来打印消息。

main()方法准备了一个资源列表,用来使用不同的资源查找方法查找。 它保存了一个String数组列表:

// A list of resources
String[] resources = {/* List of resources */};

main()方法尝试使用com.jdojo.resource模块的引用查找所有资源。 请注意,AppResource类在com.jdojo.resource模块中,因此AppResource.class.getModule()方法返回com.jdojo.resource模块的引用。

System.out.println("Using a Module:");
Module otherModule = AppResource.class.getModule();
for (String resource : resources) {
    lookupResource(otherModule, resource);
}

该代码找到com.jdojo.resource模块中未命名、无效和打开的包中的所有类文件和资源。 请注意,没有找到java/lang/Object.class,因为它在java.base模块中,而不在com.jdojo.resource模块中。 同样的原因找不到com.jdojo.resource.test模块中的资源。

现在,main()方法使用Resource Test类的Class对象来找到相同的资源,它在com.jojo.resource.test模块中。

Class cls = ResourceTest.class;
for (String resource : resources) {
    // Prepend a / to all resource names to make them absolute names
    lookupResource(cls, "/" + resource);
}

Class对象将仅在com.jdojo.resource.test模块中定位资源,这在输出中是显而易见的。 在代码中,使用斜线预先填写资源名称,因为Class类中的资源查找方法会把资源当作不以斜线开头的相对资源名称来对待,并将该类的包名称添加到该资源名称。

最后,main()方法使用应用程序和平台类加载器来定位同一组资源:

ClassLoader clSystem = ClassLoader.getSystemClassLoader();
for (String resource : resources) {
    lookupResource(clSystem, resource);
}
ClassLoader clPlatform = ClassLoader.getPlatformClassLoader();
for (String resource : resources) {
    lookupResource(clPlatform, resource);
}

类加载器将在类加载器本身或其祖先类加载器已知的所有模块中定位资源。 系统类加载器加载com.jdojo.resource和com.jdojo.resource.test模块,因此它可以根据资源查找规则强制的限制来查找这些模块中的资源。 即引导类加载器从java.base模块加载Object类,因此系统类加载器可以找到java/lang/Object.class文件。

平台类加载器不加载com.jdojo.resource和com.jdojo.resource.test应用程序模块。 在输出中很明显.平台类加载器只发现一个资源,java/lang/Object.class,由父类引导类加载器进行加载。

九. 访问运行时映像中的资源

我们来看几个在运行时映像中访问资源的例子。 在JDK 9之前,可以使用ClassLoader类的getSystemResource()静态方法。 以下是在JDK 8中查找Object.class文件的代码:

import java.net.URL;
...
String resource = "java/lang/Object.class";
URL url = ClassLoader.getSystemResource(resource);
System.out.println(url);
// jar:file:/C:/java8/jre/lib/rt.jar!/java/lang/Object.class

输出显示使用jar方案返回的URL指向rt.jar文件。

JDK 9不再在JAR中存储运行时映像。 它可能在将来更改成内部格式存储。 JDK提供了一种使用jrt方案以与格式和位置无关的方式访问运行时资源的方法。 上面代码在JDK 9中通过使用jrt方案返回一个URL,而不是jar方案:

jrt:/java.base/java/lang/Object.class

Tips
如果你的代码从运行时映像访问资源,并期望使用jar方案的URL,则需要在JDK 9中进行更改,因为在JDK 9中将使用jrt格式获取URL。

使用jrt方案的语法如下:

jrt://

是模块的名称,是模块中特定类或资源文件的路径。 都是可选的。 jrt:/,指的是保存在当前运行时映像中的所有类和资源文件。 jrt:/是指保存在模块中的所有类和资源文件。 jrt://指的是模块中名为的特定类或资源文件。 以下是使用jrt方案引用类文件和资源文件的两个URL的示例:

jrt:/java.sql/java/sql/Driver.class
jrt:/java.desktop/sun/print/resources/duplex.png

第一个URL为java.sql模块中java.sql.Driver类的类文件命名。 第二个URL是java.desktop模块中的映像文件sun/print/resources/duplex.png命名。

Tips
可以使用jrt方案访问运行时映像中的资源,但是在使用ModuleClassClassLoader类中的资源查找方式是不可访问的。

可以使用jrt方案创建一个URL。 以下代码片段显示了如何吧一个图片文件读入到Image对象中,以及在运行时映像中把一个类文件读入到字节数组。

// Load the duplex.png into an Image object
URL imageUrl = new URL("jrt:/java.desktop/sun/print/resources/duplex.png");
Image image = ImageIO.read(imageUrl);
// Use the image object here
System.out.println(image);
// Load the contents of the Object.class file
URL classUrl = new URL("jrt:/java.base/java/lang/Object.class");
InputStream input = classUrl.openStream();
byte[] bytes = input.readAllBytes();
System.out.println("Object.class file size: " + bytes.length);

输出结果为:

BufferedImage@3e57cd70: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@67b467e9 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 41 height = 24 #numDataElements 4 dataOff[0] = 3
Object.class file size: 1859

什么时候可以使用其他形式的jrt方案,以便表示运行时映像中的所有文件和模块中的所有文件? 可以使用jrt方案来引用一个模块来授予Java策略文件的权限。 Java策略文件中的以下条目将为java.activation模块中的代码授予所有权限:

grant codeBase "jrt:/java.activation" {
    permission java.security.AllPermission;
}

许多工具和IDE需要枚举运行时映像中的所有模块,软件包和文件。 JDK 9为了jrt URL方案,附带一个只读NIO FileSystem提供者。 可以使用此提供者列出运行时映像中的所有类和资源文件。 有一些工具和IDE将在JDK 8上运行,但将支持JDK 9的代码开发。这些工具还需要获取JDK 9运行时映像中的类和资源文件列表。 当你安装JDK 9时,它在lib目录中包含一个jrt-fs.jar文件。 可以将此JAR文件添加到在JDK 8上运行的工具的类路径,并使用jrt FileSystem,如下所示。

jrt文件系统包含由斜线(/)表示的根目录,其中包含两个名为包和模块的子目录:

/
/packages
/modules

以下代码片段为jrt URL方案创建了一个NIO FileSystem

// Create a jrt FileSystem
FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
The following snippet of code reads an image file and the contents of the Object.class file:
// Load an image from a module
Path imagePath = fs.getPath("modules/java.desktop", "sun/print/resources/duplex.png");
Image image = ImageIO.read(Files.newInputStream(imagePath));
// Use the image object here
System.out.println(image);
// Read the Object.class file contents
Path objectClassPath = fs.getPath("modules/java.base", "java/lang/Object.class");
byte[] bytes = Files.readAllBytes(objectClassPath);
System.out.println("Object.class file size: " + bytes.length);

输出结果为:

BufferedImage@5f3a4b84: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@5204062d transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 41 height = 24 #numDataElements 4 dataOff[0] = 3
Object.class file size: 1859

以下代码片段将打印运行时映像中所有模块中的所有类和资源文件。 类似地,可以为包创建·Path`类列举运行时映像中的所有包。

// List all modules in the runtime image
Path modules = fs.getPath("modules");
Files.walk(modules)
     .forEach(System.out::println);

输出结果为:

/modules
/modules/java.base
/modules/java.base/java
/modules/java.base/java/lang
/modules/java.base/java/lang/Object.class
/modules/java.base/java/lang/AbstractMethodError.class
...

我们来看一个从运行时映像访问资源的完整程序。 下面包含名为com.jdojo.resource.jrt的模块的模块声明。

// module-info.java
module com.jdojo.resource.jrt {
    requires java.desktop;
}

接下来是JrtFileSystem类的源代码,它位于com.jdojo.resource.jrt模块中。

// JrtFileSystem.java
package com.jdojo.resource.jrt;
import java.awt.Image;
import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.imageio.ImageIO;
public class JrtFileSystem {
    public static void main(String[] args) throws IOException {
        // Create a jrt FileSystem
        FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
        // Load an image from a module
        Path imagePath = fs.getPath("modules/java.desktop", "sun/print/resources/duplex.png");
        Image image = ImageIO.read(Files.newInputStream(imagePath));
        // Use the image object here
        System.out.println(image);
        // Read the Object.class file contents
        Path objectClassPath = fs.getPath("modules/java.base", "java/lang/Object.class");
        byte[] bytes = Files.readAllBytes(objectClassPath);
        System.out.println("Object.class file size: " + bytes.length);
        // List 5 packages in the runtime image
        Path packages = fs.getPath("packages");
        Files.walk(packages)
             .limit(5)
             .forEach(System.out::println);
        // List 5 modules’ entries in the runtime image
        Path modules = fs.getPath("modules");
        Files.walk(modules)
             .limit(5)
             .forEach(System.out::println);
    }
}

输出结果为:

BufferedImage@5bfbf16f: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@27d415d9 transparency = 3 has alpha = true isAlphaPre = false ByteInterleavedRaster: width = 41 height = 24 #numDataElements 4 dataOff[0] = 3
Object.class file size: 1859
packages
packages/com
packages/com/java.activation
packages/com/java.base
packages/com/java.corba
modules
modules/java.desktop
modules/java.desktop/sun
modules/java.desktop/sun/print
modules/java.desktop/sun/print/resources

JrtFileSystem类,演示使用jrt URL方案从运行时映像访问资源。
注意,程序仅打包和模块目录中的五个条目。 可以访问java.desktop模块中的sun/print/resources/duplex.png。 java.desktop模块不打开sun.print.resources包。 使用ModuleClassClassLoader类中的任何资源查找方法来定位 sun/print/resources/duplex.png将失败。

十. 使用JDK内部API

JDK由公共API和内部API组成。 公共API旨在用于开发可移植Java应用程序。 JDK中的java.*javax.*org.*包包含在公共API。 如果应用程序仅使用公共API,则可以在支持Java平台的所有操作系统上运行。 这种应用提供的另一个保证是,如果它在JDK版本N中工作,它将继续在JDK版本N + 1中工作。

com.sun.*sun.*jdk.*包用于实现JDK本身,它们组成内部API,这不意味着由开发人员使用。 内部API不能保证在所有操作系统上运行。 com.sun.*sun.*等软件包是Oracle JDK的一部分。 如果使用其他供应商的JDK,这些软件包将不可用。 非Oracle JDK(如IBM的JDK)将使用其他软件包名称来实现其内部API。 下图显示了不同类别的JDK API。

Java 9 揭秘(8. JDK 9重大改变下)_第3张图片
基于其预期用途的JDK API类别

在JDK 9模块化之前,可以使用任何JAR的公共类,即使这些类是JDK内部API。 开发人员和一些广泛使用的库已经使用JDK内部API来方便,或者由于这些API提供的功能难以在JDK之外实现。 这些类的示例是BASE64EncoderBASE64Decoder。 开发人员为了方便使用它们,它们可以作为sun.misc包中的JDK内部API使用,即使它们不难开发。 另一个广泛使用的类是sun.misc包中的Unsafe类。 在JDK之外开发一个类来替代Unsafe类,因为它访问了JDK内部是很困难的。

仅用于方便使用的内部API在JDK之外不被使用,或者它们所存在的支持的替换已经被分类为非关键内部API,并且已经封装在JDK 9中。示例是Sun.misc包中的BASE64EncoderBASE64Decoder类,JDK 8里,Base64.EncoderBase64.Decoder`类作为公共API的一部分添加到java.util包中。

在JDK之外广泛使用但难以开发的内部API被归类为关键的内部API。 如果存在替换,它们被封装在JDK 9中。 封装在JDK 9中但可以使用命令行选项的关键内部API已使用@jdk.Exported注解。 JDK 9不提供以下类的替代,这些类被认为是关键的内部API。 它们可以通过jdk.unsupported模块访问。

com.sun.nio.file.ExtendedCopyOption
com.sun.nio.file.ExtendedOpenOption
com.sun.nio.file.ExtendedWatchEventModifier
com.sun.nio.file.SensitivityWatchEventModifier
sun.misc.Signal
sun.misc.SignalHandler
sun.misc.Unsafe
sun.reflect.Reflection
sun.reflect.ReflectionFactory

Tips
在JDK 9中,大多数JDK内部API已封装在模块中,默认情况下不可访问。但仍然可以使用--add-read非标准命令行选项访问它们。

以下类中的addPropertyChangeListener()removePropertyChangeListener()方法已在JDK 8中弃用,并已从JDK 9中删除:

java.util.logging.LogManager
java.util.jar.Pack200.Packer
java.util.jar.Pack200.Unpacker

可以使用位于JAVA_HOME\bin目录中的jdeps工具来查找代码在JDK内部API上的类级依赖关系。 还需要使用--jdk-internals选项,如下所示:

jdeps --jdk-internals --class-path  

这里,可以是类文件,目录或JAR文件的路径。 该命令分析上的所有类。 以下命令打印jersey-common.jar文件中JDK内部API的用法,假设JAR位于C:\Java9Revealed\extlib目录中。

C:\Java9Revealed>jdeps --jdk-internals extlib\jersey-common.jar

下面是部分输出:

jersey-common.jar -> jdk.unsupported
   org.glassfish.jersey.internal.util.collection.ConcurrentHashMapV8 -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
org.glassfish.jersey.internal.util.collection.ConcurrentHashMapV8$TreeBin -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
...

十一. 修补模块内容

有时候,可能需要用另一个版本替换特定模块的类文件和资源进行测试和调试。 在JDK 9之前,可以使用-Xbootclasspath/p选项来实现此目的。 此选项已在JDK 9中删除。在JDK 9中,需要使用--patch-module非标准命令行选项。 此选项可用于javac和java命令。 其语法如下:

--patch-module =

这里,是正在替换其内容的模块的名称。 是包含新模块内容的JAR或目录列表; 列表中的每个元素都由特定于主机的路径分隔符分隔,该字符是Windows上的分号和类UNIX平台上的冒号。

可以对同一命令多次使用--patch-module选项,因此可以修补多个模块的内容。 可以修补应用程序模块,库模块和平台模块。

Tips
当使用--patch-module选项时,无法替换module-info.class文件。 试图这样做是默认无视的。

现在,我们将运行一个修补com.jdojo.intro模块的例子。 使用新的Welcome.class文件替换此模块中的Welcome.class文件。 回想一下,我们在第3章中创建了Welcome类。新类将打印一个不同的消息。 新的类声明如下所示。 在源代码中,此类位于com.jdojo.intro.patch 的NetBeans项目中。

// Welcome.java
package com.jdojo.intro;
public class Welcome {
    public static void main(String[] args) {
        System.out.println("Hello Module System.");
        // Print the module name of the Welcome class
        Class cls = Welcome.class;
        Module mod = cls.getModule();
        String moduleName = mod.getName();
        System.out.format("Module Name: %s%n", moduleName);
    }
}

现在,需要使用以下命令为上面新的Welcome类编译源代码:

C:\Java9Revealed>javac -Xmodule:com.jdojo.intro
  --module-path com.jdojo.intro\dist
  -d patches\com.jdojo.intro.patch com.jdojo.intro.patch\src\com\jdojo\intro\Welcome.java

即使删除前两个选项:-Xmodule-module-path,此命令也将成功。 但是,当编译平台类(如java.util.Arrays)时,将需要这些选项。 否则,将收到错误。-Xmodule选项指定要编译的源代码所属的模块名称。 --module-path选项指定在哪里查找-Xmodule选项中指定的模块。 这些选项用于定位编译新类所需的其他类。 在这种情况下,Welcome类不依赖于com.jdojo.intro模块中的任何其他类。 这就是为什么在这种情况下删除这些选项不会影响结果。-d选项指定编译的Welcome.class文件的保存位置。

以下是从com.jdojo.intro模块运行原始Welcome类的命令:

C:\Java9Revealed>java --module-path com.jdojo.intro\dist
--module com.jdojo.intro/com.jdojo.intro.Welcome

输出结果为:

Welcome to the Module System.
Module Name: com.jdojo.intro

现在是使用修补版本运行Welcome类的时候了。 这是执行此操作的命令:

C:\Java9Revealed>java --module-path com.jdojo.intro\dist
  --patch-module com.jdojo.intro=patches\com.jdojo.intro.patch
  --module com.jdojo.intro/com.jdojo.intro.Welcome

输出结果为:

Hello Module System.
Module Name: com.jdojo.intro

当使用--patch-module选项时,在搜索模块路径之前,模块系统会搜索此选项中指定的路径。 请注意,此选项中指定的路径包含模块的内容,但这些路径不是模块路径。

十二. 总结

如果将旧版应用程序迁移到JDK 9,JDK 9进行了一些突破性的更改,这点必须注意。

JDK 9中对JDK的非直观版本控制方案已经进行了改进。JDK版本字符串由以下四个元素组成:版本号,预发布信息,构建信息和附加信息。 只有第一个是强制性的。 正则表达式$vnum(-$pre)?(\+($build)?(-$opt)?)?定义了版本字符串的格式。 一个简短版本的字符串只包含前两个元素:一个版本号,可选的是预发布信息。 可以有一个简短到“9”的版本字符串,其中只包含主版本号。“99.0.1-ea+154-20170130.07.36am”,这个版本字符串包含了所有元素。

JDK 9添加了一个名为Runtime.Version的静态嵌套类,其实例表示JDK版本字符串。 该类没有公共构造函数。 获取其实例的唯一方法是调用其静态方法名parse(String vstr)。 如果版本字符串为空或无效,该方法可能会抛出运行时异常。 该类包含几个方法来获取版本的不同部分。

JDK 9更改了JDK和JRE安装的目录布局。 现在,除了JDK安装包含开发工具和JRE不包含的JMOD格式的平台模块的拷贝之外,JDK和JRE安装之间没有区别。 可以构建自己的JRE(使用jlink工具),它可以包含JRE中需要的JDK的任何部分。

在Java SE 9之前,可以使用“支持的标准覆盖机制”来使用实现“承认标准”或“独立API”的较新版本的类和接口。 这些包括在Java Community Process之外创建的javax.rmi.CORBA包和Java API for XML Processing(JAXP)。 Java SE 9仍然支持这种机制。 在Java SE 9中,需要使用--upgrade-module-path命令行选项。 此选项的值是包含标准标准和独立API的模块的目录列表。

在版本9之前的Java SE允许一个扩展机制,可以通过将JAR放在系统属性java.ext.dirs指定的目录中来扩展运行时映像。 如果未设置此系统属性,则使用jre\lib\ext目录作为其默认值。 Java SE 9不支持扩展机制。 如果需要类似的功能,可以将这些JAR放在类路径的前面。

在版本9之前,JDK使用三个类加载器来加载类。 他们是引导类加载器,扩展类加载器和系统(应用程序)类加载器。 它们分层排列 —— 没有父类的引导类加载器,引导类加载器作为扩展类加载器的父类,并扩展类加载器作为系统类加载器作为的父级。 在尝试加载类型本身之前,类加载器将类型加载要求委托给其父类(如果有)。 JDK 9保持了三类装载机的向后兼容性。 JDK 9不支持扩展机制,所以扩展类加载器没有意义。 JDK 9已经将扩展类加载器重命名为平台类加载器,该引用可以使用ClassLoader类的静态方法getPlatformClassLoader()获取。 在JDK 9中,每个类加载器加载不同类型的模块。

在JDK 9中,默认情况下封装命名模块中的资源。只有当资源处于未命名,无效或打开的包中时,命名模块中的资源才能被另一个模块中的代码访问。名称以.class(所有类文件)结尾的命名模块中的所有资源都可以通过其他模块中的代码访问。可以使用jrt方案的URL来访问运行时映像中的任何资源。

在JDK 9之前,可以使用JDK内部API。 JDK 9中的大多数JDK内部API已被封装。有些通过jdk.unsupported模块来提供。可以使用jdeps工具和--jdk-internals选项来查找代码对JDK内部API的类级依赖性。

有时候,可能需要用另一个版本替换特定模块的类文件和资源进行测试和调试。在JDK 9之前,可以使用已在JDK 9中删除的-Xbootclasspath/p选项来实现。在JDK 9中,需要使用--patch-module非标准命令行选项。 javac和java命令可以使用此选项。

你可能感兴趣的:(Java 9 揭秘(8. JDK 9重大改变下))