java · 2022-08-31 0

java创建jmod并将jmod打包成jre

一、编写java文件

Greeting.java

package com.greeting;

public class Greeting {
    public boolean hello(String xmlNsPrefix) {
        return false;
    }
}

Main.java

package com.main;

import com.greeting.Greeting;

import javax.xml.XMLConstants;

public class Main {

    public static void main(String[] args) {
        Greeting g = new Greeting();
        System.out.println(g.hello(XMLConstants.XML_NS_PREFIX));
    }
}

module-info.java

module hello.world {
    requires java.base; // 可不写,任何模块都会自动引入java.base
    requires java.xml;
}

三个java文件放在 /home/zxm/testjmod 下,再新建 foo1 目录,用于存放编译生成的 class 文件

目录结构:

foo1/
Greeting.java
Main.java
module-info.java

二、编译文件

java版本:

zxm@zxm-pc:~/testjmod$ java -version
java version "11.0.16.1" 2022-08-18 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.16.1+1-LTS-1)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.16.1+1-LTS-1, mixed mode)

java命令使用:

zxm@zxm-pc:~/testjmod$ java -help
用法:java [options] <主类> [args...]
           (执行类)
   或  java [options] -jar <jar 文件> [args...]
           (执行 jar 文件)
   或  java [options] -m <模块>[/<主类>] [args...]
       java [options] --module <模块>[/<主类>] [args...]
           (执行模块中的主类)
   或  java [options] <源文件> [args]
           (执行单个源文件程序)

 将主类、源文件、-jar <jar 文件>、-m 或
 --module <模块>/<主类> 后的参数作为参数
 传递到主类。

 其中,选项包括:

    -cp <目录和 zip/jar 文件的类搜索路径>
    -classpath <目录和 zip/jar 文件的类搜索路径>
    --class-path <目录和 zip/jar 文件的类搜索路径>
                  使用 : 分隔的, 用于搜索类文件的目录, JAR 档案
                  和 ZIP 档案列表。
    -p <模块路径>
    --module-path <模块路径>...
                  用 : 分隔的目录列表, 每个目录
                  都是一个包含模块的目录。
    --upgrade-module-path <模块路径>...
                  用 : 分隔的目录列表, 每个目录
                  都是一个包含模块的目录, 这些模块
                  用于替换运行时映像中的可升级模块
    --add-modules <模块名称>[,<模块名称>...]
                  除了初始模块之外要解析的根模块。
                  <模块名称> 还可以为 ALL-DEFAULT, ALL-SYSTEM,
                  ALL-MODULE-PATH.
    --list-modules
                  列出可观察模块并退出
    -d <module name>
    --describe-module <模块名称>
                  描述模块并退出
    --dry-run     创建 VM 并加载主类, 但不执行 main 方法。
                  此 --dry-run 选项对于验证诸如
                  模块系统配置这样的命令行选项可能非常有用。
    --validate-modules
                  验证所有模块并退出
                  --validate-modules 选项对于查找
                  模块路径中模块的冲突及其他错误可能非常有用。
    -D<名称>=<值>
                  设置系统属性
    -verbose:[class|module|gc|jni]
                  启用详细输出
    -version      将产品版本输出到错误流并退出
    --version     将产品版本输出到输出流并退出
    -showversion  将产品版本输出到错误流并继续
    --show-version
                  将产品版本输出到输出流并继续
    --show-module-resolution
                  在启动过程中显示模块解析输出
    -? -h -help
                  将此帮助消息输出到错误流
    --help        将此帮助消息输出到输出流
    -X            将额外选项的帮助输出到错误流
    --help-extra  将额外选项的帮助输出到输出流
    -ea[:<程序包名称>...|:<类名>]
    -enableassertions[:<程序包名称>...|:<类名>]
                  按指定的粒度启用断言
    -da[:<程序包名称>...|:<类名>]
    -disableassertions[:<程序包名称>...|:<类名>]
                  按指定的粒度禁用断言
    -esa | -enablesystemassertions
                  启用系统断言
    -dsa | -disablesystemassertions
                  禁用系统断言
    -agentlib:<库名>[=<选项>]
                  加载本机代理库 <库名>, 例如 -agentlib:jdwp
                  另请参阅 -agentlib:jdwp=help
    -agentpath:<路径名>[=<选项>]
                  按完整路径名加载本机代理库
    -javaagent:<jar 路径>[=<选项>]
                  加载 Java 编程语言代理, 请参阅 java.lang.instrument
    -splash:<图像路径>
                  使用指定的图像显示启动屏幕
                  自动支持和使用 HiDPI 缩放图像
                  (如果可用)。应始终将未缩放的图像文件名 (例如, image.ext)
                  作为参数传递给 -splash 选项。
                  将自动选取提供的最合适的缩放
                  图像。
                  有关详细信息, 请参阅 SplashScreen API 文档
    @argument 文件
                  一个或多个包含选项的参数文件
    -disable-@files
                  阻止进一步扩展参数文件
    --enable-preview
                  允许类依赖于此发行版的预览功能
