Java 9 模块化系统(Project Jigsaw)深度解析

1. 引言

1.1 什么是 Project Jigsaw?

Project Jigsaw 是 Java 9 引入的一项重要特性,其核心是将 Java 平台引入模块化系统。这项特性最早由 Oracle 于 JSR 376 提出,旨在解决 Java 平台和大型应用程序架构中的一系列结构性问题。模块系统是对 Java 类加载器机制和访问控制模型的系统性扩展,它不仅影响开发者编写代码的方式,还改变了平台的打包、部署和运行方式。

简而言之,Project Jigsaw 为 Java 引入了新的构建单元——模块(module),用于组织代码和资源,并显式声明依赖关系和可访问性边界。

Java 模块系统的核心组成部分是模块描述文件 module-info.java,它定义了模块对外公开的包、依赖的其他模块、使用的服务等。这种方式替代了传统的隐式依赖、扁平类路径模式,使得系统的边界更清晰、耦合度更低、启动更高效。

1.2 模块化系统的好处

引入模块系统之后,Java 平台和开发者可以享受以下诸多优势:

更强的封装性

模块系统允许模块显式声明哪些包对外公开(exports),哪些包是模块内部的实现细节。这极大提升了封装性,避免了以往所有类都能被任意访问的局面。

更清晰的依赖管理

通过 requires 语句,模块可以声明依赖的其他模块,Java 编译器和运行时可以根据这些信息检测缺失依赖、冲突、循环依赖等问题,从而提升构建的健壮性。

启动时间更快、运行效率更高

JVM 可以基于模块信息进行更精准的类加载和优化,从而提升启动时间和运行性能。JLink 等工具也可以基于模块化信息生成更小巧的运行时镜像。

安全性提升

模块强封装机制能够有效防止反射访问内部类,提高系统的安全性。此外,只有明确导出的包才对外开放,也避免了非预期访问。

可维护性与可演进性增强

模块系统天然支持系统拆分、边界划分,代码结构更清晰,有利于团队协作、功能演进和代码重构。

支持定制运行时

使用工具如 jlink,开发者可以按需定制 Java 运行时,仅包含需要的模块,构建最小化、轻量化的专用运行环境。

综上所述,Project Jigsaw 不只是语法糖或语义扩展,而是 Java 平台结构层面的根本性变革。

2. 背景与历史

2.1 Java 的演化与模块化需求

在 Java 9 之前,Java 平台主要通过类、包、JAR 文件进行组织。虽然这些结构在最初满足了中小型项目的需求,但随着应用程序和平台本身的规模不断扩大,现有结构显现出诸多局限:

  1. 类路径污染:Java 传统的类路径(classpath)是一种扁平结构,所有类都在一个共享的命名空间中,容易出现类冲突(ClassNotFoundException 或 ClassCastException)。

  2. 缺乏明确依赖声明:在类路径中无法明确指定模块之间的依赖关系,只能通过手工维护文档或构建脚本处理。

  3. 难以隐藏内部实现:包级别的访问控制无法阻止其他代码通过反射访问内部类,封装性不足。

  4. 难以拆分平台:JDK 是一个整体,无法裁剪无关模块,导致部署包庞大,难以适配轻量级设备或云原生场景。

  5. 工具链支持受限:由于缺乏结构化信息,像编译器、IDE、构建工具难以准确判断依赖和范围,影响智能提示、重构、安全性。

这些问题催生了对更高级别的结构化支持,也为 Project Jigsaw 的提出奠定了基础。

2.2 Project Jigsaw 的设计目标

Project Jigsaw 并不仅仅是引入“模块”这一语法概念,而是旨在从语言、平台、工具三方面彻底改进 Java 的模块化能力,其核心目标包括:

1. 可伸缩的模块平台(Scalable Platform)

将 JDK 拆分为若干个标准模块,使其能够按需组合、精简打包,以适配嵌入式、容器化部署等轻量化场景。例如,Java 9 开始,JDK 被拆分为大约 90 个模块(如 java.base、java.sql、java.logging 等)。

