(转) Java 9学习笔记

Posted 每天更强一点...

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(转) Java 9学习笔记 相关的知识,希望对你有一定的参考价值。

reference:

[1] http://www.cnblogs.com/IcanFixIt/p/7278696.html

 

一. JDK and JRE

在 Java SE 9之前,JDK中:

  • bin目录用于包含命令行开发和调试工具,如javac,jar和javadoc。 它还用于包含Java命令来启动Java应用程序。
  • include目录包含在编译本地代码时使用的C/C++头文件。
  • lib目录包含JDK工具的几个JAR和其他类型的文件。 它有一个tools.jar文件,其中包含javac编译器的Java类。
  • jre\\bin目录包含基本命令,如java命令。 在Windows平台上,它包含系统的运行时动态链接库(DLL)。
  • jre\\lib目录包含用户可编辑的配置文件,如.properties和.policy文件。
  • jre\\lib\\approved目录包含允许使用标准覆盖机制的JAR。 这允许在Java社区进程之外创建的实施标准或独立技术的类和接口的更高版本被并入Java平台。 这些JAR被添加到JVM的引导类路径中,从而覆盖了Java运行时中存在的这些类和接口的任何定义。
  • jre\\lib\\ext目录包含允许扩展机制的JAR。 该机制通过扩展类加载器(该类加载器)加载了该目录中的所有JAR,该引导类加载器是系统类加载器的子进程,它加载所有应用程序类。 通过将JAR放在此目录中,可以扩展Java SE平台。 这些JAR的内容对于在此运行时映像上编译或运行的所有应用程序都可见。
  • jre\\lib目录包含几个JAR。 rt.jar文件包含运行时的Java类和资源文件。 许多工具依赖于rt.jar文件的位置。
  • jre\\lib目录包含用于非Windows平台的动态链接本地库。
  • jre\\lib目录包含几个其他子目录,其中包含运行时文件,如字体和图像。

JDK和JRE的根目录包含多个文件,如COPYRIGHT,LICENSE和README.html。 根目录中的发行文件包含一个描述运行时映像(如Java版本,操作系统版本和体系结构)的键值对。 以下代码显示了JDK 8中的示例版本文件的部分内容:

JAVA_VERSION="1.8.0_66"
OS_NAME="Windows"
OS_VERSION="5.2"
OS_ARCH="amd64"
BUILD_TYPE="commercial"

Java SE 9之前的JDK和JRE目录布局

 

Java SE 9调整了JDK的目录层次结构,并删除了JDK和JRE之间的区别。JDK 9中的JRE安装不包含include和jmods目录。

在Java SE 9 的JDK中:

  • 没有名为jre的子目录。
  • bin目录包含所有命令。 在Windows平台上,它继续包含系统的运行时动态链接库。
  • conf目录包含用户可编辑的配置文件,例如以前位于jre\\lib目录中的.properties和.policy文件。
  • include目录包含要在以前编译本地代码时使用的C/C++头文件。 它只存在于JDK中。
  • jmods目录包含JMOD格式的平台模块。 创建自定义运行时映像时需要它。 它只存在于JDK中。
  • legal 目录包含法律声明。
  • lib目录包含非Windows平台上的动态链接本地库。 其子目录和文件不应由开发人员直接编辑或使用。

JDK 9的根目录有如COPYRIGHT和README等文件。 JDK 9中的发行文件包含一个带有MODULES键的新条目,其值为映像中包含的模块列表。 JDK 9映像中的发行文件的部分内容如下所示:

MODULES=java.rmi,jdk.jdi,jdk.policytool
OS_VERSION="5.2"
OS_ARCH="amd64"
OS_NAME="Windows"
JAVA_VERSION="9"
JAVA_FULL_VERSION="9-ea+133"


Java SE 9中的JDK目录布局

JDK中的lib\\tools.jar和JRE中的lib\\rt.jar已从Java SE 9中删除。这些JAR中可用的类和资源现在以文件中的内部格式存储在lib目录的命名模块中。 可以使用称为jrt的新方案来从运行时映像检索这些类和资源。 依靠这些JAR位置的应用程序将不再工作。

二. 扩展机制

版本9之前的Java SE允许扩展机制,可以通过将JAR放置在系统属性java.ext.dirs指定的目录中来扩展运行时映像。 如果未设置此系统属性,则使用jre\\lib\\ext目录作为其默认值。 该机制通过扩展类加载器(这是引导类加载器的子类)和系统类加载器的父级加载了该目录中的所有JAR。 它加载所有应用程序类。 这些JAR的内容对于在此运行时映像上编译或运行的所有应用程序都可见。

