core java 9_Java 9 揭秘(2. 模块化系统)

文 by / 林本托

Tips

做一个终身学习的人。

9c7475ea446f

Java 9

在此章节中,主要介绍以下内容:

在JDK 9之前Java源代码用于编写,打包和部署的方式以及该方法的潜在问题

JDK 9中有哪些模块

如何声明模块及其依赖关系

如何封装模块

什么是模块路径

什么是可观察的模块

如何打印可观察模块的列表

如何打印模块的描述

本章旨在为你简要概述JDK 9中引入的模块系统。后续章节将详细介绍所有这些概念,并附有实例。 不要担心,如果你第一次不了解所有模块相关的概念。 一旦你获得开发模块代码的经验,你可以回来并重新阅读本章。

一. Java 9 之前的开发

在 JDK 9之前,开发一个 Java 应用程序通常包括以下步骤:

Java源代码以Java类型(如类,接口,枚举和注释)的形式编写。

不同的Java类型被安排在一个包(package)中,而且始终属于一个明确或默认的包。 一个包是一个逻辑的类型集合,本质上为它包含的类型提供一个命名空间。 即使声明为public,包可能包含公共类型,私有类型和一些内部实现类型。

编译的代码被打包成一个或多个JAR文件,也称为应用程序JAR,因为它们包含应用程序代码。 一个程序包中的代码可能会引用多个JAR。

应用程序可能使用类库。 类库作为一个或多个JAR文件提供给应用程序使用。

通过将所有JAR文件,应用程序JAR文件和JAR类库放在类路径上来部署应用程序。

下图显示了JAR文件中打包的代码的典型布局。 该图仅显示了包和Java 类型,不包括其他内容,如manifest.mf文件和资源文件。

9c7475ea446f

JAR 内部布局

20多年来,Java社区以这种编写,编译,打包和部署Java代码的方式开发。 但是,20年漫长的旅程并没有像你所希望的一样顺利! 这样安排和运行Java代码就存在固有的问题:

一个包只是一个类型的容器,而不强制执行任何可访问性边界。包中的公共类型可以在所有其他包中访问;没有办法阻止在一个包中公开类型的全局可见性。

除了以java和javax开头的包外,包应该是开放扩展的。如果你在具有包级别访问的JAR中进行了类型化,则可以在其他JAR中访问定义与你的名称相同的包中的类型。

Java运行时会看到从JAR列表加载的一组包。没有办法知道是否在不同的JAR中有多个相同类型的副本。Java运行时首先加载在类路径中遇到的JAR中找到的类型。

Java运行时可能会出现由于应用程序在类路径中需要的其中一个JAR引起的运行时缺少类型的情况。当代码尝试使用它们时,缺少的类型会引起运行时错误。

在启动时没有办法知道应用程序中使用的某些类型已经丢失。还可以包含错误的JAR文件版本,并在运行时产生错误。

这些问题在Java社区中非常频繁和臭名昭着,他们得到了一个名字 ——JAR-hell。

包装JDK和JRE也是一个问题。 它们作为一个整体作为使用,从而增加了下载时间,启动时间和内存占用。 单体JRE使得Java不可能在内存很小的设备上使用。 如果将Java应用程序部署到云端,则需要支付更多的费用购买更多的使用内存。 大多数情况下,单体JRE使用的内存比所需的内存多,这意味着需要为云服务支付更多的内存。 Java 8中引入的Compact配置文件通过允许将JRE的一个子集打包在称为紧凑配置文件的自定义运行时映像中,大大减少了JRE大小,从而减少了运行时内存占用。

Tips

在早期访问版本中,JDK 9包含三个名为java.compact1,java.compact2和java.compact3的模块,这些模块对应于JDK 8中的三个compact配置文件。之后,它们被删除,因为JDK中的模块可以完全控制在自定义JRE中包含的模块列表。

可以将JDK 9之前的JDK/JRE中的这些问题分为三类:

不可靠的配置

弱封装

JDK/JRE的单体结构

下图显示了Java运行时如何看到类路径上的所有JAR,以及如何从其他JAR访问一个JAR中的代码,没有任何限制,除了在访问控制方面由类型声明指定的代码。

9c7475ea446f

类路径上的JAR,由Java运行时加载和访问

Java 9通过引入开发,打包和部署Java应用程序的新方法来解决这些问题。 在Java 9中,Java应用程序由称为模块的小型交互组件组成。 Java 9也已经将JDK/JRE组织为一组模块。

二. 全新的模块系统

Java 9引入了一个称为模块的新的程序组件。 您可以将Java应用程序视为具有明确定义的边界和这些模块之间依赖关系的交互模块的集合。 模块系统的开发具有以下目标:

可靠的配置

强封装

模块化JDK/JRE

这些目标是解决Java 9之前开发和部署Java应用程序所面临的问题。

可靠的配置解决了用于查找类型的容易出错的类路径机制的问题。 模块必须声明对其他模块的显式依赖。 模块系统验证应用程序开发的所有阶段的依赖关系 —— 编译时,链接时和运行时。 假设一个模块声明对另一个模块的依赖,并且第二个模块在启动时丢失。 JVM检测到依赖关系丢失,并在启动时失败。 在Java 9之前,当使用缺少的类型时,这样的应用程序会生成运行时错误(不是在启动时)。

强大的封装解决了类路径上跨JAR的公共类型的可访问性问题。 模块必须明确声明其中哪些公共类型可以被其他模块访问。 除非这些模块明确地使其公共类型可访问,否则模块不能访问另一个模块中的公共类型。 Java 9中的公共类型并不意味着程序的所有部分都可以访问它。 模块系统增加了更精细的可访问性控制。

Tips

Java 9通过允许模块在开发的所有阶段声明明确的依赖关系并验证这些依赖关系来提供可靠的配置。它通过允许模块声明其公共类型可以访问其他模块的软件包来提供强大的封装。

JDK 9通过将其前身的体结构分解成一组称为平台模块的模块来重写。 JDK 9还引入了一个可选的阶段,称为链接时,这可能在编译时和运行时之间发生。 在链接期间,使用一个链接器,它是JDK 9附带的一个名为jlink的工具,用于创建应用程序的自定义运行时映像,其中仅包含应用程序中使用的模块。 这将运行时的大小调整到最佳大小。

三. 什么是模块化

模块是代码和数据集合。 它可以包含Java代码和本地代码。 Java代码被组织为一组包含诸如类,接口,枚举和注解等类型的类。 数据可以包括诸如图像文件和配置文件的资源。

对于Java代码,模块可以看做零个或多个包的集合。 下图显示了三个名为policy,claim和utility的模块,其中policy模块包含两个包,claim模块包含一个包,而utility模块不包含任何包。

9c7475ea446f

类型,包和模块的排列

一个模块不仅仅是一个包的容器。 除了其名称,模块定义包含以下内容:

所需的其他模块(或依赖于)的列表

导出的软件包列表(其公共API),其他模块可以使用

开放的软件包(其整个API,公共和私有)到其他反射访问模块的列表

使用的服务列表(或使用java.util.ServiceLoader类发现和加载)

提供的服务的实现列表

在使用这些模块时,可以使用这些方面中的一个或多个。

Java SE 9平台规范将平台划分为称为平台模块的一组模块。 Java SE 9平台的实现可能包含一些或所有平台模块,从而提供可扩展的Java运行时。 标准模块的名字是以Java 为前缀。 Java SE标准模块的示例有java.base,java.sql,java.xml和java.logging。 支持标准平台模块中的API,供开发人员使用。

非标准平台模块是JDK的一部分,但未在Java SE平台规范中指定。 这些JDK特定的模块的名称以jdk为前缀。 JDK特定模块的示例是jdk.charsets,jdk.compiler,jdk.jlink,jdk.policytool和jdk.zipfs。 JDK特定模块中的API不适用于开发人员。 这些API通常用于JDK本身以及不能轻易获得使用Java SE API所需功能的库开发人员使用。 如果使用这些模块中的API,则可能会在未经通知的情况下对其进行支持或更改。