要为长选项指定参数, 可以使用 --<名称>=<值> 或
--<名称> <值>。

javac命令使用:

zxm@zxm-pc:~/testjmod$ javac --help
用法: javac <options> <source files>
其中, 可能的选项包括:
  @<filename>                  从文件读取选项和文件名
  -Akey[=value]                传递给注释处理程序的选项
  --add-modules <模块>(,<模块>)*
        除了初始模块之外要解析的根模块; 如果 <module>
                为 ALL-MODULE-PATH, 则为模块路径中的所有模块。
  --boot-class-path <path>, -bootclasspath <path>
        覆盖引导类文件的位置
  --class-path <path>, -classpath <path>, -cp <path>
        指定查找用户类文件和注释处理程序的位置
  -d <directory>               指定放置生成的类文件的位置
  -deprecation                 输出使用已过时的 API 的源位置
  --enable-preview             启用预览语言功能。要与 -source 或 --release 一起使用。
  -encoding <encoding>         指定源文件使用的字符编码
  -endorseddirs <dirs>         覆盖签名的标准路径的位置
  -extdirs <dirs>              覆盖所安装扩展的位置
  -g                           生成所有调试信息
  -g:{lines,vars,source}       只生成某些调试信息
  -g:none                      不生成任何调试信息
  -h <directory>               指定放置生成的本机标头文件的位置
  --help, -help, -?            输出此帮助消息
  --help-extra, -X             输出额外选项的帮助
  -implicit:{none,class}       指定是否为隐式引用文件生成类文件
  -J<flag>                     直接将 <标记> 传递给运行时系统
  --limit-modules <模块>(,<模块>)*
        限制可观察模块的领域
  --module <module-name>, -m <module-name>
        只编译指定的模块, 请检查时间戳
  --module-path <path>, -p <path>
        指定查找应用程序模块的位置
  --module-source-path <module-source-path>
        指定查找多个模块的输入源文件的位置
  --module-version <版本>        指定正在编译的模块版本
  -nowarn                      不生成任何警告
  -parameters                  生成元数据以用于方法参数的反射
  -proc:{none,only}            控制是否执行注释处理和/或编译。
  -processor <class1>[,<class2>,<class3>...]
        要运行的注释处理程序的名称; 绕过默认的搜索进程
  --processor-module-path <path>
        指定查找注释处理程序的模块路径
  --processor-path <path>, -processorpath <path>
        指定查找注释处理程序的位置
  -profile <profile>           请确保使用的 API 在指定的配置文件中可用
  --release <release>          针对特定 VM 版本进行编译。支持的目标: 6, 7, 8, 9, 10, 11
  -s <directory>               指定放置生成的源文件的位置
  -source <release>            提供与指定发行版的源兼容性
  --source-path <path>, -sourcepath <path>
        指定查找输入源文件的位置
  --system <jdk>|none          覆盖系统模块位置
  -target <release>            生成特定 VM 版本的类文件
  --upgrade-module-path <path>
        覆盖可升级模块位置
  -verbose                     输出有关编译器正在执行的操作的消息
  --version, -version          版本信息
  -Werror                      出现警告时终止编译

java8 使用 --classpath,而模块化后使用的 --module-path

另:
可以向低版本编译,例,javac -d foo1/ Test1.java Test2.java Test.java -encoding UTF-8 -source 7 -target 7javac -d foo1/ Test1.java Test2.java Test.java -encoding UTF-8 --release 7

虽然说 Java 的向后兼容做得非常非常好, 但是也不是100%兼容,特别是高版本没有 javax 包。
比如,源代码使用 javax.xml.bind.JAXBException 类,使用 jdk11 编译成 jdk7,会编译失败

--release 参数是 jdk 9 开始支持的

使用 java 命令运行程序