Java SE 9不支持扩展机制。 如果需要类似的功能,可以将这些JAR放在类路径的前面。 使用名为JAVA_HOME\\lib\\ext的目录或设置名为java.ext.dirs的系统属性会导致JDK 9中的错误。

类加载器的改变

在程序运行时,每个类型都由类加载器加载,该类由java.lang.ClassLoader类的一个实例表示。 如果你有一个对象引用obj,你可以通过调用obj.getClass().getClassLoader()方法获得它的类加载器引用。 可以使用其getParent()方法获取类加载器的父类。

在版本9之前,JDK使用三个类加载器来加载类,如下图所示。 图中箭头方向表示委托方向。 可以添加更多的类加载器,这是ClassLoader类的子类。 来自不同位置和类型的JDK加载类中的三个类加载器。

版本9之前的JDK中的类加载器层次结构

JDK类加载器以分层方式工作 —— 引导类加载器位于层次结构的顶部。 类加载器将类加载请求委托给上层类加载器。 例如,如果应用程序类加载器需要加载一个类,它将请求委托给扩展类加载器,扩展类加载器又将请求委托给引导类加载器。 如果引导类加载器无法加载类,扩展类加载器将尝试加载它。 如果扩展类加载器无法加载类,则应用程序类加载器尝试加载它。 如果应用程序类加载器无法加载它,则抛出ClassNotFoundException异常。

引导类加载器是扩展类加载器的父类。 扩展类加载器是应用程序类加载器的父类。 引导类加载器没有父类。 默认情况下,应用程序类加载器将是你创建的其他类加载器的父类。

引导类加载器加载由Java平台组成的引导类,包括JAVA_HOME\\lib\\rt.jar中的类和其他几个运行时JAR。 它完全在虚拟机中实现。 可以使用-Xbootclasspath/p-Xbootclasspath/a命令行选项来附加引导目录。 可以使用-Xbootclasspath选项指定引导类路径,该选项将替换默认的引导类路径。 在运行时,sun.boot.class.path系统属性包含引导类路径的只读值。 JDK通过null表示这个类加载器。 也就是说,你不能得到它的引用。 例如,Object类由引导类加载器加载,并且Object.class.getClassLoade()表达式将返回null。

扩展类加载器用于通过java.ext.dirs系统属性指定的目录中的位于JAR中的扩展机制加载可用的类。要获得扩展类加载器的引用,需要获取应用程序类加载器的引用,并在该引用上使用getParent()方法。

应用程序类加载器从由CLASSPATH环境变量指定的应用程序类路径或命令行选项-cp-classpath加载类。应用程序类加载器也称为系统类加载器,这是一种误称,它暗示它加载系统类。可以使用ClassLoader类的静态方法getSystemClassLoader()获取对应用程序类加载器的引用。

JDK 9保持三级分层类加载器架构以实现向后兼容。但是,从模块系统加载类的方式有一些变化。 JDK 9类加载器层次结构如下图所示。

JDK 9中的加载器层次结构

 

请注意,在JDK 9中,应用程序类加载器可以委托给平台类加载器以及引导类加载器;平台类加载器可以委托给引导类加载器和应用程序类加载器。 以下详细介绍JDK 9类加载器的工作原理。

在JDK 9中,引导类加载器是由类库和代码在虚拟机中实现的。 为了向后兼容,它在程序中仍然由null表示。 例如,Object.class.getClassLoader()仍然返回null。 但是,并不是所有的Java SE平台和JDK模块都由引导类加载器加载。 举几个例子,引导类加载器加载的模块是java.basejava.loggingjava.prefsjava.desktop。 其他Java SE平台和JDK模块由平台类加载器和应用程序类加载器加载,这在下面介绍。 JDK 9中不再支持用于指定引导类路径,-Xbootclasspath-Xbootclasspath/p选项以及系统属性sun.boot.class.path-Xbootclasspath/a选项仍然受支持,其值存储在jdk.boot.class.path.append的系统属性中。

JDK 9不再支持扩展机制。 但是,它将扩展类加载器保留在名为平台类加载器的新名称下。 ClassLoader类包含一个名为getPlatformClassLoader()的静态方法,该方法返回对平台类加载器的引用。 下表包含平台类加载器加载的模块列表。 平台类加载器用于另一目的。 默认情况下,由引导类加载器加载的类将被授予所有权限。 但是,几个类不需要所有权限。 这些类在JDK 9中已经被取消了特权,并且它们被平台类加载器加载以提高安全性。

下面是JDK 9中由平台加载器加载的模块列表。