JavaFX不是Java SE 9平台规范的一部分。 但是,在安装JDK/JRE时,会安装与JavaFX相关的模块。 JavaFX模块名称以javafx为前缀。 JavaFX模块的示例是javafx.base,javafx.controls,javafx.fxml,javafx.graphics和javafx.web。

作为Java SE 9平台的一部分的java.base模块是原始模块。 它不依赖于任何其他模块。 模块系统只知道java.base模块。 它通过模块中指定的依赖关系发现所有其他模块。 java.base模块导出核心Java SE软件包,如java.lang,java.io,java.math,java.text,java.time,java.util等。

四. 模块依赖关系

包括JDK 8之前的版本,一个包中的公共类型可以被其他包访问,没有任何限制。 换句话说,包没有控制它们包含的类型的可访问性。 JDK 9中的模块系统对类型的可访问性提供了细粒度的控制。

模块之间的可访问性是所使用的模块和使用模块之间的双向协议:模块明确地使其公共类型可供其他模块使用,并且使用这些公共类型的模块明确声明对第一个模块的依赖。 模块中的所有未导出的软件包都是模块的私有的,它们不能在模块之外使用。

将包中的 API 设置为公共供其他模块使用被称之为导出包。如果名为policy的模块将名为pkg1的包设置为公共类型可用于其他模块访问,则说明policy模块导出包pkg1。如果名为claim的模块声明对policy模块的依赖性,则称之为claim模块读取(read)policy模块。这意味着,在claim模块内部可以访问policy模块导出包中的所有公共类型。模块还可以选择性地将包导出到一个或多个命名模块。这种导出成为qualified导出或module-friendly导出。 qualified导出中的包中的公共类型只能访问指定的命名模块。

在模块系统的上下文中,可以互换使用三个术语 —— 需要(require),读取(read)和依赖(depend)。 以下三个语句意思相同:P读取Q,P需要Q,P依赖Q,其中P和Q指的是两个模块。

下图描述了两个名为policy和claim的模块之间的依赖关系。 policy模块包含两个名为pkg1和pkg2的包,它导出包pkg1,该包使用虚线边界显示,以将其与未导出的包pkg2区分开来。 claim模块包含两个件包pkg3和pkg4,它不导出包。 它声明了对policy模块的依赖。

9c7475ea446f

在两个模块间声明依赖

在JDK 9中,您可以如下声明这两个模块:

module policy {

exports pkg1;

}

module claim {

requires policy;

}

Tips

用于指示模块中的依赖关系的语法是不对称的 ——导出一个包,但需要一个模块。

如果你的模块依赖于另一个模块,则该模块声明要求知道模块名称。几个Java框架和工具在很大程度上依赖于反射来在运行时访问未导出的模块的代码。它们提供了很大的功能,如依赖注入,序列化,Java Persistence API的实现,代码自动化和调试。Spring,Hibernate和XStream是这样的框架和库的例子。这些框架和库不了解你的应用程序模块。 但是,他们需要访问模块中的类型来完成他们的工作。 他们还需要访问模块的私有成员,这打破了JDK 9中强封装的前提。当模块导出软件包时,依赖于第一个模块的其他模块只能访问导出的软件包中的公共API。 在运行时,在模块的所有软件包上授予深入的反射访问权限(访问公共和私有API),可以声明一个开放的模块。

9c7475ea446f

开放的模块允许反射访问其所有成员

在JDK 9中,可以如下声明这两个模块:

open module policy.model {

requires jdojo.jpa;

}

module jdojo.jpa {

// The module exports its packages here

}

1. 模块图

模块系统只知道一个模块:java.base。 java.base模块不依赖于任何其他模块。 所有其他模块都隐含地依赖于java.base模块。

应用程序的模块化结构可以被视为一个称为模块图。 在模块图中,每个模块都表示为一个节点。 如果第一个模块依赖于第二个模块,则存在从模块到另一个模块的有向边。 通过将称为根模块的一组初始模块的依赖关系与称为可观察模块的模块系统已知的一组模块相结合来构建模块图。

Tips

模块解析意味着该模块所依赖的模块可用。 假设名为P的模块取决于两个名为Q和R的模块。解析模块P表示您定位模块Q和R,并递归地解析模块Q和R。

构建模块图旨在在编译时,链接时和运行时解析模块依赖关系。 模块解析从根模块开始,并遵循依赖关系链接,直到达到java.base模块。 有时,可能在模块路径上有一个模块,但是会收到该模块未找到的错误。 如果模块未解析,并且未包含在模块图中,则可能会发生这种情况。 对于要解决的模块,需要从根模块开始依赖关系链。 根据调用编译器或Java启动器的方式,选择一组默认的根模块。 还可以将模块添加到默认的根模块中。 了解在不同情况下如何选择默认根模块很重要:

如果应用程序代码是从类路径编译的,或者主类是从类路径运行的,则默认的根模块将由java.se模块和所有非“java.”系统模块组成,如“jdk.”和“JavaFX.”。 如果java.se模块不存在,则默认的根模块将由所有“java.”和非“java.*”模块组成。

如果您的应用程序由模块组成,则默认的根模块将依赖于以下阶段:

在编译时,它由所有正在编译的模块组成。

在链接时,它是空的。

在运行时,它包含有主类的模块。 在java命令中使用--module或-m选项指定要运行的模块及其主类。

继续介绍policy和claim模块的例子,假设pkg3.Main是claim模块中的主类,并且两个模块都作为模块化JAR打包在C:\ Java9Revealed\lib目录中。下图显示了使用以下命令运行应用程序时在运行时构建的模块图:

9c7475ea446f

模块图的示例

C:\Java9Revealed>java -p lib -m claim/pkg3.Main

claim模块包含应用程序的主类。 因此,claim是创建模块图时唯一的根模块。 policy模块需要被解决,因为claim模块依赖于policy模块。 还需要解析java.base模块,因为所有其他模块都依赖于它,这两个模块也是如此。

模块图的复杂性取决于根模块的数量和模块之间的依赖关系。 假设除了依赖于policy模块之外,claim模块还取决于java.sql的平台模块。 claim模块的新声明如下所示:

module policy {

requires policy;

requires java.sql;

}

如下图,显示在claim模块中运行pkg3.Main类时将构建的模块图。 请注意,java.xml和java.logging模块也存在于图中,因为java.sql模块依赖于它们。 在图中,claim模块是唯一的根模块。

9c7475ea446f

显示对java.sql模块依赖的模块图

下图显示了java.se的平台模块的最复杂的模块图形之一。 java.se模块的模块声明如下:

9c7475ea446f

以java.se为根模块的模块图

java.se 模块的定义如下所示:

module java.se {

requires transitive java.sql;

requires transitive java.rmi;

requires transitive java.desktop;

requires transitive java.security.jgss;

requires transitive java.security.sasl;

requires transitive java.management;

requires transitive java.logging;

requires transitive java.xml;

requires transitive java.scripting;

requires transitive java.compiler;

requires transitive java.naming;

requires transitive java.instrument;

requires transitive java.xml.crypto;

requires transitive java.prefs;

requires transitive java.sql.rowset;

requires java.base;

requires transitive java.datatransfer;

}

有时,需要将模块添加到默认的根模块中,以便解析添加的模块。 可以在编译时,链接时和运行使用--add-modules命令行选项指定其他根模块:

--add-modules

这里的是逗号分隔的模块名称列表。

可以使用以下特殊值作为具有特殊含义的--add-modules选项的模块列表:

ALL-DEFAULT

ALL-SYSTEM

ALL-MODULE-PATH

所有三个特殊值在运行时都有效。 只能在编译时使用ALL-MODULE-PATH。