2. 增强的封装性(Stronger Encapsulation)

通过模块描述符(module-info.java)显式定义模块公开的 API 和隐藏的实现细节,防止外部代码滥用内部类,提升封装性和安全性。

3. 可靠的配置机制(Reliable Configuration)

通过模块系统显式声明依赖(requires)和导出(exports),在编译和运行时自动检测缺失依赖、重复依赖或循环依赖,从而构建更加健壮的系统。

4. 多语言支持(Multiple Language Support)

支持用不同语言(如 Java、Kotlin、Scala)编写的模块共存于同一应用中,只要遵循模块结构即可集成。

5. 兼容历史系统(Maintain Backward Compatibility)

兼容现有非模块化的代码和库,支持逐步迁移;引入“自动模块”“未命名模块”等机制为过渡提供缓冲。

Project Jigsaw 经过多个版本的迭代,最终在 Java 9 正式合并入主线分支(JEP 261),成为该版本的核心特性之一。

 

3. 理解模块

Java 9 模块化系统的核心概念是“模块(module)”。本章将从模块的语义、结构、语法等维度详细解析,帮助读者建立起对 Java 模块的系统性理解。

3.1 模块的定义与基本结构

在 Java 中,模块是一组相关包(packages)和资源的集合。它们打包在一起形成应用、库或运行时的一部分。模块通过一个特殊的描述文件 module-info.java 显式地声明它的依赖、暴露的 API、使用的服务等信息。

模块的基本特征:
  1. 自包含(Self-contained):每个模块包含自己需要的类和资源。

  2. 显式依赖(Explicit dependencies):使用 requires 语句声明依赖其他模块。

  3. 受控访问(Controlled access):通过 exports 控制对外暴露的包,其它包保持封装。

  4. 服务声明(Service declaration):模块可声明使用/提供服务,便于松耦合编程。

模块与包、类的区别:
级别概念作用
类(Class)最小的代码单元实现具体功能逻辑
包(Package)类的逻辑分组控制类访问范围
模块(Module)包的逻辑分组控制包的可见性和依赖关系

模块是对包和类组织的进一步抽象,用于定义更大粒度的访问边界和依赖管理。

3.2 module-info.java 语法详解

module-info.java 是每个模块的声明文件,位于模块的根目录下。它告诉 Java 编译器和 JVM:

  • 本模块叫什么名字

  • 本模块依赖哪些其他模块

  • 本模块开放哪些包给其他模块

  • 本模块使用/提供哪些服务

以下是一个典型示例:

module com.example.app {
    requires java.sql;
    requires com.example.utils;

    exports com.example.app.api;
    exports com.example.app.model;

    uses com.example.spi.MyService;
    provides com.example.spi.MyService with com.example.impl.MyServiceImpl;
}
常见关键字说明:
  • module:声明模块名,必须唯一,建议使用包名风格(如 com.company.module)。

  • requires:声明对其他模块的依赖。

  • exports:声明对外暴露的包。

  • opens:声明开放包给反射访问(如 JAXB、Spring)。不同于 exportsopens 是运行时开放而非编译期开放。

  • uses:声明该模块使用某个服务接口(配合 ServiceLoader)。

  • provides ... with:声明实现了某个服务接口的具体类。

3.3 模块 vs 包 vs 类

为帮助大家更系统性理解模块的抽象层次,我们可以从代码组织结构上进行比较:

维度类(Class)包(Package)模块(Module)
最小构成方法、字段类集合包集合
封装边界privateprotected默认访问修饰符exports/opens
单元标识类名包名模块名(module-info.java
可见性控制成员级别类级别包级别
依赖声明构造函数/成员变量requires
示例对比说明:

假设我们开发了一个图书管理系统,其中包含三个模块:

  • com.library.core:核心功能模块

  • com.library.db:数据访问模块,依赖 core

  • com.library.app:应用启动模块,依赖 coredb

每个模块都包含若干包,如:

com.library.core.api
com.library.core.impl
com.library.db.dao
com.library.app.main

而模块之间的依赖关系、暴露接口、服务使用就通过 module-info.java 来完成组织与管理。这种结构清晰可控,远胜传统 JAR 混杂的方式。

4. 创建模块化应用程序

本章将通过实际示例,带领你从零构建一个模块化的 Java 应用程序。我们将涵盖模块的结构组织、模块声明的编写、模块之间的依赖配置等操作。内容循序渐进,适合刚接触模块系统的开发者。

4.1 构建基本模块结构

在模块化开发中,项目的目录结构清晰规范是成功的关键。下面是一个典型的多模块项目结构:

bookstore-app/
├── com.bookstore.model/
│   ├── module-info.java
│   └── com/bookstore/model/Book.java
├── com.bookstore.service/
│   ├── module-info.java
│   └── com/bookstore/service/BookService.java
├── com.bookstore.main/
│   ├── module-info.java
│   └── com/bookstore/main/Main.java
模块说明:
  • com.bookstore.model:包含实体类 Book

  • com.bookstore.service:包含业务逻辑类 BookService,依赖 model 模块

  • com.bookstore.main:应用入口,依赖 modelservice

4.2 示例:简单模块化应用程序

1. com.bookstore.model 模块

module-info.java

module com.bookstore.model {
    exports com.bookstore.model;
}

Book.java

package com.bookstore.model;

public class Book {
    private String title;
    private String author;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    public String getTitle() { return title; }
    public String getAuthor() { return author; }

    @Override
    public String toString() {
        return title + " by " + author;
    }
}
2. com.bookstore.service 模块

module-info.java

module com.bookstore.service {
    requires com.bookstore.model;
    exports com.bookstore.service;
}

BookService.java

package com.bookstore.service;

import com.bookstore.model.Book;

public class BookService {
    public Book getSampleBook() {
        return new Book("Effective Java", "Joshua Bloch");
    }
}
3. com.bookstore.main 模块

module-info.java

module com.bookstore.main {
    requires com.bookstore.model;
    requires com.bookstore.service;
}

Main.java

package com.bookstore.main;

import com.bookstore.model.Book;
import com.bookstore.service.BookService;

public class Main {
    public static void main(String[] args) {
        BookService service = new BookService();
        Book book = service.getSampleBook();
        System.out.println("Book info: " + book);
    }
}

这个示例展示了如何使用模块将应用程序的不同职责划分清楚,并通过 requiresexports 建立模块之间的联系。

4.3 多模块间的依赖关系处理

在构建多模块系统时,依赖关系的声明和管理尤为关键。模块间依赖通过 requires 指令声明,同时 exports 决定哪些包可供其他模块访问。

依赖传递(Transitive Dependencies)

当模块 A 依赖模块 B,模块 B 又依赖模块 C 时,默认 A 无法访问 C。但如果 B 使用了 requires transitive,则 A 也可以访问 C。

示例:

module com.example.b {
    requires transitive com.example.c;
}

module com.example.a {
    requires com.example.b; // 可隐式访问 com.example.c
}
反射访问问题与 opens

默认情况下,Java 模块不会允许反射访问其内部类。如果使用框架如 Spring、JAXB 等需要反射,可以使用 opens 指令开放包。

opens com.example.model to spring.core;
冲突与重复依赖

模块化系统会在编译期和运行期检测重复模块声明或循环依赖,防止潜在问题。例如:

 

5. 构建模块化应用程序

在上一章中,我们已经完成了一个简单模块化项目的结构搭建与代码编写。本章将深入讲解如何通过命令行工具对模块化项目进行构建,包括编译、打包、配置模块路径,以及类路径与模块路径的区别。

5.1 使用 javac 编译模块

Java 9 引入了 --module-source-path--module 参数,支持编译模块化源代码。我们仍以上一章中的 bookstore 项目为例,展示如何逐步构建。

1. 项目目录组织(源代码)
bookstore-app/
├── mods/                  <-- 编译输出目录
├── src/
│   ├── com.bookstore.model/
│   │   └── ...
│   ├── com.bookstore.service/
│   │   └── ...
│   └── com.bookstore.main/
│       └── ...
2. 编译命令示例
# 编译 model 模块
javac -d mods/com.bookstore.model \
      src/com.bookstore.model/module-info.java \
      src/com.bookstore.model/com/bookstore/model/*.java

# 编译 service 模块(依赖 model)
javac --module-path mods \
      -d mods/com.bookstore.service \
      src/com.bookstore.service/module-info.java \
      src/com.bookstore.service/com/bookstore/service/*.java

# 编译 main 模块(依赖 model 和 service)
javac --module-path mods \
      -d mods/com.bookstore.main \
      src/com.bookstore.main/module-info.java \
      src/com.bookstore.main/com/bookstore/main/*.java

说明:

  • --module-path mods:设置模块路径,Javac 会在此目录下查找依赖模块。

  • -d:指定编译后的输出目录。

  • 每个模块编译到 mods 目录下以模块名命名的子目录中。

5.2 模块路径与类路径的区别

模块路径和类路径虽然都用于定位类文件,但它们在模块系统中的作用完全不同:

特性类路径(Classpath)模块路径(Module Path)
文件结构扁平结构结构化,模块为单位
依赖声明隐式依赖(通过代码或构建工具)显式依赖(通过 module-info.java
封装控制严格控制,仅导出包可访问
冲突处理类冲突不易发现,可能运行时失败编译/运行期检测冲突

注意:模块路径是模块系统工作的核心,不支持传统类路径的类自动访问模块,也不能把模块和非模块混合在类路径中使用。

5.3 创建模块化 JAR 文件

编译完成后,我们通常需要将模块打包成 JAR 文件进行分发或部署。打包时保留模块结构。

jar --create --file=mlibs/com.bookstore.model.jar \
    --main-class=com.bookstore.model.Book \
    -C mods/com.bookstore.model .

jar --create --file=mlibs/com.bookstore.service.jar \
    -C mods/com.bookstore.service .

jar --create --file=mlibs/com.bookstore.main.jar \
    --main-class=com.bookstore.main.Main \
    -C mods/com.bookstore.main .

说明:

  • --create:表示创建 JAR

  • --file:指定输出文件名

  • --main-class:指定 JAR 启动入口(可选)

  • -C:切换到对应目录压缩其内容

通过以上步骤,我们就成功将模块编译并打包为结构化的模块化 JAR 文件,可供运行或发布使用。

6. 运行模块化应用程序

在构建完成模块化应用程序之后,下一步就是运行它。本章将详细讲解如何使用 java 命令启动模块化程序,解析关键参数,如 --module-path--module,并说明如何设置启动类和调试常见问题。

6.1 使用 java 命令运行模块

Java 9 开始,java 命令支持模块化启动,通过指定模块路径与主模块来运行应用程序。

示例:运行 bookstore 应用

假设我们已将三个模块编译并打包成 JAR 文件,保存在 mlibs/ 目录中:

mlibs/
├── com.bookstore.model.jar
├── com.bookstore.service.jar
└── com.bookstore.main.jar

我们希望运行 com.bookstore.main 模块中的 Main 类:

java --module-path mlibs \
     --module com.bookstore.main/com.bookstore.main.Main

说明:

  • --module-path mlibs:指定模块搜索路径(等价于类路径中的 -cp)。

  • --module <module>/<main-class>:指定要启动的模块及其主类。

注意:如果模块已在 module-info.java 中使用 main-class 指定了主类,也可以简写:

java --module-path mlibs --module com.bookstore.main

6.2 多模块路径与运行时依赖

模块路径可以接受多个目录或 JAR 路径,使用平台分隔符(Unix 为 :,Windows 为 ;)分隔。例如:

java --module-path "lib:mlibs" --module my.main/module.Main

同时,模块路径下的每个 JAR 都应是合法的模块化 JAR,即含有 module-info.class 文件。否则只能作为“自动模块”或放入类路径处理。

6.3 启动错误与排查

模块化应用启动常见问题包括:

1. 找不到模块
Error: Module com.example.app not found

排查:确保 --module-path 指向正确目录,并含有目标模块的 JAR。

2. 无法访问类或包
Error: class com.example.Foo is not accessible

排查:检查目标类是否在导出的包中(exports),或者是否被 opens 打开。

3. 主类未定义
Error: no main manifest attribute, in com.example.app.jar

排查:使用 --module <module>/<MainClass> 方式手动指定主类,或在 module-info.java 中添加 main-class 指令并重新打包。

6.4 在 IDE 中运行模块应用

尽管命令行控制最为灵活,但在实际开发中我们常使用 IDE(如 IntelliJ IDEA、Eclipse)来运行模块化应用。大多数主流 IDE 在 Java 9 之后已原生支持模块结构。

IntelliJ IDEA 示例:
  1. 新建 Java Module 项目,确保启用 module-info.java。

  2. 在运行配置中设置 --module-path 和主模块信息。

  3. IDEA 会自动处理模块依赖和类路径设置。

Eclipse 示例:
  1. 安装支持 Java 9+ 的 Eclipse 版本。

  2. 创建 Modular Project。

  3. 使用 Project Properties > Java Build Path > Module Dependencies 管理依赖。

 

7. 迁移现有项目到模块化系统

将一个已有的 Java 应用迁移到 Java 9 模块系统并非易事,尤其对于历史遗留系统而言。本章将指导你逐步完成迁移过程,从分析依赖、处理封装冲突到分阶段迁移策略,助力老项目安全平滑地过渡到模块化架构。

7.1 遗留系统迁移的挑战

遗留项目通常存在以下问题:

  • 类路径依赖复杂、重复:多个 JAR 之间存在冲突或循环依赖。

  • 广泛使用反射访问内部类:模块化系统默认禁止对未开放包的反射访问。

  • 无明确边界定义:所有类在默认包结构下暴露,没有封装控制。

  • 缺乏结构化构建管理:如使用 Ant 或手工构建,缺乏模块化编译流程。

这些问题导致无法直接将项目转为模块化系统,必须采取渐进式、工具辅助的方式进行迁移。

7.2 使用 jdeps 工具分析依赖

Java 提供了 jdeps 工具用于分析 JAR 文件之间的依赖关系,帮助识别模块划分、未使用依赖、非法访问等。

示例:分析模块依赖
jdeps --module-path mods -s \
      --multi-release 9 \
      --generate-module-info out-dir \
      my-legacy-lib.jar

说明:

  • --multi-release 9:为支持多版本类文件的分析

  • --generate-module-info:自动生成初始的 module-info.java(需手工完善)

  • -s:简略输出依赖(仅模块名)

该工具将帮助我们识别:

  • 依赖哪些模块(如 java.xml, java.sql, 第三方库)

  • 哪些包存在非法访问(例如未导出的内部类)

  • 哪些 JAR 可以归并、拆分为模块

7.3 分阶段迁移策略与实战示例

迁移路径推荐分为以下几步:

第一步:构建模块结构,保留类路径兼容

将应用拆分为逻辑子模块,并为每个模块建立对应的目录与初始 module-info.java,但暂时仍使用类路径启动。

第二步:转换主模块为真正模块

选择启动模块(如 app)作为首个真正模块,编写完整 module-info.java,指定依赖与导出包。

第三步:使用自动模块过渡依赖

对于暂未模块化的第三方依赖(如旧版 Apache Commons JAR),可以放入模块路径,由 JVM 自动识别为“自动模块”:

jar --create --file=mlibs/legacy.jar -C legacy-lib/ .
java --module-path mlibs --module my.app/com.my.App

JVM 会基于 JAR 文件名生成模块名,但不建议长期依赖自动模块,应尽快替换为显式模块。

第四步:解决强封装与反射访问问题

若应用使用反射访问未导出类,可通过以下方式解决:

  • module-info.java 中使用 opens 语句开放包:

opens com.example.internal to spring.core;
  • 启动时使用命令行参数开放访问(不推荐长期依赖):

--add-opens com.example.internal/com.example.internal.Class=ALL-UNNAMED
第五步:逐步模块化其他模块与依赖
  • 对于核心业务模块、工具模块逐一补全 module-info.java

  • 替换非模块化第三方依赖为模块化版本(如使用 Maven Central 发布的模块 JAR)

  • 使用构建工具如 Maven/Gradle 配合插件(如 moditect)管理模块边界与打包

 

8. 高级模块特性

Java 模块系统不仅支持基本的模块定义与依赖管理,还引入了多项高级特性,使模块系统更具扩展性和灵活性。本章将详尽探讨这些特性,包括服务加载机制(usesprovides)、自动模块与未命名模块的作用,以及强封装的相关参数处理方法。

8.1 usesprovides:模块化服务加载机制

在模块系统中,服务的声明与发现不再仅靠 SPI(Service Provider Interface)文件,而是通过 usesprovides 指令完成。

uses:声明模块依赖某服务
module com.example.client {
    uses com.example.spi.Formatter;
}
provides:声明模块提供某服务实现
module com.example.impl {
    provides com.example.spi.Formatter with com.example.impl.JsonFormatter;
}
使用 ServiceLoader
ServiceLoader<Formatter> loader = ServiceLoader.load(Formatter.class);
for (Formatter f : loader) {
    System.out.println(f.format("hello"));
}

这样,客户端模块无需显式依赖具体实现,只需声明依赖服务接口,模块系统将通过 ServiceLoader 自动查找实现。

好处:
  • 松耦合:客户端与实现解耦

  • 模块隔离:只需依赖接口模块

  • 可扩展:新增实现模块无需修改客户端

8.2 自动模块与未命名模块

Java 为了兼容旧有类库,引入了两种非显式模块机制。

自动模块(Automatic Modules)
  • 指传统 JAR 被放置于 --module-path 中,无 module-info.class

  • JVM 会根据 JAR 文件名推断模块名,并导出所有包

# 假设放入 JAR: lib/commons-lang3-3.12.0.jar
java --module-path lib --module my.module/Main

注意: 自动模块是临时兼容机制,不具备封装控制和稳定模块名。应尽快迁移为显式模块。

未命名模块(Unnamed Module)
  • 位于 classpath 中的类和 JAR 属于 unnamed module

  • unnamed module 可以访问模块路径中的所有模块

  • 模块路径中的模块默认不能访问 unnamed module

用途: 支持非模块化代码与模块化系统混用,但无法享受模块系统的封装与检测能力。

8.3 强封装与参数处理:--add-exports--add-opens

模块系统默认只允许访问显式导出的包。如果访问未导出的内部类或方法,需要使用以下参数:

--add-exports

强制将模块的某个包导出给指定模块。

--add-exports java.base/sun.security.util=ALL-UNNAMED

适用于在编译期/运行期访问未导出 API,但并不会开放反射访问。

--add-opens

除了导出包,还允许反射访问(适用于框架如 Spring、JAXB)。

--add-opens java.base/java.lang=ALL-UNNAMED
建议使用场景
  • 框架兼容老 API(过渡方案)

  • 测试内部类

  • 访问 JDK 内部类(谨慎使用,未来可能失效)

--illegal-access(已废弃)

Java 9 默认允许非法访问,Java 16 起已移除该机制,应避免依赖。

9. 模块化开发最佳实践

随着模块化系统在 Java 9 的引入,构建大型、可维护、可扩展的系统有了更清晰的结构边界和封装机制。本章将从模块划分策略、版本管理、测试方法、演进模式等方面,系统总结模块化开发中的最佳实践,帮助你避免常见陷阱,提升工程质量。

9.1 模块划分与依赖管理建议

1. 每个模块职责单一,围绕“业务边界”划分

良好模块应聚焦单一职责(SRP),例如:

  • user.api:定义用户操作接口(如服务接口、DTO)

  • user.service:包含用户业务逻辑实现

  • user.persistence:封装数据库访问

2. 不要过度拆分模块

模块过细会增加管理复杂度,特别是在构建、部署、测试时产生不必要的开销。保持合理粒度。

3. 显式控制模块间依赖

使用 requires 指令定义依赖关系,杜绝隐式引用。通过 exports 精确控制暴露的 API 包,避免无意泄漏内部结构。

4. 防止循环依赖

模块之间不能形成循环依赖。可通过引入“抽象模块”(仅定义接口)中断循环依赖链。

9.2 API 与实现分离策略

推荐将接口与实现分属不同模块:

  • com.example.api(公共接口,导出)

  • com.example.impl(内部实现,不导出)

这种结构有利于隐藏实现细节、替换实现、进行测试隔离。

9.3 模块版本控制与演进

Java 模块系统不内建版本支持,因此版本管理需由构建工具(如 Maven、Gradle)控制:

  • 避免直接依赖 SNAPSHOT 或本地构建 JAR

  • 使用语义化版本命名(Semantic Versioning)

  • 构建过程建议生成 module descriptor (module-info.class) 并记录模块版本元数据

9.4 模块化项目的测试策略

单元测试
  • 测试模块可独立运行

  • 使用 requires transitive 暴露测试依赖模块

  • 使用 opens 指令允许测试框架访问包

集成测试
  • 启动主模块,配置模块路径完整覆盖所有依赖

  • 利用 --add-opens 临时开放包访问权限

构建工具整合
  • Maven:使用 moditect-maven-plugin 添加 module-info

  • Gradle:Java 9+ 插件已支持 module-info 编译

9.5 构建系统中的模块优化技巧

  • 使用多模块项目结构简化依赖和打包

  • JAR 文件名规范:<module-name>-<version>.jar

  • 自动生成 module-info.java 初稿后再手动优化

9.6 常见问题与误区

问题原因建议
启动报错“module not found”模块路径未正确配置使用 --module-path 指向所有依赖
运行时反射失败未开放内部包使用 opens--add-opens
循环依赖模块设计不合理抽象接口解耦,或合并模块
自动模块名冲突多个 JAR 被赋予相同模块名显式定义模块或避免命名重复

 10. 完整配置参考

一份完整示例的 module-info.java 文件,涵盖所有常见的模块声明语法元素,并在代码中用注释详细说明每个配置的作用和使用场景:

/**
 * 示例模块声明文件 module-info.java
 * 详细演示了模块系统中的各种指令和配置
 */
module com.example.myapp {

    // requires:声明本模块依赖的其他模块
    requires java.base;              // 所有模块隐式依赖,通常不用写,示例演示
    requires java.sql;               // 依赖 Java 平台的 SQL 模块
    requires transitive com.example.api;  // 传递依赖,依赖本模块的模块也会隐式依赖 com.example.api

    // requires static:表示编译时需要依赖,但运行时可选
    requires static lombok;          // 仅编译时需要 lombok 注解处理器,运行时无需

    // exports:导出包,允许其他模块访问这些包中的公共类型
    exports com.example.myapp.api;           // 导出公共 API 包
    exports com.example.myapp.utils to com.example.client; // 仅导出给指定模块

    // opens:打开包,允许反射访问该包(不等同于 exports)
    opens com.example.myapp.internal;         // 允许所有模块通过反射访问此包(常用于框架)
    opens com.example.myapp.reflection to spring.core;  // 仅允许 spring.core 模块反射访问

    // uses:声明本模块使用的服务接口
    uses com.example.spi.Formatter;

    // provides:声明本模块提供的服务实现
    provides com.example.spi.Formatter with com.example.myapp.impl.JsonFormatter;

    // 注释:module-info.java 文件不能包含类、接口定义,只能包含模块声明相关语句
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

探索java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值