读后笔记 -- Java核心技术(第11版 卷 II) Chapter9 Java 平台模块系统

9.1 模块的概念

一个平台模块包含:

  • 一个包集合;
  • 可选地包含资源文件和像本地库这样的其他文件;
  • 一个有关模块中可访问的包的列表;
  • 一个有关这个模块依赖的所有其他模块的列表;

平台模块系统的优点:

  • 1)强封装:可控制可访问的包,无须操心维护不想开放的代码;
  • 2)可靠的配置:可避免 类路径中常见的类重复或丢失问题;

 

模块化的相关文章参照:https://www.cnblogs.com/bruce-he/p/17443594.html 

 


9.2 对模块命名

1)模块名和包名可以完全相同,如 java.sql 模块包含 java.sql、javax.sql 和 javax.transaction.xa 这三个包;

2)模块之间无任何层次关系,如 com.horstmann 和 com.horstmann.corejava 没有关系;

 


9.3 模块化的 hello, world 程序

// HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello Modular World!");
    }
}

// module-info.java
@SuppressWarnings("module")
module v2ch09.hellomod {
}

编译:

 * 1. method 1: 生成一个新的文件夹 mods 保存相关的 class 文件
 *  1.1. switch to path v2ch09.hellomod\src
 *  1.2. javac -d mods\v2ch09.hellomod module-info.java com\bruce\hello\HelloWorld.java
 *  1.3. java -p mods -m v2ch09.hellomod/com.bruce.hello.HelloWorld
 *
 * 2. method 2: 直接在原相关路径上产生 class 文件
 *  2.1 switch to path root path lessonlearn_coreJava
 *  2.2 run "javac v2ch09.hellomod\src\module-info.java v2ch09.hellomod\src\com\bruce\HelloWorld.java"
 *  2.3 run "java -p v2ch09.hellomod -m v2ch09.hellomod/com.bruce.hello.HelloWorld"
 *
 *  Note: Must use "/" in step3, not "\"
 */

 


9.4 对模块的需求

结构:

代码:

// HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        JOptionPane.showMessageDialog(null, "Hello Modular World!");
    }
}

// module-info.java
module v2ch09.requiremod {
}

编译:

// 编译:
// switch to path v2ch09.requiremod\src
> javac -d mods\v2ch09.requiremod module-info.java com\bruce\hello\HelloWorld.java

// error message:
error: package javax.swing is not visible
    (package javax.swing is declared in module java.desktop, but module v2ch09.requiremod does not read it)

// root cause:
code 需要加载 "java.desktop",如果在 module-info.java 中加上 "requires java.desktop" 编译即可通过。section 9.3 hellomod 的 module-info.java 没有 require 任何内容,是其默认加载了 "reuqires java.base"

Swing 应用程序的模块图:

   (通过查看 java.desktop,可以看出 其requires 3个包,并 exports 哪些包可用)

 


9.5 导出包

module java.xml {
    exports javax.xml;
    exports javax.xml.catalog;
    exports javax.xml.datatype;
    ...
}

包在模块中被导出(exports)的规则:

  • 1)包被导出:public 和 protected 的类和接口,以及 public 和 protected 的成员,在模块外部可以访问(protected 的类型和成员只有在子类中是可访问的);
  • 2)没有导出的包:模块外不可访问(这与模块化之前很不相同);

 

案例:

 

// Greeter.java
package com.horstmann.greet.internal;

import com.horstmann.greet.Greeter;

public interface Greeter {
    static Greeter newInstance() {
        return new com.horstmann.greet.internal.GreeterImpl();
    }

    String greet(String subject);
}

// GreeterImpl.java
package com.horstmann.greet;

public class GreeterImpl implements Greeter {
    @Override
    public String greet(String subject) {
        return "Hello, " + subject + "!";
    }
}

// com.horstmann.greet\src\module-info.java
module com.horstmann.greet {
     exports com.horstmann.greet;
}

// HelloWorld.java
package com.horstmann.hello;

import com.horstmann.greet.Greeter;

public class HelloWorld {
    public static void main(String[] args) {
        Greeter greeter = Greeter.newInstance();
        System.out.println(greeter.greet("Modular World"));
    }
}

// v2ch09.exportedpkg\src\module-info.java,根据提示,需要将 com.horstmann.greet 作为依赖加载
module v2ch09.exportedpkg {
     requires com.horstmann.greet;
}

编译过程:

step1: compile com.horstmann.greet module
  1) switch to C:\Users\xxx\IdeaProjects\trunk\lessonlearn_coreJava
  2) run "javac -d .\com.horstmann.greet\mods com.horstmann.greet\src\module-info.java \
       com.horstmann.greet\src\com\horstmann\greet\Greeter.java \
       com.horstmann.greet\src\com\horstmann\greet\internal\GreeterImpl.java"
      // 此步是指定 com.horstmann.greet 的类文件输出,在 step2 时,它需要从该目录获取类文件结构

step2: compile HelloWorld.java with last module
  1) run with "javac -encoding UTF-8 -p com.horstmann.greet\mods v2ch09.exportedpkg\src\module-info.java v2ch09.exportedpkg\src\com\horstmann\hello\HelloWorld.java"

step3: run this program with last modules。注意 红色的符合部分
  1) run with "java -p v2ch09.exportedpkg;com.horstmann.greet\mods -m v2ch09.exportedpkg/com.horstmann.hello.HelloWorld"

 


9.6 模块化的 JAR

将两个模块都编译成 jar 包,然后运行

1)按照 https://www.cnblogs.com/bruce-he/p/17219451.html 生成 com.horstmann.greet.jar

2)在 v2ch09.exportedpkg 中,就可以将生成的 com.horstmann.greet.jar 作为依赖

3)按照步骤1)的方式,生成 v2ch09.exportedpkg.jar

4)cmd 下运行下面命令

java -p C:\Users\xxx\IdeaProjects\trunk\lessonlearn_coreJava\out\artifacts\com_horstmann_greet\com.horstmann.greet.jar;C:\Users\xxx\IdeaProjects\trunk\lessonlearn_coreJava\out\artifacts\v2ch09_exportedpkg\v2ch09.exportedpkg.jar -m v2ch09.exportedpkg/com.horstmann.hello.HelloWorld

注意:

-m 后面必须指定包名下面的主函数,否则将报 “v2ch09.exportedpkg 不具有 ModuleMainClass 属性”

 


9.7 模块和反射式访问

1. 之前非模块结构,访问私有域的方式:

Field f = obj.getClass().getDeclaredField("salary");
f.setAccessible(true);
double value = f.getDouble(obj);
f.setDouble(obj, value * 1.1);

 

2. 模块化访问私有域,将有所不同

1)结构:

    

2)具体代码:

// Country.java
package com.bruce.places;

public class Country {
    private String name;
    private double area;

    public Country(String name, double area) {
        this.name = name;
        this.area = area;
    }

    public String getName() { return name; }
    public double getArea() {  return area;  }
}

// Demo.java
package com.bruce.places;
import com.bruce.util.ObjectAnalyzer;

public class Demo {
    public static void main(String[] args) {
        var belgium = new Country("Belgium", 30510);
        var analyzer = new ObjectAnalyzer();
        System.out.println(analyzer.toString(belgium));
    }
}

// v2ch09.openpkg\module-info.java
@SuppressWarning("module") module v2ch09.openpkg { requires com.bruce.util; } // com.bruce.util\module-info.java module com.bruce.util { exports com.bruce.util; }

3)编译并运行

javac -d .\com.bruce.util\mods com.bruce.util\src\module-info.java com.bruce.util\src\com\bruce\util\ObjectAnalyzer.java

javac -p com.bruce.util\mods v2ch09.openpkg\src\module-info.java v2ch09.openpkg\src\com\bruce\places\*.java

java -p v2ch09.openpkg;com.bruce.util\mods -m v2ch09.openpkg/com.bruce.places.Demo

提示错误信息:

Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private java.lang.String com.bruce.places.Country.name accessible: module v2ch09.openpkg does not 
"opens com.bruce.places" to module com.bruce.util

4)解决方案:

// 修改 v2ch09.openpkg\module-info.java
@SuppressWarnings("module")
module v2ch09.openpkg {
    requires com.bruce.util;
opens com.bruce.places; }
// 重新编译步骤3)的后2步。opens 放在具体的方法前面,意味着仅该包对外开放,如果放在 module 前,则该模块的所有包都对外开放。

 


9.8 自动模块

模块化兼容以前的 JAR 包 的方案:

  • 1)自动模块;
  • 2)不具名模块;

 