zxm@zxm-pc:~/testjmod$ mkdir foo1
zxm@zxm-pc:~/testjmod$ javac -d foo1/ module-info.java Greeting.java Main.java
zxm@zxm-pc:~/testjmod$ java -cp foo1/ com.main.Main
false
zxm@zxm-pc:~/testjmod$ java -p foo1 -m hello.world/com.main.Main
false
zxm@zxm-pc:~/testjmod$ java --module-path foo1 -m hello.world/com.main.Main
false

目录结构:

foo1
    com
        greeting
            Greeting.class
        main
            Main.class
    module-info.class
Greeting.java
Main.java
module-info.java

三、创建 jmod

jar 命令使用

zxm@zxm-pc:~/testjmod$ jar --help
用法: jar [OPTION...] [ [--release VERSION] [-C dir] files] ...
jar 创建类和资源的档案, 并且可以处理档案中的
单个类或资源或者从档案中还原单个类或资源。

 示例:
 # 创建包含两个类文件的名为 classes.jar 的档案:
 jar --create --file classes.jar Foo.class Bar.class
 # 使用现有的清单创建档案, 其中包含 foo/ 中的所有文件:
 jar --create --file classes.jar --manifest mymanifest -C foo/ .
 # 创建模块化 jar 档案, 其中模块描述符位于
 # classes/module-info.class:
 jar --create --file foo.jar --main-class com.foo.Main --module-version 1.0
     -C foo/ classes resources
 # 将现有的非模块化 jar 更新为模块化 jar:
 jar --update --file foo.jar --main-class com.foo.Main --module-version 1.0
     -C foo/ module-info.class
 # 创建包含多个发行版的 jar, 并将一些文件放在 META-INF/versions/9 目录中:
 jar --create --file mr.jar -C foo classes --release 9 -C foo9 classes

要缩短或简化 jar 命令, 可以在单独的文本文件中指定参数,
并使用 @ 符号作为前缀将此文件传递给 jar 命令。

 示例:
 # 从文件 classes.list 读取附加选项和类文件列表
 jar --create --file my.jar @classes.list

 主操作模式:

  -c, --create               创建档案
  -i, --generate-index=FILE  为指定的 jar 档案生成
                             索引信息
  -t, --list                 列出档案的目录
  -u, --update               更新现有 jar 档案
  -x, --extract              从档案中提取指定的 (或全部) 文件
  -d, --describe-module      输出模块描述符或自动模块名称

 在任意模式下有效的操作修饰符:

  -C DIR                     更改为指定的目录并包含
                             以下文件
  -f, --file=FILE            档案文件名。省略时, 基于操作
                             使用 stdin 或 stdout
      --release VERSION      将下面的所有文件都放在
                             jar 的版本化目录中 (即 META-INF/versions/VERSION/)
  -v, --verbose              在标准输出中生成详细输出

 在创建和更新模式下有效的操作修饰符:

  -e, --main-class=CLASSNAME 捆绑到模块化或可执行 
                             jar 档案的独立应用程序
                             的应用程序入口点
  -m, --manifest=FILE        包含指定清单文件中的
                             清单信息
  -M, --no-manifest          不为条目创建清单文件
      --module-version=VERSION    创建模块化 jar 或更新
                             非模块化 jar 时的模块版本
      --hash-modules=PATTERN 计算和记录模块的散列, 
                             这些模块按指定模式匹配并直接或
                             间接依赖于所创建的模块化 jar 或
                             所更新的非模块化 jar
  -p, --module-path          模块被依赖对象的位置, 用于生成
                             散列

 只在创建, 更新和生成索引模式下有效的操作修饰符:

  -0, --no-compress          仅存储; 不使用 ZIP 压缩

 其他选项:

  -?, -h, --help[:compat]    提供此帮助,也可以选择性地提供兼容性帮助
      --help-extra           提供额外选项的帮助
      --version              输出程序版本

 如果模块描述符 'module-info.class' 位于指定目录的
 根目录中, 或者位于 jar 档案本身的根目录中, 则
 该档案是一个模块化 jar。以下操作只在创建模块化 jar,
 或更新现有的非模块化 jar 时有效: '--module-version',
 '--hash-modules' 和 '--module-path'。

 如果为长选项提供了必需参数或可选参数, 则它们对于
 任何对应的短选项也是必需或可选的。

jmod 命令使用

zxm@zxm-pc:~/testjmod$ jmod --help
用法: jmod (create|extract|list|describe|hash) <选项> <jmod 文件>