如果使用ALL-DEFAULT作为模块列表,则从应用程序从类路径运行时使用的默认的根模块集将添加到根集中。 这对于作为容器的应用程序是有用的,托管可能需要容器应用程序本身不需要的其他模块的其他应用程序。 这是一种使所有Java SE模块可用于容器的方法,因此任何托管的应用程序都可能使用到它们。

如果将ALL-SYSTEM用作模块列表,则将所有系统模块添加到根集中。 这对于运行测试时非常有用。

如果使用ALL-MODULE-PATH作为模块列表,则在模块路径上找到的所有模块都将添加到根集中。 这对于诸如Maven这样的工具非常有用,这确保了应用程序需要模块路径上的所有模块。

Tips

即使模块存在于模块路径上,也可能会收到模块未找到的错误。 在这种情况下,需要使用--add-modules命令行选项将缺少的模块添加到默认的根模块中。

JDK 9支持一个有用的非标准命令行选项,它打印描述在构建模块图时用于解析模块的步骤的诊断消息。 选项是-Xdiag:resolver。 以下命令在声明模块中运行pkg3.Main类。 显示部分输出。 在诊断消息的结尾,你会发现一个结果:部分列出了解决模块。

使用命令C:\Java9Revealed>java -Xdiag:resolver -p lib -m claim/pkg3.Main,会得到如下输出:

[Resolver] Root module claim located