自动模块:模块路径上没有 module-info.class 文件的 JAR。其具有如下属性:

  • 1)模块隐式地包含对其他所有模块的 requires 子句;
  • 2)其所有包都被导出,且是开放的;
  • 3)如果在 JAR 文件清单 META-INF/MANIFEST.MF 中具有键为 Automatic-Module-Name 的项,那么它的值就会变为模块名;
  • 4)否则,模块名将从 JAR 文件中获得,将文件名中尾部的版本号删除,并将非字母数字的字符替换为句点;

 

结构:

 用例:

// module-info.java
@SuppressWarnings("module")
module v2ch09.automod {
     requires org.apache.commons.csv;
}

// CSVDemo.java
public class CSVDemo {
    public static void main(String[] args) throws IOException {
        var in = new FileReader("./v2ch09.automod/countries.csv");
        // withDelimiter(';') 必须使用 单引号,并且 throws IOException
        Iterable<CSVRecord> records = CSVFormat.EXCEL.withDelimiter(';').withHeader().parse(in);
        for (CSVRecord record : records) {
            String name = record.get("Name");
            double area = Double.parseDouble(record.get("Area"));
            System.out.println(name + " has area " + area);
        }
    }
}

编译:

1. 解释:
-- 1.1 模块路径:当前 v2ch09.automod\ 为模块路径
-- 1.2 类路径: 当前 v2ch09.automod\src\ 为类路径的一部分 (可以通过 System.out.println("System.getProperty("classpath")"); 打印当前的类路径)

2. 对比课本 section 9.8 自动模块的差异:
-- 2.1 去掉 File -> Project Structure\Module\dependencies\ 下面的 jar (这样就去掉了 v2ch09.automod.iml 里面的 指定 jar 路径)
-- 2.2 将 commons-csv-1.10.0.jar 放在 v2ch09.automod\ 根目录下面
-- 2.3 以下在 cmd 下面运行(在 IDE 里面看不到区别)
-- 2.3.1 > 进入 目录 lessonlearn_corejava>
-- 2.3.2 > javac -p v2ch09.automod;commons-csv-1.10.0.jar v2ch09.automod\src\com\bruce\places\CSVDemo.java v2ch09.automod\src\module-info.java
-- 2.3.3 > java -p v2ch09.automod;commons-csv-1.10.0.jar -m v2ch09.automod/com.bruce.places.CSVDemo

对比:
将 commons-csv-1.10.0.jar 放在类路径 v2ch09.automod\src 下面时,再运行 2.3.2 将会提示 找不到模块 org.apache.commons.csv

另外:
1. IDE 中,因为不会指定 -p,所以必须通过添加外部依赖的方式。这样做,不论 jar 放在 模块路径或类路径,效果都一样
2. 区分模块路径和类路径的目的:将模块路径下面的第三方 jar 不用放在 编译后形成的 类一个目录下面。如果 .jar 包放在类路径,将和编译后的 .class 放置在一个目录,不容易管理

 


9.9 不具名模块

模块的几种类型:

  • 1)自动模块(section 9.8):JAR 包在模块路径上,但没有 module-info.class 文件;
  • 2)不具名模块(section 9.9):任何不在模块路径中的类都是不具名模块的一部分;
  • 3)明确模块:JAR 包在模块路径,且有 module-info.class;

 

不具名模块的特性:

  • 1)可以访问所有其他模块的模块;
  • 2)所有包都会被导出,并且开放;

 


9.10 用于迁移的命令行标识

非法的模块访问的权限设置:

  • --illegal-access=permit:Java 9 的默认行为,每一种非法访问第一次出现时打印一条消息;
  • --illegal-access=warn:Java 11 的默认行为,对每次非法访问都打印一条消息;
  • --illegal-access=debug:每次非法访问都打印一条消息和栈轨迹;
  • --illegal-access=deny:未来的默认行为,直接拒绝所有非法访问;

 

案例:一个应用程序使用了不再能继续访问的内部 API,而且不能访问源代码

解决方案:用 --add-exports 标志启动该应用程序,指定希望导出的模块和包,以及将包导出的模块。

java --illegal-access=deny --add-exports java.sql.rowset/com.sun.rowset=ALL_UNNAMED -jar MyApp.jar    // 该例中,将其导出到了不具名模块中,这样其他应用程序就可以访问

 


9.11 传递的需求和静态的需求

1)传递的需求:requires transitive

module javafx.controls {
    requires transitive javafx.base;
    ...
}
// 任何声明需要 javafx.controls 的模块都自动地需要 javafx.base

2)静态的需求:requires static

  它声明一个模块必须在编译时出现,而在运行时是可选的。

     (对比 requires:编译和运行时都需要的模块

 


9.12 限定导出和开放

1)限定导出 (exports ... to):所列的模块可以访问这个包,其他的模块不行