java.activation
java.xml.ws.annotation
jdk.desktop
java.compiler
javafx.base
jdk.dynalink
java.corba
javafx.controls
jdk.javaws
java.jnlp
javafx.deploy
jdk.jsobject
java.scripting
javafx.fxml
jdk.localedata
java.se
javafx.graphics
jdk.naming.dns
java.se.ee
javafx.media
jdk.plugin
java.security.jgss
javafx.swing
jdk.plugin.dom
java.smartcardio
javafx.web
jdk.plugin.server
java.sql
jdk.accessibility
jdk.scripting.nashorn
java.sql.rowset
jdk.charsets
jdk.security.auth
java.transaction
jdk.crypto.cryptoki
jdk.security.jgss
java.xml.bind
jdk.crypto.ec
jdk.xml.dom
java.xml.crypto
jdk.crypto.mscapi
jdk.zipfs
java.xml.ws
jdk.deploy

应用程序类加载器加载在模块路径上找到的应用程序模块和一些提供工具或导出工具API的JDK模块,如下表所示。 仍然可以使用ClassLoader类的getSystemClassLoader()的静态方法来获取应用程序类加载器的引用。

jdk.attach
jdk.jartool
jdk.jstatd
jdk.compiler
jdk.javadoc
jdk.pack
jdk.deploy.controlpanel
jdk.jcmd
jdk.packager
jdk.editpad
jdk.jconsole
jdk.packager.services
jdk.hotspot.agent
jdk.jdeps
jdk.policytool
jdk.internal.ed
jdk.jdi
jdk.rmic
jdk.internal.jvmstat
jdk.jdwp.agent
jdk.scripting.nashorn.shell
jdk.internal.le
jdk.jlink
jdk.xml.bind
jdk.internal.opt
jdk.jshell
jdk.xml.ws

Tips
在JDK 9之前,扩展类加载器和应用程序类加载器是java.net.URLClassLoader类的一个实例。 在JDK 9中,平台类加载器(以前的扩展类加载器)和应用程序类加载器是内部JDK类的实例。 如果你的代码依赖于·URLClassLoader·类的特定方法,代码可能会在JDK 9中崩溃。

JDK 9中的类加载机制有所改变。 三个内置的类加载器一起协作来加载类。 当应用程序类加载器需要加载类时,它将搜索定义到所有类加载器的模块。 如果有合适的模块定义在这些类加载器中,则该类加载器将加载类,这意味着应用程序类加载器现在可以委托给引导类加载器和平台类加载器。 如果在为这些类加载器定义的命名模块中找不到类,则应用程序类加载器将委托给其父类,即平台类加载器。 如果类尚未加载,则应用程序类加载器将搜索类路径。 如果它在类路径中找到类,它将作为其未命名模块的成员加载该类。 如果在类路径中找不到类,则抛出ClassNotFoundException异常。

当平台类加载器需要加载类时,它将搜索定义到所有类加载器的模块。 如果一个合适的模块被定义为这些类加载器中,则该类加载器加载该类。 这意味着平台类加载器可以委托给引导类加载器以及应用程序类加载器。 如果在为这些类加载器定义的命名模块中找不到一个类,那么平台类加载器将委托给它的父类,即引导类加载器。

当引导类加载器需要加载一个类时,它会搜索自己的命名模块列表。 如果找不到类,它将通过命令行选项-Xbootclasspath/a指定的文件和目录列表进行搜索。 如果它在引导类路径上找到一个类,它将作为其未命名模块的成员加载该类。

三. 访问资源

资源是应用程序使用的数据,例如图像,音频,视频,文本文件等。Java提供了一种通过在类路径上定位资源来访问资源的位置无关的方式。 需要以与在JAR中打包类文件相同的方式打包资源,并将JAR添加到类路径。 通常,类文件和资源打包在同一个JAR中。 访问资源是每个Java开发人员执行的重要任务。 在接下来的章节中,将在版本9和JDK 9之前解释JDK中提供可用的API。

在Java代码中,资源由资源名称标识,资源名称是由斜线(/)分隔的一串字符串。 对于存储在JAR中的资源,资源名称仅仅是存储在JAR中的文件的路径。 例如,在JDK 9之前,存储在rt.jar中的java.lang包中的Object.class文件是一个资源,其资源名称是java/lang/Object.class。

在JDK 9之前,可以使用以下两个类中的方法来访问资源:

java.lang.Class
java.lang.ClassLoader

资源由ClassLoader定位。 一个Class代理中的资源寻找方法到它的ClassLoader。 因此,一旦了解ClassLoader使用的资源加载过程,将不会在使用Class类的方法时遇到问题。 在两个类中有两种不同的命名实例方法:

URL getResource(String name)
InputStream getResourceAsStream(String name)

两种方法都会以相同的方式找到资源。 它们的差异仅在于返回类型。 第一个方法返回一个URL,而第二个方法返回一个InputStream。 第二种方法相当于调用第一种方法,然后在返回的URL对象上调用openStream()

Tips
如果找不到指定的资源,所有资源查找方法都将返回null。

ClassLoader类包含三个额外的查找资源的静态方法:

static URL getSystemResource(String name)
static InputStream getSystemResourceAsStream(String name)
static Enumeration<URL> getSystemResources(String name)

这些方法使用系统类加载器(也称为应用程序类加载器)来查找资源。 第一种方法返回找到的第一个资源的URL。 第二种方法返回找到的第一个资源的InputStream。 第三种方法返回使用指定的资源名称找到的所有资源的URL枚举。

要找到资源,有两种类型的方法可以从——getSystemResource *getResource *中进行选择。 在讨论哪种方法是最好的之前,重要的是要了解有两种类型的资源:

  • 系统资源
  • 非系统资源

系统资源是在bootstrap类路径,扩展目录中的JAR和应用程序类路径中找到的资源。非系统资源可以存储在除路径之外的位置,例如在特定目录,网络上或数据库中。 getSystemResource()方法使用应用程序类加载程序找到一个资源,委托给它的父类,它是扩展类加载器,后者又委托给它的父类(引导类加载器)。如果你的应用程序是独立的应用程序,并且它只使用三个内置的JDK类加载器,那么你将很好的使用名为getSystemResource *的静态方法。它将在类路径中找到所有资源,包括运行时映像中的资源,如rt.jar文件。如果你的应用程序是在浏览器中运行的小程序,或在应用程序服务器和Web服务器中运行的企业应用程序,则应使用名为getResource*的实例方法,它可以使用特定的类加载器来查找资源。如果在Class对象上调用getResource*方法,则会使用当前类加载器(加载Class对象的类加载器)来查找资源。

传递给ClassLoader类中所有方法的资源名称都是绝对的,它们不以斜线(/)开头。 例如,当调用ClassLoadergetSystemResource()方法时,将使用java/lang/Object.class作为资源名称。

Class类中的资源查找方法可以指定绝对和相对资源名称。 绝对资源名称以斜线开头,而相对资源名称不用。 当使用绝对名称时,Class类中的方法会删除前导斜线并委派给加载Class对象的类加载器来查找资源。 以下调用

Test.class.getResource("/resources/test.config");
会被转换成
Test.class.getClassLoader().getResource("resources/test.config");

当使用相对名称时,Class类中的方法预先添加了包名称,在使用斜线后跟斜线替换包名中的点,然后再委托加载Class对象的类加载器来查找资源。 假设测试类在com.jdojo.test包中,以下调用:
Test.class.getResource("resources/test.config");
会被转换成
Test.class.getClassLoader() .getResource("com/jdojo/test/resources/test.config");

在JDK 9中包含三类资源查找方法:

java.lang.Class
java.lang.ClassLoader
java.lang.Module

ClassClassLoader类没新增任何新的方法。 Module类包含一个getResourceAsStream(String name)方法,如果找到该资源,返回一个InputStream;否则返回null。

四. 资源命名和查找资源

资源使用由斜线分隔的字符串序列命名,例如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<Module> 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");
}

五. 访问运行时映像

在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:/<module-name>/<path>

<module-name>是模块的名称,<path>是模块中特定类或资源文件的路径。 <module-name><path>都是可选的。 jrt:/,指的是保存在当前运行时映像中的所有类和资源文件。 jrt:/<module-name>是指保存在<module-name>模块中的所有类和资源文件。 jrt:/<module-name>/<path>指的是<module-name>模块中名为<path>的特定类或资源文件。 以下是使用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@bfbf16f: type = 6 ColorModel: #pixelBits = 32 numComponents = 4 color space = java.awt.color.ICC_ColorSpace@7d415d9 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。

基于其预期用途的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 <class-path> <input-path>

这里,<input-path>可以是类文件,目录或JAR文件的路径。 该命令分析<input-path><class-path>上的所有类。 以下命令打印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,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学习笔记 的主要内容,如果未能解决你的问题,请参考以下文章

Java 学习笔记

Java 学习笔记

[转]React官方学习笔记

学习笔记:python3,代码片段(2017)

JSP九大内置对象与Servlet学习笔记[转]

(转) Java中的负数及基本类型的转型详解