主操作模式:
  create    - 创建新的 jmod 档案
  extract   - 从档案中提取所有文件
  list      - 输出所有条目的名称
  describe  - 输出模块详细信息
  hash      - 记录绑定模块的散列。

 Option                              Description                            
 ------                              -----------                            
  -?, -h, --help                      输出此帮助消息                                
  --class-path <path>                 包含类的应用程序 jar 文件|目录                     
  --cmds <path>                       本机命令的位置                                
  --config <path>                     用户可编辑配置文件的位置                           
  --dir <path>                        提取操作的目标目录                              
  --dry-run                           散列模式的模拟运行                              
  --exclude <pattern-list>            排除与所提供逗号分隔的模式列表匹配的文件, 每个元素使用以下格式之一:    
                                        <glob 模式>, glob:<glob 模式> 或 regex:<正则
                                        表达式模式>                               
  --hash-modules <regex-pattern>      计算和记录散列, 以将打包模块绑定到与指定 <正则表达式模式> 匹配并直接或间
                                        接依赖于的模块。散列记录在所创建的 JMOD 文件中, 或者记录在    
                                        jmod hash 命令指定的模块路径的 JMOD 文件或模块化 JAR 
                                        中。                                   
  --header-files <path>               标头文件的位置                                
  --help-extra                        输出额外选项的帮助                              
  --legal-notices <path>              法律声明位置                                 
  --libs <path>                       本机库的位置                                 
  --main-class <String: class-name>   主类                                     
  --man-pages <path>                  帮助页的位置                                 
  --module-version <module-version>   模块版本                                   
  -p, --module-path <path>            模块路径                                   
  --target-platform <String: target-  目标平台                                   
    platform>                                                                
  --version                           版本信息                                   
  @<filename>                         从指定文件读取选项

两种方式,第一种,根据生成的 class 文件,先生成 jar 包,再生成 jmod;第二种,根据生成的 class 文件,直接生成 jmod

可指定主类

使用 jar -d -f <jar_file> 可判断一个 jar 包,是不是支持模块化

# zxm@zxm-pc:~/testjmod$ jar --create --file hello.jar --main-class com.main.Main -C foo1/ .
# zxm@zxm-pc:~/testjmod$ jmod create --class-path hello.jar hello.jmod
# zxm@zxm-pc:~/testjmod$ jmod list hello.jmod
# classes/module-info.class
# classes/META-INF/MANIFEST.MF
# classes/com/main/Main.class
# classes/com/greeting/Greeting.class
# zxm@zxm-pc:~/testjmod$ jmod create --class-path foo1/ --main-class com.main.Main hello.jmod
# zxm@zxm-pc:~/testjmod$ jar -d -f hello.jar 
# hello.world jar:file:///home/zxm/testjmod/hello.jar/!module-info.class
# requires java.base
# requires java.xml
# contains com.greeting
# contains com.main
# main-class com.main.Main
zxm@zxm-pc:~/testjmod$ jmod create --class-path foo1/ hello.jmod
zxm@zxm-pc:~/testjmod$ jmod list hello.jmod
classes/module-info.class
classes/com/main/Main.class
classes/com/greeting/Greeting.class

四、运行 jmod

zxm@zxm-pc:~/testjmod$ java --module-path hello.jar --module hello.world
false

五、打包 jre 并运行

如果 jmod 中的 MANIFEST.MF 指定 Main-Class,则可使用 jre/bin/java --module hello.world 运行,否则出现 模块 hello.world 不具有 ModuleMainClass 属性,请使用 -m <模块>/<主类>;如果 jmod 中的 MANIFEST.MF 没有指定 Main-Class,则可使用 jre/bin/java --module hello.world/com.main.Main

zxm@zxm-pc:~/testjmod$ jlink --module-path hello.jmod --add-modules java.base,java.xml,hello.world --output jre/
# zxm@zxm-pc:~/testjmod$ jre/bin/java --module hello.world
# false
zxm@zxm-pc:~/testjmod$ jre/bin/java --module hello.world/com.main.Main
false

java --list-modules:获取所有可用 jdk 模块的列表

jlink --no-header-files --no-man-pages --compress=2 --add-modules <module-list from step 1> --output java-runtime:创建一个精简的JRE

jlink--module-path 等效于类路径,告诉JVM在哪里搜索模块,--add-modules 这指定要解决的其他根模块