[Resolver] (file:///C:/Java9Revealed/lib/claim.jar)

[Resolver] Module java.base located, required by claim

[Resolver] (jrt:/java.base)

[Resolver] Module policy located, required by claim

[Resolver] (file:///C:/Java9Revealed/lib/policy.jar)

...

[Resolver] Result:

[Resolver] claim

[Resolver] java.base

...

[Resolver] policy

五. 聚合模块

你可以创建一个不包含任何代码的模块。 它收集并重新导出其他模块的内容。 这样的模块称为聚合模块。假设有几个模块依赖于五个模块。 您可以为这五个模块创建一个聚合模块,现在,你的模块只能依赖于一个模块 —— 聚合模块。

为了方便, Java 9包含几个聚合模块,如java.se和java.se.ee。 java.se模块收集Java SE的不与Java EE重叠的部分。 java.se.ee模块收集组成Java SE的所有模块,包括与Java EE重叠的模块。

六. 声明模块

本节包含用于声明模块的语法的快速概述。 在以后的章节中更详细地解释每个部分。 如果不明白本节提到的模块,请继续阅读。

使用模块声明来定义模块,是Java编程语言中的新概念。其语法如下:

[open] module {

;

;

...

}

open修饰符是可选的,它声明一个开放的模块。 一个开放的模块导出所有的包,以便其他模块使用反射访问。 是要定义的模块的名称。 是一个模块语句。 模块声明中可以包含零个或多个模块语句。 如果它存在,它可以是五种类型的语句之一:

导出语句(exports statement);

开放语句(opens statement);

需要语句(requires statement);

使用语句(uses statement);

提供语句(provides statement)。

导出和开放语句用于控制对模块代码的访问。 需要语句用于声明模块对另一个模块的依赖关系。 使用和提供的语句分别用于表达服务消费和服务提供。 以下是名为myModule的模块的模块声明示例:

module myModule {

// Exports the packages - com.jdojo.util and

// com.jdojo.util.parser

exports com.jdojo.util;

exports com.jdojo.util.parser;

// Reads the java.sql module

requires java.sql;

// Opens com.jdojo.legacy package for reflective access

opens com.jdojo.legacy;

// Uses the service interface java.sql.Driver

uses java.sql.Driver;

// Provides the com.jdojo.util.parser.FasterCsvParser

// class as an implementation for the service interface

// named com.jdojo.util.CsvParser

provides com.jdojo.util.CsvParser

with com.jdojo.util.parser.FasterCsvParser;

}

你可以使用模块声明中的open修饰符来创建一个开放模块。 一个开放模块可以将其所有软件包的反射访问授予其他模块。 你不能在open模块中再使用open语句,因为所有程序包都是在open模块中隐式打开的。 以下代码段声明一个名为myLegacyModule的开放模块:

open module myLegacyModule {

exports com.jdojo.legacy;

requires java.sql;

}

1. 模块命名

模块名称可以是Java限定标识符。 合法标识符是一个或多个由点分隔的标识符,例如policy,com.jdojo.common和com.jdojo.util。 如果模块名称中的任何部分不是有效的Java标识符,则会发生编译时错误。 例如,com.jdojo.common.1.0不是有效的模块名称,因为名称中的1和0不是有效的Java标识符。

与包命名约定类似,使用反向域名模式为模块提供唯一的名称。 使用这个惯例,名为com.jdojo.common的最简单的模块可以声明如下:

module com.jdojo.common {

// No module statements

}

模块名称不会隐藏具有相同名称的变量,类型和包。 因此,可以拥有一个模块以及具有相同名称的变量,类型或包。 他们使用的上下文将区分哪个名称是指什么样的实体。

在JDK 9中, open, module, requires, transitive, exports, opens, to, uses, provides 和 with是受限关键字。只有当具体位置出现在模块声明中时,它们才具有特殊意义。 可以将它们用作程序中其他地方的标识符。 例如,以下模块声明是有效的,即使它不使用直观的模块名称:

// Declare a module named module

module module {

// Module statements go here

}

第一个模块字被解释为一个关键字,第二个是一个模块名称。

你可以在程序中的任何地方声明一个名为module的变量:

String module = "myModule";

2. 模块的访问控制

导出语句将模块的指定包导出到所有模块或编译时和运行时的命名模块列表。 它的两种形式如下:

exports ;

exports to , ...;

以下是使用了导出语句的模块示例:

module M {

exports com.jdojo.util;

exports com.jdojo.policy

to com.jdojo.claim, com.jdojo.billing;

}

开放语句允许对所有模块的反射访问指定的包或运行时指定的模块列表。 其他模块可以使用反射访问指定包中的所有类型以及这些类型的所有成员(私有和公共)。 开放语句采用以下形式:

opens ;

opens to , ...;

使用开放语句的实例:

module M {

opens com.jdojo.claim.model;

opens com.jdojo.policy.model to core.hibernate;

opens com.jdojo.services to core.spring;

}

Tips

对比导出和打开语句。 导出语句允许仅在编译时和运行时访问指定包的公共API,而打开语句允许在运行时使用反射访问指定包中的所有类型的公共和私有成员。

如果模块需要在编译时从另一个模块访问公共类型,并在运行时使用反射访问类型的私有成员,则第二个模块可以导出并打开相同的软件包,如下所示:

module N {

exports com.jdojo.claim.model;

opens com.jdojo.claim.model;

}

阅读有关模块的时候会遇到三个短语:

模块M导出包P

模块M打开包Q

模块M包含包R

前两个短语对应于模块中导出语句和开放语句。 第三个短语意味着该模块包含的包R既不导出也不开放。 在模块系统的早期设计中,第三种情况被称为“模块M隐藏包R”。

3. 声明依赖关系

需要(require)语句声明当前模块与另一个模块的依赖关系。 一个名为M的模块中的“需要N”语句表示模块M取决于(或读取)模块N。语句有以下形式:

requires ;

requires transitive ;

requires static ;

requires transitive static ;

require语句中的静态修饰符表示在编译时的依赖是强制的,但在运行时是可选的。requires static N语句意味着模块M取决于模块N,模块N必须在编译时出现才能编译模块M,而在运行时存在模块N是可选的。require语句中的transitive修饰符会导致依赖于当前模块的其他模块具有隐式依赖性。假设有三个模块P,Q和R,假设模块Q包含requires transitive R语句,如果如果模块P包含包含requires Q语句,这意味着模块P隐含地取决于模块R。

4. 配置服务

Java允许使用服务提供者和服务使用者分离的服务提供者机制。 JDK 9允许使用语句(uses statement)和提供语句(provides statement)实现其服务。

使用语句可以指定服务接口的名字,当前模块就会发现它,使用 java.util.ServiceLoader类进行加载。格式如下:

uses ;

使用语句的实例如下:

module M {

uses com.jdojo.prime.PrimeChecker;

}

com.jdojo.PrimeChecker是一个服务接口,其实现类将由其他模块提供。 模块M将使用java.util.ServiceLoader类来发现和加载此接口的实现。

提供语句指定服务接口的一个或多个服务提供程序实现类。 它采取以下形式:

provides

with , ...;

相同的模块可以提供服务实现,可以发现和加载服务。 模块还可以发现和加载一种服务,并为另一种服务提供实现。 以下是例子:

module P {

uses com.jdojo.CsvParser;

provides com.jdojo.CsvParser

with com.jdojo.CsvParserImpl;

provides com.jdojo.prime.PrimeChecker

with com.jdojo.prime.generic.FasterPrimeChecker;

}

七. 模块描述符

在了解上一节中如何声明模块之后,你可能会对模块声明的源代码有几个疑问:

在哪里保存模块声明的源代码? 是否保存在文件中? 如果是,文件名是什么?

在哪里放置模块声明源代码文件?

模块的声明的源代码如何编译?

1. 编译模块声明

模块声明存储在名为module-info.java的文件中,该文件存储在该模块的源文件层次结构的根目录下。 Java编译器将模块声明编译为名为module-info.class的文件。 module-info.class文件被称为模块描述符,它被放置在模块的编译代码层次结构的根目录下。 如果将模块的编译代码打包到JAR文件中,则module-info.class文件将存储在JAR文件的根目录下。

模块声明不包含可执行代码。 实质上,它包含一个模块的配置。 那为什么我们不在XML或JSON格式的文本文件中保留模块声明,而是在类文件中? 类文件被选为模块描述符,因为类文件具有可扩展,明确定义的格式。 模块描述符包含源码级模块声明的编译形式。 它可以通过工具来增强,例如 jar工具,在模块声明初始编译之后,在类文件属性中包含附加信息。 类文件格式还允许开发人员在模块声明中使用导入和注解。

2. 模块版本

在模块系统的初始原型中,模块声明还包括模块版本的。 包括模块版本在声明中使模块系统的实现复杂化,所以模块版本从声明中删除。

模块描述符(类文件格式)的可扩展格式被利用来向模块添加版本。 当将模块的编译代码打包到JAR中时,该jar工具提供了一个添加模块版本的选项,最后将其添加到module-info.class文件中。

3. 模块源文件结构

我们来看一个组织源代码和一个名为com.jdojo.contact的模块的编译代码的例子。 该模块包含用于处理联系信息的包,例如地址和电话号码。 它包含两个包:

com.jdojo.contact.info

com.jdojo.contact.validator

com.jdojo.contact.info包中包含两个类 —— Address 和 Phone。 com.jdojo.contact.validator包中包含一个名为Validator的接口和两个名为AddressValidator和PhoneValidator的类。

下图显示了com.jdojo.contact模块中的内容

9c7475ea446f

com.jdojo.contact模块中的内容

在Java 9中,Java编译器工具javac添加了几个选项。 它允许一次编译一个模块或多个模块。 如果要一次编译多个模块,则必须将每个模块的源代码存储在与模块名称相同的目录下。 即使只有一个模块,也最好遵循此源目录命名约定。

假设你想编译com.jdojo.contact模块的源代码。 可以将其源代码存储在名为C:\j9r\src的目录中,其中包含以下文件:

module-info.java

com\jdojo\contact\info\Address.java

com\jdojo\contact\info\Phone.java

com\jdojo\contact\validator\Validator.java

com\jdojo\contact\validator\AddressValidator.java

com\jdojo\contact\validator\PhoneValidator.java

请注意,需要遵循包层次结构来存储接口和类的源文件。

如果要一次编译多个模块,则必须将源代码目录命名为com.jdojo.contact,这与模块的名称相同。 在这种情况下,可以将模块的源代码存储在名为C:\j9r\src的目录中,其目录如下:

com.jdojo.contact\module-info.java

com.jdojo.contact\com\jdojo\contact\info\Address.java

com.jdojo.contact\com\jdojo\contact\info\Phone.java

com.jdojo.contact\com\jdojo\contact\validator\Validator.java

com.jdojo.contact\com\jdojo\contact\validator\AddressValidator.java

com.jdojo.contact\com\jdojo\contact\validator\PhoneValidator.java

模块的编译后代码将遵循与之前看到的相同的目录层次结构。

八. 打包模块

模块的artifact可以存储在:

目录中

模块化的JAR文件中

JMOD文件中,它是JDK 9中引入的一种新的模块封装格式

1. 目录中的模块

当模块的编译代码存储在目录中时,目录的根目录包含模块描述符(module-info.class文件),子目录是包层次结构的镜像。 继续上一节中的示例,假设将com.jdojo.contact模块的编译代码存储在C:\j9r\mods\ com.jdojo.contact目录中。 目录的内容如下:

com\jdojo\contact\info\Address.class

com\jdojo\contact\info\Phone.class

com\jdojo\contact\validator\Validator.class

com\jdojo\contact\validator\AddressValidator.class

com\jdojo\contact\validator\PhoneValidator.class

2. 模块化JAR中的模块

JDK附带一个jar工具,以JAR(Java Archive)文件格式打包Java代码。 JAR格式基于ZIP文件格式。 JDK 9增强了在JAR中打包模块代码的jar工具。 当JAR包含模块的编译代码时,JAR称为模块化JAR。 模块化JAR在根目录下包含一个module-info.class文件。

无论在JDK 9之前使用JAR,现在都可以使用模块化JAR。 例如,模块化JAR可以放置在类路径上,在这种情况下,模块化JAR中的module-info.class文件将被忽略,因为module-info在Java中不是有效的类名。

在打包模块化JAR的同时,可以使用JDK 9中添加的jar工具中可用的各种选项,将模块描述符中的信息例如模块版本添加到主类中。

Tips

模块化JAR在各个方面来看都是一个JAR,除了它在根路径下包含的模块描述符。通常,比较重要的Java应用程序由多个模块组成。 模块化JAR可以是一个模块,包含编译的代码。 需要将应用程序的所有模块打包到单个JAR中。

继续上一节中的示例,com.jdojo.contact模块的模块化JAR内容如下。 请注意,JAR在META-INF目录中始终包含一个MANIFEST.MF文件。

module-info.class

com/jdojo/contact/info/Address.class

com/jdojo/contact/info/Phone.class

com/jdojo/contact/validator/Validator.class

com/jdojo/contact/validator/AddressValidator.class

com/jdojo/contact/validator/PhoneValidator.class

META-INF/MANIFEST.MF

3. JMOD文件中的模块

JDK 9引入了一种称为JMOD的新格式来封装模块。 JMOD文件使用.jmod扩展名。 JDK模块被编译成JMOD格式,放在JDK_HOME \ jmods目录中。例如,可以找到一个包含java.base模块内容的java.base.jmod文件。 仅在编译时和链接时才支持JMOD文件。 它们在运行时不受支持。

九. 模块路径

自JDK开始以来,类路径机制查找类型已经存在。 类路径是一系列目录,JAR文件和ZIP文件。 当Java需要在各个阶段(编译时,运行时,工具使用等)中查找类型时,它会使用类路径中的条目来查找类型。

Java 9类型作为模块的一部分存在。 Java需要在不同阶段查找模块,而不是类似于Java 9之前的模块。Java 9引入了一种新的机制来查找模块,它被称为模块路径。

模块路径是包含模块的路径名称序列,其中路径名可以是模块化JAR,JMOD文件或目录的路径。 路径名由特定于平台的路径分隔符分隔,在UNIX平台上为冒号(:),Windows平台上分号(;)。

当路径名称是模块化的JAR或JMOD文件时,很容易理解。 在这种情况下,如果JAR或JMOD文件中的模块描述符包含要查找的模块的模块定义,则会找到该模块。 如果路径名是目录,则存在以下两种情况:

如果类文件存在于根目录,则该目录被认为具有模块定义。 根目录下的类文件将被解释为模块描述符。 所有其他文件和子目录将被解释为此一个模块的一部分。 如果根目录中存在多个类文件,则首先找到的文件被解释为模块描述符。 经过几次实验,JDK 9似乎以按字母排列的顺序拾取了第一类文件。 这种存储模块编译代码的方式肯定会让你头疼。 因此,如果目录在根目录中包含多个类文件,请避免向模块路径添加目录。

如果根目录中不存在类文件,则目录的内容将被不同的解释。 目录中的每个模块化JAR或JMOD文件被认为是模块定义。 每个子目录,如果它包含在它的根一个 module-info.class文件,被认为具有展开目录树格式的模块定义。 如果一个子目录的根目录不包含一个module-info.class文件,那么它不会被解释为包含一个模块定义。 请注意,如果子目录包含模块定义,则其名称不必与模块名称相同。 模块名称是从module-info.class文件中读取的。

以下是Windows上的有效模块路径:

C:\mods

C:\mods\com.jdojo.contact.jar;C:\mods\com.jdojo.person.jar

C:\lib;C:\mods\com.jdojo.contact.jar;C:\mods\com.jdojo.person.jar

第一个模块路径包含名为C:\mods的目录的路径。 第二个模块路径包含两个模块化JAR——com.jdojo.contact.jar和com.jdojo.person.jar的路径。 第三个模块路径包含三个元素 —— 目录C:\lib的路径,以及两个模块化JAR——com.jdojo.contact.jar和com.jdojo.person.jar的路径。 在类似UNIX的平台上显示相当于这些路径:

/usr/ksharan/mods

/usr/ksharan/mods/com.jdojo.contact.jar:/usr/ksharan/com.jdojo.person.jar

/usr/ksharan/lib:/usr/ksharan/mods/com.jdojo.contact.jar:/usr/ksharan/mods/com.jdojo.person.jar

避免模​​块路径问题的最佳方法是不要将分解的目录用作模块定义。

有两个目录作为模块路径 —— 一个包含所有应用程序模块化JAR的目录,另一个包含用于外部库的所有模块化JAR的目录。例如,可以使用C:\applib 和 C:\extlib作为Windows上的模块路径,其中C:\applib目录包含所有应用程序模块化JAR,C:\extlib目录包含所有外部库的模块化JAR。

JDK 9已经更新了所有的工具来使用模块路径来查找模块。这些工具提供了指定模块路径的新选项。到JDK 9,已经看到以一个连字符(-)开头的UNIX样式选项,例如-cp和-classpath。在JDK 9中有如此多的附加选项,JDK设计人员对于开发人员来说也用完了有意义的短名称的选项。因此,JDK 9开始使用GNU样式选项,其中选项以两个连续的连字符开头,并且单词由连字符分隔。以下是GNU样式命令行选项的几个示例:

--class-path

--module-path

--module-version

--main-class

--print-module-descriptor

Tips

要打印工具支持的所有标准选项的列表,使用--help或-h选项运行该工具,对于所有非标准选项,使用-X选项运行该工具。 例如,java -h和java -X命令将分别打印java命令的标准和非标准选项列表。

JDK 9中的大多数工具(如javac,java和jar)都支持两个选项来在命令行上指定一个模块路径。 它们是-p和--module-path。 将继续支持现有的UNIX样式选项以实现向后兼容性。 以下两个命令显示如何使用两个选项来指定java工具的模块路径:

// Using the GNU-style option

C:\>java --module-path C:\applib;C:\lib other-args-go-here

// Using the UNIX-style option

C:\>java -p C:\applib;C:\extlib other-args-go-here

当您使用GNU样式选项时,可以使用以下两种形式之一指定该选项的值:

--

--=

上面的命令也可以写成如下形式:

// Using the GNU-style option

C:\>java --module-path=C:\applib;C:\lib other-args-go-here

当使用空格作为名称值分隔符时,需要至少使用一个空格。 您使用=作为分隔符时,不得在其周围包含任何空格。

十. 可观察模块

在模块查找过程中,模块系统使用不同类型的模块路径来定位模块。 在模块路径上与系统模块一起发现的一组模块被称为可观察模块。 可以将可观察模块视为模块系统在特定阶段可用的所有模块的集合,例如编译时,链接时和运行时,或可用于工具。

JDK 9为java命令添加了一个名为--list-modules的新选项。 该选项可用于打印两种类型的信息:可观察模块的列表和一个或多个模块的描述。 该选项可以以两种形式使用:

--list-modules

--list-modules ,...

在第一种形式中,该选项没有跟随任何模块名称。 它打印可观察模块的列表。 在第二种形式中,该选项后面是逗号分隔的模块名称列表,用于打印指定模块的模块描述符。

以下命令打印可观察模块的列表,其中仅包括系统模块:

c:\Java9Revealed> java --list-modules

java.base@9-ea

java.se.ee@9-ea

java.sql@9-ea

javafx.base@9-ea

javafx.controls@9-ea

jdk.jshell@9-ea

jdk.unsupported@9-ea

...

上面显示的是输出部分内容。 输出中的每个条目都包含两个部分—— 一个模块名称和一个由@符号分隔的版本字符串。 第一部分是模块名称,第二部分是模块的版本字符串。 例如,在java.base@9-ea中,java.base是模块名称,9-ea是版本字符串。 在版本字符串中,数字9表示JDK 9,ea代表早期访问。 运行命令时,你可能会得到不同的版本字符串输出。

现在在C:\Java9Revealed\lib目录中放置了三个模块化JAR。 如果提供此目录作为java命令的模块路径,这些模块将被包含在可观察模块列表中。以下命令显示了改变指定一个模块路径后,观察到的模块列表。 这里,lib目录是相对路径,C:\Java9Revealed是当前目录。

C:\Java9Revealed>java --module-path lib --list-modules

claim (file:///C:/Java9Revealed/lib/claim.jar)

policy (file:///C:/Java9Revealed/lib/policy.jar)

java.base@9-ea

java.xml@9-ea

javafx.base@9-ea

jdk.unsupported@9-ea

jdk.zipfs@9-ea

...

注意,对于应用程序模块,--list-modules选项还会打印它们的位置。 当获得意想不到的结果,并且不知道正在使用哪些模块以及哪些位置时,此信息有助于排除故障。

以下命令将com.jdojo.intro模块指定为--list-modules选项的参数,以打印模块的描述:

C:\Java9Revealed>java --module-path lib --list-modules claim

module claim (file:///C:/Java9Revealed/lib/claim.jar)

exports com.jdojo.claim

requires java.sql (@9-ea)

requires mandated java.base (@9-ea)

contains pkg3

输出的第一行包含模块名称和包含该模块的模块化JAR位置。 第二行表示该模块导出com.jdojo.claim模块。 第三行表示该模块需要java.sql模块。 第四行表示模块强制依赖于java.base模块。 回想一下,除了java.base模块之外的每个模块都取决于java.base模块。 除了java.base模块,在每个模块的描述中看到需要强制的java.base模块。 第五行声明该模块包含一个名为pkg3的包,既不导出也不开放。

你还可以使用--list-modules打印系统模块的描述,例如java.base和java.sql。 以下命令打印出java.sql模块的描述。

C:\Java9Revealed>java --list-modules java.sql

module java.sql@9-ea

exports java.sql

exports javax.sql

exports javax.transaction.xa

requires transitive java.xml

requires mandated java.base

requires transitive java.logging

uses java.sql.Driver

十一. 总结

Java中的包已被用作类型的容器。 应用程序由放置在类路径上的几个JAR组成。 软件包作为类型的容器,不强制执行任何可访问性边界。 类型的可访问性内置在使用修饰符的类型声明中。 如果包中包含内部实现,则无法阻止程序的其他部分访问内部实现。 类路径机制在使用类型时线性搜索类型。 这导致在部署的JAR中缺少类型时,在运行时接收错误的另一个问题 —— 有时在部署应用程序后很长时间。 这些问题可以分为两种类型:封装和配置。

JDK 9引入了模块系统。 它提供了一种组织Java程序的方法。 它有两个主要目标:强大的封装和可靠的配置。 使用模块系统,应用程序由模块组成,这些模块被命名为代码和数据的集合。 模块通过其声明来控制模块的其他模块可以访问的部分。 访问另一个模块的部分的模块必须声明对第二个模块的依赖。 控制访问和声明依赖的是达成强封装的基础。 在应用程序启动时解决了一个模块的依赖关系。 在JDK 9中,如果一个模块依赖于另一个模块,并且运行应用程序时第二个模块丢失,则在启动时将会收到一个错误,而不是应用程序运行后的某个时间。 这是一个可靠的基础配置。

使用模块声明定义模块。 模块的源代码通常存储在名为module-info.java的文件中。 一个模块被编译成一个类文件,通常命名为module-info.class。 编译后的模块声明称为模块描述符。 模块声明不允许指定模块版本。 但诸如将模块打包到JAR中的jar工具的可以将模块版本添加到模块描述符中。

使用module关键字声明模块,后跟模块名称。 模块声明可以使用五种类型的模块语句:exports,opens,require,uses和provide。 导出语句将模块的指定包导出到所有模块或编译时和运行时的命名模块列表。 开放语句允许对所有模块的反射访问指定的包或运行时指定的模块列表, 其他模块可以使用反射访问指定包中的所有类型以及这些类型的所有成员(私有和公共)。 使用语句和提供模块语句用于配置模块以发现服务实现并提供特定服务接口的服务实现。

从JDK 9开始,open, module, requires, transitive, exports,opens,to,uses,provides和with都是受限关键字。 只有当具体位置出现在模块声明中时,它们才具有特殊意义。

模块的源代码和编译代码被安排在目录,JAR文件或JMOD文件中。 在目录和JAR文件中,module-info.class文件位于根目录。

与类路径类似,JDK 9引入了模块路径。 但是,它们的使用方式有所不同。 类路径用于搜索类型的定义,而模块路径用于查找模块,而不是模块中的特定类型。 Java工具(如java和javac)已经被更新为使用模块路径和类路径。 您可以使用--module-path或-p选项指定这些工具的模块路径。

JDK 9引入了与工具一起使用的GNU风格选项。 选项以两个破折号开头,每个单词用短划线分隔,例如--module-path,--class-path,--list-modules等。如果选项接受一个值,则该值可以跟随选项加上空格或=。 以下两个选项是一样的:

--module-path C:\lib

--module-path=C:\lib

模块系统在某个阶段(编译时,运行时,工具等)中可用的模块列表被称为可观察模块。 可以使用--list-modules选项与java命令列出运行时可用的可观察模块。 还可以使用此选项打印模块的描述。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1篇 Java开发必备基础 第1章 搭建Java开发环境 1.1 Java的过去、现在和未来 1.1.1 Java的历史 1.1.2 Java的语言特点 1.1.3 Java API简介 1.1.4 Java未来发展 1.2 Java程序设计环境 1.2.1 命令行工具——JDK 6.0 1.2.2 安装工具包JDK 1.2.3 设置JDK环境 1.2.4 集成开发环境安装——MyEclipse 8.5 1.2.5 MyEclipse的一些常用操作 1.3 创建和运行Java程序 1.3.1 手工创建、编译和运行Java程序 1.3.2 在MyEclipse 8.5创建、运行、调试和管理Java项目 1.4 小结 第2章 Java面向对象编程 2.1 面向对象的一些概念 2.1.1 面向对象涉及的概念 2.1.2 类和对象 2.2 面向对象的一些特性 2.2.1 继承特性 2.2.2 多态特性 2.2.3 封装特性 2.3 Java实现的面向对象特性 2.3.1 定义类 2.3.2 创建对象 2.3.3 实现继承 2.3.4 实现多态 2.3.5 实现封装 2.4 小结 第3章 Java新特性 3.1 Java的一些简单新特性 3.1.1 静态导入 3.1.2 可变参数函数 3.1.3 增强版for循环 3.1.4 基本数据的拆、装箱操作(autoboxing和unboxing) 3.2 枚举 3.2.1 枚举的实现原理 3.2.2 枚举的简单应用 3.2.3 枚举的高级特性 3.3 反射 3.3.1 反射的基石——Class类 3.3.2 反射的基本应用 3.3.3 反射的高级应用 3.4 标注 3.4.1 标注的简单使用 3.4.2 JDK的内置标注 3.5 泛型 3.5.1 为什么要使用泛型 3.5.2 泛型的一些特性 3.5.3 泛型的通配符 3.6 类加载器 3.6.1 什么是类加载器 3.6.2 什么是类加载器的委派模型 3.6.3 编写一个自己的加载器 3.7 动态代理 3.7.1 什么是代理 3.7.2 动态代理基础类 3.7.3 InvocationHandler接口 3.7.4 动态代理类的设计模式 3.8 小结 第2篇 线程开发 第4章 学生并发接水(线程Thread) 4.1 学生并发接水原理 4.1.1 项目结构框架分析 4.1.2 项目功能业务分析 4.2 不排队形式学生并发接水 4.2.1 水龙头类 4.2.2 学生类 4.2.3 测试类 4.3 学生并发接水的其他形式 4.3.1 “排队接水”水龙头类 4.3.2 “接完水后一起回教室”水龙头类 4.4 知识点扩展——线程的基础知识 4.4.1 为什么要使用线程 4.4.2 多线程程序的编写方式 4.5 小结 第5章 模拟做饭场景(线程的join()方法) 教学视频:7分钟5.1 做饭场景原理 5.1.1 项目结构框架分析 5.1.2 项目功能业务分析 5.2 纷乱的做饭场景 5.2.1 儿子的类 5.2.2 妈妈的类 5.2.3 做饭场景的类 5.2.4 修改后的妈妈类 5.3 知识点扩展——线程的状态 5.3.1 线程的创建状态 5.3.2 线程的暂停状态 5.3.3 线程的结束状态 5.4 小结 第6章 火车站售票系统(线程安全知识) 6.1 火车站售票系统原理 6.1.1 项目结构框架分析 6.1.2 项目功能业务分析 6.2 没有实现线程安全的火车票售票系统 6.2.1 火车票的类 6.2.2 售票台的类 6.2.3 实现线程安全的火车票售票系统 6.3 知识点扩展——线程的同步知识 6.3.1 为什么要使用同步机制 6.3.2 Synchronized的同步块 6.3.3 Synchronized的同步方法 6.3.4 死锁的问题 6.4 小结 第7章 生产者与消费者问题(线程通信知识) 7.1 生产者与消费者原理 7.1.1 项目结构框架分析 7.1.2 项目功能业务分析 7.2 无线程通信的生产者与消费者项目 7.2.1 生产者类 7.2.2 消费者类 7.2.3 储存库类 7.2.4 测试类 7.3 实现线程通信的生产者与消费者项目 7.3.1 生产者和消费者的类 7.3.2 储存库的类 7.4 知识点扩展——线程的通信知识 7.4.1 线程通信的基本知识 7.4.2 线程通信的具体实例 7.5 小结 第8章 关机工具(Timer类+系统命令) 8.1 关机工具原理 8.1.1 项目结构框架分析 8.1.2 项目功能业务分析 8.2 关机工具的实现过程 8.2.1 关机工具的类 8.2.2 关机工具的工具类 8.3 知识点扩展——关机工具项目涉及的知识 8.3.1 Timer和TimerTask类 8.3.2 shutdown命令 8.3.3 通过shutdown命令实现网络远程关机 8.4 小结 第3篇 GUI(图形用户界面)开发 第9章 典型的图形用户界面(各种组件) 9.1 Label和Button的用户界面 9.1.1 分析按钮和面板的用户界面 9.1.2 按钮和面板的用户界面 9.1.3 组件Button和Label的基本知识 9.2 复选框的用户界面 9.2.1 分析复选框的用户界面 9.2.2 按钮和面板的用户界面 9.2.3 组件Checkbox和CheckboxGroup的基本知识 9.3 下拉菜单和列表的用户界面 9.3.1 分析下拉菜单和列表的用户界面 9.3.2 下拉菜单和列表的用户界面 9.3.3 Choice和List组件的基本知识 9.4 输入的用户界面 9.4.1 分析输入的用户界面 9.4.2 输入的用户界面 9.4.3 TextField和TextArea组件的基本知识 9.5 滚动条的用户界面 9.5.1 分析滚动条的用户界面 9.5.2 滚动条的用户界面 9.5.3 滚动组件的基本知识 9.6 菜单的用户界面 9.6.1 分析菜单组件的用户界面 9.6.2 菜单的用户界面 9.6.3 菜单组件的基本知识 9.7 对话框的用户界面 9.7.1 分析对话框和文件对话框的用户界面 9.7.2 对话框的用户界面 9.7.3 Dialog和FileDialog组件的基本知识 9.8 小结 第10章 计算器(布局管理器) 10.1 计算器原理 10.1.1 项目结构框架分析 10.1.2 项目功能业务分析 10.2 计算器的实现过程 10.3 知识点扩展——事件机制的高级知识 10.3.1 为什么需要版面的配置 10.3.2 Java语言的各种布局管理器 10.4 小结 第11章 秒表(事件+线程) 11.1 秒表原理 11.1.1 项目结构框架分析 11.1.2 项目功能业务分析 11.2 秒表的实现过程 11.2.1 秒表类 11.2.2 测试秒表的类 11.3 知识点扩展——事件机制的基础知识 11.3.1 事件处理机制 11.3.2 Window事件 11.3.3 Mouse事件 11.3.4 Key事件 11.3.5 其他底层事件 11.3.6 事件的高级编写方法 11.4 小结 第12章 捉迷藏游戏(事件) 12.1 捉迷藏游戏原理 12.1.1 项目结构框架分析 12.1.2 项目功能业务分析 12.2 捉迷藏游戏的实现过程 12.2.1 捉迷藏游戏项目的原理 12.2.2 自定义按钮类 12.2.3 测试的类 12.3 知识点扩展——事件机制的高级知识 12.3.1 事件多重应用 12.3.2 事件处理的详细过程 12.4 小结 第13章 鼠标绘直线(绘图+事件) 13.1 鼠标绘直线原理 13.1.1 项目结构框架分析 13.1.2 项目功能业务分析 13.2 鼠标绘直线的实现过程 13.2.1 直线的类 13.2.2 实现窗口类——通过paint()方法 13.2.3 实现窗口类——通过双缓冲技术 13.3 知识点扩展——画图的基础知识 13.3.1 画图的基础知识 13.3.2 各种类型对象的绘制 13.4 小结 第14章 指针时钟项目(Swing组件+时间算法) 14.1 指针时钟原理 14.1.1 项目结构框架分析 14.1.2 项目功能业务分析 14.2 指针时钟的实现过程 14.2.1 指针时钟的界面 14.2.2 绘制指针时钟的类 14.3 知识点扩展——从AWT到Swing的过渡 14.3.1 窗口类JFrame 14.3.2 按钮类JButton和面板类JLabel 14.3.3 单选按钮和复选框组件 14.3.4 选择框组件 14.3.5 输入框组件 14.4 小结 第15章 控制动画项目 (JSlider和Timer组件) 15.1 控制动画原理 15.1.1 项目结构框架分析 15.1.2 项目功能业务分析 15.2 控制动画的实现过程 15.2.1 控制动画的主界面 15.2.2 控制动画的逻辑 15.3 知识点扩展——JSlider和Timer组件的基础知识 15.3.1 使用JSlider组件创建无刻度的滑杆 15.3.2 使用JSlider组件创建带数字刻度的滑杆 15.3.3 使用JSlider组件创建带字符刻度的滑杆 15.3.4 JSlider组件的高级应用 15.3.5 Swing的多线程 15.3.6 Timer组件的基础知识 15.3.7 Timer组件的应用 15.4 小结 第16章 记事本(对话框组件) 16.1 记事本原理 16.1.1 项目结构框架分析 16.1.2 项目功能业务分析 16.2 记事本的实现过程 16.2.1 实现记事本的界面 16.2.2 实现菜单功能 16.2.3 文件类型的过滤 16.3 记事本的实现过程——字体设置对话框 16.3.1 字体设置对话框——主界面 16.3.2 字体设置对话框——jPanel1组件界面 16.3.3 字体设置对话框——其他组件 16.4 小结 第17章 拼图游戏(GUI综合应用) 17.1 拼图游戏原理 17.1.1 项目结构框架分析 17.1.2 项目功能业务分析 17.1.3 拼图游戏项目的原理 17.2 拼图游戏的实现过程 17.2.1 实现移动功能的按钮类 17.2.2 主面板的类 17.2.3 主窗口的类 17.3 小结 第4篇 文件操作和访问 18.1 文件属性查看器原理 18.1.1 项目结构框架分析 18.1.2 项目功能业务分析 18.2 文件属性查看器项目 18.2.1 实现显示文件信息的自定义窗口 18.2.2 自定义窗口的显示 18.3 知识点扩展——文件的操作和访问 18.3.1 通过FileOp类实现文件创建和删除功能 18.3.2 通过FileDir类实现列举文件和目录的功能 18.3.3 File类提供的属性和方法 18.3.4 文件访问的基本概念 18.3.5 文件的基本访问方式——字节方式 18.3.6 文件的基本访问方式——字符方式 18.3.7 文件的高级访问方式 18.4 小结 第19章 文件内容查看器(GUI+文件访问) 19.1 文件内容查看器原理 19.1.1 项目结构框架分析 19.1.2 项目功能业务分析 19.2 文件内容查看器项目 19.2.1 设计项目的界面——文件内容查看器输入界面 19.2.2 “打开”菜单项的处理方法 19.2.3 单击列表选项的处理方法 19.3 知识点扩展——管道的访问 19.3.1 管道的访问——字节方式 19.3.2 管道的访问——字符方式 19.4 知识点扩展——内存的访问 19.4.1 内存的访问——字节方式 19.4.2 内存的访问——字符和字符串方式 19.5 小结 第20章 日记簿(GUI+文件访问和操作) 20.1 日记簿原理 20.1.1 项目结构框架分析 20.1.2 项目功能业务分析 20.2 日记簿项目 20.2.1 设计项目的界面——日记薄输入界面 20.2.2 “保存”按钮的事件处理 20.2.3 “查看日记”按钮的事件处理 20.2.4 设计项目的界面——日记列表界面 20.2.5 “查看”按钮的事件处理 20.2.6 “删除”按钮的事件处理 20.3 知识点扩展——过滤流的基础知识 20.3.1 过滤流的缓存(Buffering)类 20.3.2 过滤流实现字节和字符相互转换类 20.3.3 过滤流特定数据类型类 20.3.4 过滤流对象序列化类 20.3.5 过滤流打印类 20.4 小结 第21章 查找和替换项目(GUI+字符串处理) 21.1 查找和替换原理 21.1.1 项目结构框架分析 21.1.2 项目功能业务分析 21.2 查找和替换项目——利用AWT组件 21.2.1 设计项目的界面——查找和替换输入界面 21.2.2 各种按钮的事件处理 21.2.3 字符串处理的类 21.3 查找和替换项目——利用Swing组件 21.3.1 设计项目的界面——查找和替换输入界面 21.3.2 各种按钮的事件处理 21.4 小结 第5篇 Applet程序开发 第22章 图像轮显动画项目(显示图像+多线程) 22.1 图像轮显动画原理 22.1.1 项目结构框架分析 22.1.2 项目功能业务分析 22.2 图像轮显动画项目 22.3 知识点扩展——Applet程序的基础知识 22.3.1 Applet程序的执行过程 22.3.2 Applet程序的执行环境 22.3.3 Applet程序的输出 22.3.4 Applet程序的标记 22.3.5 参数的传递 22.3.6 Applet程序的相关方法 22.4 小结 第23章 Applet事件监听项目 (事件处理机制) 23.1 Applet事件监听原理 23.1.1 项目结构框架分析 23.1.2 项目功能业务分析 23.2 Applet事件监听项目 23.2.1 事件监听的类 23.2.2 承载事件监听的页面 23.3 知识点扩展——MyEclipse开发环境对Applet程序的支持 23.3.1 MyEclipse开发环境对Applet项目的支持 23.3.2 MyEclipse开发环境对JAR的支持 23.4 小结 第24章 动画播放项目(音频操作+多线程) 24.1 动画播放原理 24.1.1 项目结构框架分析 24.1.2 项目功能业务分析 24.2 动画播放项目 24.2.1 动画的类 24.2.2 控制动画的类 24.3 知识点扩展——Applet程序的高级知识 24.3.1 音频播放 24.3.2 Applet的上下文对象 24.4 小结 25.1 网络聊天室原理 25.1.1 项目结构框架分析 25.1.2 项目功能业务分析 25.2 网络聊天室的实现过程 25.3 知识点扩展——网络编程和UDP协议 25.3.1 网络编程涉及的基本概念 25.3.2 套接字(Socket)机制 25.3.3 UDP协议类 25.3.4 TCP协议类 25.3.5 TCP协议客户端类 25.4 小结 第26章 FTP服务器客户端 (FtpClient+I/O处理) 26.1 FTP服务器客户端原理 26.1.1 项目结构框架分析 26.1.2 项目功能业务分析 26.2 FTP服务器客户端的实现过程 26.2.1 FTP服务器操作的工具类 26.2.2 实现文件上传的类 26.2.3 实现文件下载的类 26.3 知识点扩展——FtpClient类的相关知识 26.3.1 实现FTP服务器相关操作类 26.3.2 相关JAR包导入问题 26.4 小结 第27章 Web服务器(HTTP协议) 27.1 Web服务器原理 27.1.1 项目结构框架分析 27.1.2 项目功能业务分析 27.2 Web服务器的实现过程 27.2.1 实现与浏览器通信的类 27.2.2 实现Web服务器的类 27.2.3 浏览器所请求的页面 27.3 知识点扩展——HTTP协议知识 27.3.1 HTTP协议原理 27.3.2 实现HTTP协议服务器的原理 27.4 小结 28.1 QQ聊天工具原理 28.1.1 项目结构框架分析 28.1.2 项目功能业务分析 28.2 QQ项目——对象模型的类 28.2.1 信息的类 28.2.2 “用户”的类 28.3 QQ项目——登录功能 28.3.1 QQ服务器界面的设计 28.3.2 QQ服务器后台程序 28.3.3 QQ客户端登录界面的设计 28.3.4 QQ客户端后台程序 28.3.5 成员列表窗口 28.4 QQ项目——聊天功能 28.4.1 服务器端的信息转发 28.4.2 客户端信息的发送和接收 28.4.3 客户端信息转发类 28.5 小结 第7篇 项目案例实战 第29章 人员信息管理项目 (接口设计模式+MySQL数据库) 29.1 人员信息管理原理 29.1.1 项目结构框架分析 29.1.2 项目功能业务分析 29.2 人员信息管理项目前期准备 29.2.1 设计数据库 29.2.2 数据库操作相关类 29.3 人员信息管理项目——DAO层 29.3.1 实现数据连接操作(DAO)的接口 29.3.2 实现数据连接操作(DAO)的实现类 29.3.3 实现数据连接操作(DAO)的代理类 29.3.4 实现数据连接操作(DAO)的工厂类 29.4 人员信息管理项目——服务层和表示层 29.4.1 人员信息管理项目的服务层 29.4.2 人员信息管理项目的表示层 29.4.3 工具类 29.5 人员信息管理项目——代理类测试 29.5.1 测试实现业务功能的各种方法 29.5.2 人员信息管理入口类 29.6 知识点扩展——设计模式的基础知识 29.6.1 工厂设计模式 29.6.2 代理设计模式 29.7 小结 第30章 国象棋游戏(GUI+游戏规则算法) 30.1 象棋游戏原理 30.1.1 象棋游戏的基本规则 30.1.2 项目结构框架分析 30.1.3 项目功能业务分析 30.2 象棋游戏项目——象棋游戏的主类 30.2.1 实现象棋游戏的主界面 30.2.2 实现象棋游戏添加棋子的功能 30.2.3 实现象棋游戏棋子闪烁的功能 30.2.4 处理单击棋子事件 30.2.5 处理单击按钮事件 30.3 象棋游戏项目——规则的内部类 30.3.1 实现卒移动和吃的方法 30.3.2 实现炮、车移动和吃的方法 30.3.3 实现马移动和吃的方法 30.3.4 实现象移动和吃的方法 30.3.5 实现士移动和吃的方法 30.3.6 实现将移动和吃的方法 30.4 小结 第31章 俄罗斯方块游戏网络版(Swing+多线程+网络编程) 31.1 俄罗斯方块游戏项目原理 31.1.1 基本原理 31.1.2 项目结构框架分析 31.1.3 项目功能业务分析 31.2 俄罗斯方块游戏项目——初步设计涉及的对象 31.2.1 正方形类 31.2.2 俄罗斯方块类 31.2.3 俄罗斯方块游戏项目的TOP10分数对象 31.3 俄罗斯方块游戏项目——服务器端和客户端 31.3.1 表示出俄罗斯方块游戏项目的服务器端 31.3.2 表示出俄罗斯方块游戏项目的客户端 31.4 俄罗斯方块游戏项目——游戏主界面 31.4.1 俄罗斯方块游戏的主界面 31.4.2 俄罗斯方块游戏的事件处理类 31.4.3 俄罗斯方块游戏的状态工具栏 31.5 俄罗斯方块游戏项目——其他界面的设计 31.5.1 “关于”面板 31.5.2 连接对方面板 31.5.3 分数报告面板 31.5.4 设置级别面板 31.5.5 警告面板和对话框 31.5.6 游戏结束面板和对话框 31.6 小结 第32章 图书管理系统项目 (GUI+Oracle数据库) 32.1 图书管理系统原理 32.1.1 项目结构框架分析 32.1.2 项目功能业务分析 32.2 图书管理系统项目——图书的操作 32.2.1 实现添加图书功能的类 32.2.2 实现修改图书功能的类 32.2.3 实现浏览图书信息的类 32.2.4 实现删除图书信息的类 32.3 图书管理系统项目——用户的操作 32.3.1 实现添加用户功能的类 32.3.2 实现删除用户功能的类 32.3.3 实现修改用户功能的类 32.3.4 实现用户登录功能的类 32.3.5 实现用户列表功能的类 32.4 图书管理系统项目——出借图书的操作 32.4.1 出借图书操作的类 32.4.2 借书列表方法 32.4.3 修改出借图书信息方法 32.5 图书管理系统项目——归还图书的操作 32.5.1 归还图书类 32.5.2 修改归还图书信息类 32.6 图书管理系统项目——该项目的其他类 32.6.1 主窗口类 32.6.2 数据库操作 32.7 小结
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值