exports com.sun.javafx.collections to javafx.controls, javafx.graphics, javafx.fxml, javafx.swing;

 

2)限定开放 (opens.. to):包仅对列出的模块开放

module v2ch09.openpkg {
    requires com.horstmann.util;
    opens com.horstmann.places to com.horstmann.util;
}

 

exports 和 open 的更详细的用途及区别:详见: https://www.cnblogs.com/bruce-he/p/17445845.html

 


9.13 服务加载

使用场景:

1). 模块中有一个接口;
2). 有一个或多个模块实现了接口;

需求:对外提供服务

实现方案:

1). 服务提供者 在 module-info.java 中,使用 
    "exports xxxserviceModule;"
    "provides xxxservice with xxImpl;" 
    向外提供服务;
2). 服务消费者 在 module-info.java 中,使用( provides 和 uses 的声明,使得消费该服务的模块允许访问私有实现类 "requires xxxserviceModule;"
     "uses xxxservice;"
3). 服务消费者使用 ServiceLoader 来加载服务类

结构:

     

代码实现:

// GreeterService
package com.horstmann.greetsvc;

import java.util.Locale;

public interface GreeterService {
    String greet(String subject);
    Locale getLocale();
}

// FrenchGreeter
package com.horstmann.greetsvc.internal;

import com.horstmann.greetsvc.GreeterService;

import java.util.Locale;

public class FrenchGreeter implements GreeterService {

    @Override
    public String greet(String subject) {
        return "Bonjour" + subject;
    }

    @Override
    public Locale getLocale() {
        return Locale.FRENCH;
    }
}

// com.horstmann.greetsvc/src/module-info.java
module com.horstmann.greetsvc {
     exports com.horstmann.greetsvc;

     provides com.horstmann.greetsvc.GreeterService with
             com.horstmann.greetsvc.internal.FrenchGreeter;
}

// HelloWorld.java
package com.horstmann.hello;

import java.util.*;
import com.horstmann.greetsvc.*;

public class HelloWorld {
    public static void main(String[] args) {
        ServiceLoader<GreeterService> greeterLoader = ServiceLoader.load(GreeterService.class);
        String desiredLanguage = "fr";
        GreeterService chosenGreeter = null;
        for (GreeterService greeter : greeterLoader) {
            if (greeter.getLocale().getLanguage().equals(desiredLanguage))
                chosenGreeter = greeter;
        }

        if (chosenGreeter == null)
            System.out.println("No suitable greeter.");
        else
            System.out.println(chosenGreeter.greet("Modular World"));
    }
}

// v2ch09.useservice\src\module-info.java
@SuppressWarnings("module")
module v2ch09.useservice {
     requires com.horstmann.greetsvc;
     uses com.horstmann.greetsvc.GreeterService;
}

编译运行:

javac -d com.horstmann.greetsvc\mods com.horstmann.greetsvc\src\module-info.java com.horstmann.greetsvc\src\com\horstmann\greetsvc\GreeterService.java \
  com.horstmann.greetsvc\src\com\horstmann\greetsvc\internal\*.java javac -p com.horstmann.greetsvc\mods v2ch09.useservice\src\com\horstmann\hello\HelloWorld.java v2ch09.useservice\src\module-info.java java -p com.horstmann.greetsvc\mods;v2ch09.useservice -m v2ch09.useservice/com.horstmann.hello.HelloWorld

 


9.14 操作模块的工具

1. jdeps 工具可以分析给定的 JAR 文件集之间的依赖关系。

jdeps -s junit-4.13.jar hamcrest-core-1.3.jar

 

// --generate-module-info :对每个分析过的模块产生 module-info 文件
jdeps --generate-module-info d:\temp\junit junit-4.13.jar hamcrest-core-1.3.jar

 

2. jlink 工具可以产生执行时无须单独的 Java 运行时环境的应用程序。

> jlink --module-path com.horstmann.greet.jar;v2ch09s.exportedpkg.jar;$JAVA_HOME/jmods --add-modules v2ch09s.exportedpkg --output e:\temp\hello

 

3. JMOD 文件

  与 JAR 文件不同,JMOD 文件(..\jdk-17\jmods)只有在链接时才有用,即只有在产生运行时镜像时才有用。

 

posted on 2023-05-26 17:57  bruce_he  阅读(92)  评论(0编辑  收藏  举报