Java 9+ 模块化系统(Jigsaw)实战:从 Jar 地狱到模块解耦的架构升级
在 Java 9 之前,Java 应用的依赖管理长期面临 “Jar 地狱”(Jar Hell)问题 —— 依赖冲突、类路径混乱、冗余依赖等问题频发,尤其在大型项目中,仅靠classpath管理依赖往往导致系统架构臃肿、维护成本高。为解决这一痛点,Java 9 正式引入模块化系统(Project Jigsaw),通过 “显式模块定义”“可控的包访问权限”“精确的依赖声明” 三大核心能力,重新定义了 Java 应用的构建与部署方式。本文将从传统 Jar 依赖的痛点出发,详解模块化系统的核心概念、语法规则、实战场景及迁移策略,帮你掌握这一面向大型工程的架构升级技术。
一、为什么需要模块化系统?—— 传统 Jar 依赖的 4 大痛点
在理解 Java 模块化系统之前,我们首先要明确:传统classpath机制在依赖管理、权限控制、架构设计等方面存在明显短板,这些痛点在大型项目或框架开发中尤为突出。
1.1 痛点 1:Jar 地狱(依赖冲突与版本混乱)
传统 Java 应用通过classpath加载类文件,但classpath是一个 “扁平的类容器”,无法区分不同版本的 Jar 包,导致依赖冲突。例如,项目同时依赖spring-core-4.3.0.jar和spring-core-5.3.0.jar,classpath会优先加载路径靠前的 Jar 包,若后续代码依赖高版本的 API,会抛出NoSuchMethodError或ClassNotFoundException。
示例:依赖冲突导致的运行时异常
// 场景:项目同时依赖spring-core-4.3.0.jar(低版本)和spring-core-5.3.0.jar(高版本)
public class SpringDependencyConflictDemo {
public static void main(String[] args) {
// Spring 5.3.0新增的API:GenericApplicationContext.setApplicationStartup()
GenericApplicationContext context = new GenericApplicationContext();
// 若classpath优先加载4.3.0版本,会抛出NoSuchMethodError
context.setApplicationStartup(new DefaultApplicationStartup());
}
}
运行结果(错误):
Exception in thread "main" java.lang.NoSuchMethodError:
org.springframework.context.support.GenericApplicationContext.setApplicationStartup(Lorg/springframework/core/metrics/ApplicationStartup;)V
at SpringDependencyConflictDemo.main(SpringDependencyConflictDemo.java:8)
问题分析:
- 版本不可控:classpath无法指定依赖版本,只能被动接受 “路径优先” 或 “先到先得” 的加载规则;
- 冲突难排查:依赖冲突往往在运行时暴露,需通过mvn dependency:tree等工具手动分析依赖树,排查成本高。
1.2 痛点 2:类访问权限过度开放(封装性缺失)
传统 Java 的访问权限(public、protected、default、private)仅作用于类内部或包内,无法限制 “跨 Jar 包的访问”。例如,一个 Jar 包中的public类会被所有依赖该 Jar 的项目访问,即使该类仅为内部工具类,无需对外暴露,导致封装性缺失,增加 API 维护成本。
示例:过度开放的内部工具类
// Jar包:common-utils-1.0.jar中的内部工具类(本应仅内部使用)
package com.example.common.utils;
// 为方便内部调用,误定义为public
public class InternalStringUtils {
// 内部工具方法:仅用于common-utils.jar内部字符串处理
public static String formatInternal(String input) {
return input.replaceAll("\\s+", "");
}
}
// 其他项目依赖common-utils-1.0.jar,意外调用内部方法
public class ExternalProjectDemo {
public static void main(String[] args) {
// 外部项目调用了本应内部使用的方法
String result = InternalStringUtils.formatInternal("hello world");
System.out.println(result); // 输出:helloworld
}
}
问题分析:
- 封装性破坏:public修饰符导致内部工具类被外部项目访问,违反 “最小权限原则”;
- API 兼容性风险:若后续InternalStringUtils修改或删除formatInternal()方法,会导致依赖该方法的外部项目崩溃。
1.3 痛点 3:冗余依赖与资源浪费
传统classpath机制下,项目往往依赖 “过度完整” 的 Jar 包(如为使用spring-core的某个工具类,需引入整个spring-contextJar 包),导致最终部署包体积庞大,冗余依赖占用磁盘空间和内存资源。
示例:冗余依赖的 Spring 项目
<!-- 传统Maven依赖:为使用Spring的BeanUtils,引入整个spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.0</version>
</dependency>
问题分析:
- 依赖膨胀:spring-context依赖spring-core、spring-beans、spring-aop等多个 Jar 包,总大小超过 1MB,而实际仅使用BeanUtils.copyProperties()一个方法;
- 部署效率低:冗余依赖导致部署包体积增大,传输和启动时间变长,尤其在容器化部署(如 Docker)场景中,影响迭代效率。
1.4 痛点 4:模块化架构设计困难
传统 Java 项目缺乏 “模块化” 的语言级支持,只能通过包结构(如com.example.service、com.example.dao)模拟模块划分,但这种划分仅停留在代码组织层面,无法在编译期和运行期强制模块边界,导致模块间依赖混乱(如 DAO 层直接依赖 Controller 层)。
示例:模块边界混乱的传统项目
// 包结构模拟模块划分,但无强制边界
com.example
├── controller // 控制层(应依赖service层,不应被service依赖)
│ └── UserController.java
├── service // 服务层(应依赖dao层,不应依赖controller层)
│ └── UserService.java
└── dao // 数据访问层(不应依赖其他层)
└── UserDao.java
// 错误依赖:service层依赖controller层(违反分层架构)
package com.example.service;
import com.example.controller.UserController; // 错误:service依赖controller
public class UserService {
public void processUser() {
UserController controller = new UserController(); // 直接实例化controller
controller.getUserInfo();
}
}
问题分析:
- 架构边界模糊:包结构无法阻止跨层依赖,导致架构设计沦为 “纸面规则”;
- 维护成本高:模块间耦合度高,修改一个模块可能影响多个依赖模块,代码重构困难。
1.2 Java 模块化系统的核心价值
Java 9 + 模块化系统通过以下 4 点设计,从根本上解决了传统 Jar 依赖的痛点:
- 显式模块定义:通过module-info.java文件明确模块名称、依赖模块及导出包,实现 “模块级” 的依赖管理;
- 可控的访问权限:仅导出模块中需要对外暴露的包,内部包默认隐藏,强化封装性;
- 精确依赖声明:模块需显式声明依赖的其他模块,避免隐式依赖和版本冲突;
- 架构边界强制:编译期和运行期强制模块间的依赖规则,防止跨模块的非法访问,保障架构设计落地。
二、Java 模块化系统的核心概念与语法规则
Java 模块化系统的核心是 “模块(Module)”—— 一个包含代码和资源的独立单元,通过module-info.java文件定义模块的元信息。掌握模块的定义、依赖、导出等语法规则,是使用模块化系统的基础。
2.1 1. 核心概念:模块、模块描述文件与模块路径
(1)模块(Module)
模块是 Java 模块化系统的基本单元,具有以下特性:
- 唯一性:每个模块有唯一的模块名称(如java.base、com.example.service);
- 自包含性:模块包含 Java 类、资源文件(如配置文件)及module-info.java描述文件;
- 依赖明确性:模块需显式声明依赖的其他模块,无隐式依赖;
- 访问可控性:模块仅导出指定的包,未导出的包仅内部可见。
(2)模块描述文件(module-info.java)
module-info.java是模块的 “身份证”,位于模块的根目录(如src/main/java/module-info.java),用于定义模块的元信息,包括模块名称、依赖模块、导出包等。
(3)模块路径(Module Path)
模块路径是 Java 9 + 新增的类加载路径,用于替代传统的classpath,支持加载模块化的 Jar 包(Module Jar)或未模块化的 Jar 包(Automatic Module)。模块路径的加载优先级高于classpath。
2.2 2. 模块描述文件的核心语法
module-info.java支持 6 类核心语法,覆盖模块定义、依赖、导出、服务等场景:
(1)定义模块名称(module关键字)
// 定义模块:模块名称为com.example.service(遵循反向域名规则,确保唯一性)
module com.example.service {
// 模块元信息(依赖、导出等)
}
(2)声明依赖模块(requires关键字)
- 普通依赖:requires 模块名;,表示当前模块依赖指定模块,且依赖是传递的(若 A 依赖 B,B 依赖 C,则 A 间接依赖 C);
- 非传递依赖:requires static 模块名;,表示依赖仅在编译期有效,运行期可选(如依赖编译工具类);
- 强制依赖:requires transitive 模块名;,表示当前模块的依赖会传递给依赖当前模块的其他模块(如 A 依赖 B,且 B requires transitive C,则 A 自动依赖 C)。
示例:声明模块依赖
module com.example.service {
// 普通依赖:依赖com.example.dao模块(运行期必需)
requires com.example.dao;
// 非传递依赖:依赖com.example.utils模块(仅编译期需要)
requires static com.example.utils;
// 强制依赖:依赖com.example.model模块,且传递给依赖当前模块的模块
requires transitive com.example.model;
}
(3)导出包(exports关键字)
exports 包名;表示当前模块导出指定的包,允许其他模块访问该包中的public类和接口;未导出的包仅当前模块内部可见,强化封装性。
示例:导出模块的公共包
module com.example.service {
requires com.example.dao;
// 导出公共包:允许其他模块访问com.example.service.api包中的类
exports com.example.service.api;
// 不导出内部包:com.example.service.internal仅模块内部可见
// (无需显式声明,默认不导出)
}
(4)定向导出包(exports...to关键字)
exports 包名 to 模块名1, 模块名2;表示当前模块仅向指定模块导出包,其他模块无法访问,适用于 “模块间协作但不对外暴露” 的场景。
示例:定向导出包
module com.example.service {
requires com.example.dao;
// 仅向com.example.controller模块导出com.example.service.api包
exports com.example.service.api to com.example.controller;
// 其他模块(如com.example.client)无法访问com.example.service.api
}
(5)提供服务(provides...with关键字)
provides 服务接口名 with 服务实现类名;表示当前模块提供指定服务接口的实现,遵循 “服务发现” 机制,支持模块解耦(如 A 模块依赖服务接口,B 模块提供实现,A 无需依赖 B)。
(6)使用服务(uses关键字)
uses 服务接口名;表示当前模块使用指定的服务接口,通过ServiceLoader加载服务实现类,无需显式依赖服务实现模块。
示例:服务提供与使用
// 1. 服务接口模块(com.example.service.api)
module com.example.service.api {
// 导出服务接口包
exports com.example.service.api;
}
// 服务接口(com.example.service.api.UserService)
package com.example.service.api;
public interface UserService {
String getUserInfo(Long userId);
}
// 2. 服务实现模块(com.example.service.impl)
module com.example.service.impl {
// 依赖服务接口模块
requires com.example.service.api;
// 提供服务实现:UserService接口由UserServiceImpl类实现
provides com.example.service.api.UserService with com.example.service.impl.UserServiceImpl;
}
// 服务实现类(com.example.service.impl.UserServiceImpl)
package com.example.service.impl;
import com.example.service.api.UserService;
public class UserServiceImpl implements UserService {
@Override
public String getUserInfo(Long userId) {
return "用户ID:" + userId + ",姓名:张三";
}
}
// 3. 服务使用模块(com.example.controller)
module com.example.controller {
// 依赖服务接口模块
requires com.example.service.api;
// 声明使用UserService服务接口
uses com.example.service.api.UserService;
// 导出控制器包
exports com.example.controller;
}
// 服务使用类(com.example.controller.UserController)
package com.example.controller;
import com.example.service.api.UserService;
import java.util.ServiceLoader;
public class UserController {
public String getUserInfo(Long userId) {
// 通过ServiceLoader加载服务实现(无需依赖com.example.service.impl模块)
ServiceLoader<UserService> serviceLoader = ServiceLoader.load(UserService.class);
UserService userService = serviceLoader.findFirst().orElseThrow();
return userService.getUserInfo(userId);
}
}
2.3 3. 模块化 Jar 包与自动模块
Java 9 + 支持两种类型的 Jar 包:
(1)模块化 Jar 包(Module Jar)
包含module-info.class(由module-info.java编译生成)的 Jar 包,是标准的模块化 Jar,支持显式依赖和导出控制。
(2)自动模块(Automatic Module)
未包含module-info.class的传统 Jar 包,Java 会自动将其转换为 “自动模块”,模块名称由 Jar 包名推导(如spring-core-5.3.0.jar的自动模块名为spring.core)。自动模块的所有包默认导出,且依赖所有其他模块。
示例:使用自动模块(依赖传统 Spring Jar 包)
// 模块描述文件:依赖传统Spring Jar包(自动模块)
module com.example.client {
// 依赖Spring的自动模块(模块名由Jar包名推导:spring.context)
requires spring.context;
// 导出客户端包
exports com.example.client;
}
注意事项:
- 自动模块兼容性:自动模块是为了兼容传统 Jar 包,建议优先使用模块化 Jar 包;
- 模块名推导规则:Jar 包名中的非字母数字字符(如-、_)会被替换为.,版本号会被忽略(如spring-core-5.3.0.jar→spring.core)。
三、Java 模块化系统的实战场景:从项目构建到架构解耦
Java 模块化系统在实际开发中应用广泛,本节将结合 3 个典型场景(模块化项目构建、模块间服务协作、传统项目模块化迁移),展示从需求分析到模块化实现的完整过程。
3.1 场景 1:模块化项目构建(Maven+Java 17)
需求:构建一个模块化的电商项目,包含 4 个模块:
- com.example.model:实体类模块(如User、Order);
- com.example.dao:数据访问模块(依赖model模块);
- com.example.service:服务模块(依赖dao模块,导出服务接口);
- com.example.controller:控制层模块(依赖service模块,使用服务接口)。
实现步骤:
(1)项目结构设计
e-commerce-modular/
├── pom.xml // 父Maven pom(管理子模块)
├── model/ // com.example.model模块
│ ├── pom.xml
│ └── src/main/java/
│ ├── module-info.java
│ └── com/example/model/
│ ├── User.java
│ └── Order.java
├── dao/ // com.example.dao模块
│ ├── pom.xml
│ └── src/main/java/
│ ├── module-info.java
│ └── com/example/dao/
│ ├── UserDao.java
│ └── OrderDao.java
├── service/ // com.example.service模块
│ ├── pom.xml
│ └── src/main/java/
│ ├── module-info.java
│ └── com/example/service/
│ ├── api/ // 服务接口包(导出)
│ │ ├── UserService.java
│ │ └── OrderService.java
│ └── impl/ // 服务实现包(不导出)
│ ├── UserServiceImpl.java
│ └── OrderServiceImpl.java
└── controller/ // com.example.controller模块
├── pom.xml
└── src/main/java/
├── module-info.java
└── com/example/controller/
├── UserController.java
└── OrderController.java
(2)父模块 pom.xml(管理子模块)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>e-commerce-modular</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>e-commerce-modular</name>
<description>Java模块化电商项目</description>
<!-- 子模块 -->
<modules>
<module>model</module>
<module>dao</module>
<module>service</module>
<module>controller</module>
</modules>
<!-- 统一配置 -->
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
(3)各子模块实现
① com.example.model模块
- module-info.java:
module com.example.model {
// 导出实体类包,允许其他模块访问
exports com.example.model;
}
- User.java:
package com.example.model;
public class User {
private Long id;
private String username;
private String email;
// 构造器、getter、setter
}
- pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.example</groupId>
<artifactId>e-commerce-modular</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>model</artifactId>
<name>model</name>
</project>
② com.example.dao模块
- module-info.java:
module com.example.dao {
// 依赖model模块
requires com.example.model;
// 导出DAO接口包(服务实现包不导出)
exports com.example.dao.api;
}
- UserDao.java(接口,com.example.dao.api包):
package com.example.dao.api;
import com.example.model.User;
public interface UserDao {
User getUserById(Long id);
}
- UserDaoImpl.java(实现,com.example.dao.impl包,不导出):
package com.example.dao.impl;
import com.example.dao.api.UserDao;
import com.example.model.User;
public class UserDaoImpl implements UserDao {
@Override
public User getUserById(Long id) {
// 模拟数据库查询
User user = new User();
user.setId(id);
user.setUsername("张三");
user.setEmail("zhangsan@example.com");
return user;
}
}
- pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.example</groupId>
<artifactId>e-commerce-modular</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dao</artifactId>
<name>dao</name>
<!-- 依赖model模块 -->
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>model</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
③ com.example.service模块
- module-info.java:
module com.example.service {
// 依赖dao模块(非传递,仅当前模块依赖)
requires com.example.dao;
// 依赖model模块(传递依赖,让依赖当前模块的模块自动依赖model)
requires transitive com.example.model;
// 导出服务接口包
exports com.example.service.api;
// 提供服务实现(服务接口由实现类提供)
provides com.example.service.api.UserService with com.example.service.impl.UserServiceImpl;
}
- UserService.java(接口,com.example.service.api包):
package com.example.service.api;
import com.example.model.User;
public interface UserService {
User getUserInfo(Long userId);
}
- UserServiceImpl.java(实现,com.example.service.impl包):
package com.example.service.impl;
import com.example.dao.api.UserDao;
import com.example.dao.impl.UserDaoImpl;
import com.example.model.User;
import com.example.service.api.UserService;
public class UserServiceImpl implements UserService {
private final UserDao userDao = new UserDaoImpl();
@Override
public User getUserInfo(Long userId) {
return userDao.getUserById(userId);
}
}
- pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.example</groupId>
<artifactId>e-commerce-modular</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service</artifactId>
<name>service</name>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>dao</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>model</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
④ com.example.controller模块
- module-info.java:
module com.example.controller {
// 依赖service模块(获取服务接口)
requires com.example.service;
// 使用UserService服务
uses com.example.service.api.UserService;
// 导出控制器包
exports com.example.controller;
}
- UserController.java:
package com.example.controller;
import com.example.model.User;
import com.example.service.api.UserService;
import java.util.ServiceLoader;
public class UserController {
private final UserService userService;
// 初始化:通过ServiceLoader加载服务实现
public UserController() {
this.userService = ServiceLoader.load(UserService.class)
.findFirst()
.orElseThrow(() -> new RuntimeException("未找到UserService实现"));
}
// 控制器方法:获取用户信息
public User getUserById(Long userId) {
return userService.getUserInfo(userId);
}
}
- pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.example</groupId>
<artifactId>e-commerce-modular</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>controller</artifactId>
<name>controller</name>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>service</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
(4)编译与运行
- 编译项目:在父模块根目录执行mvn clean package,生成各模块的 Jar 包;
- 运行控制器模块:通过模块路径指定依赖的 Jar 包,执行UserController:
java --module-path controller/target/controller-1.0.0.jar:service/target/service-1.0.0.jar:dao/target/dao-1.0.0.jar:model/target/model-1.0.0.jar --module com.example.controller/com.example.controller.UserController
3.2 场景 2:模块间服务协作(基于服务发现机制)
需求:电商项目新增 “订单服务” 模块,要求:
- 订单服务依赖用户服务(获取用户信息);
- 订单服务与用户服务通过服务发现机制协作,无直接模块依赖;
- 支持后续替换用户服务的实现,无需修改订单服务代码。
实现方案:
- 新增com.example.order模块,依赖com.example.service.api(用户服务接口模块),使用uses声明使用UserService;
- 订单服务模块的module-info.java:
module com.example.order {
// 依赖用户服务接口模块
requires com.example.service.api;
// 依赖实体类模块(传递依赖)
requires transitive com.example.model;
// 使用UserService服务
uses com.example.service.api.UserService;
// 导出订单服务接口包
exports com.example.order.api;
// 提供订单服务实现
provides com.example.order.api.OrderService with com.example.order.impl.OrderServiceImpl;
}
- 订单服务实现类:
package com.example.order.impl;
import com.example.model.User;
import com.example.order.api.OrderService;
import com.example.service.api.UserService;
import java.util.ServiceLoader;
public class OrderServiceImpl implements OrderService {
private final UserService userService;
public OrderServiceImpl() {
this.userService = ServiceLoader.load(UserService.class).findFirst().orElseThrow();
}
@Override
public String createOrder(Long userId, Long productId) {
// 调用用户服务获取用户信息
User user = userService.getUserInfo(userId);
// 模拟创建订单
String orderId = "ORDER_" + System.currentTimeMillis();
return String.format("订单创建成功:订单ID=%s,用户=%s,商品ID=%d",
orderId, user.getUsername(), productId);
}
}
优势:
- 解耦协作:订单服务仅依赖用户服务接口,不依赖具体实现,实现 “接口与实现分离”;
- 可替换性:若后续用户服务实现变更(如从本地 DAO 改为远程 RPC 调用),仅需替换服务实现模块,无需修改订单服务代码;
- 可测试性:单元测试时可提供 Mock 的UserService实现,隔离外部依赖。
3.3 场景 3:传统项目的模块化迁移(分步迁移策略)
需求:将一个传统的非模块化电商项目(基于 Java 8)迁移到 Java 17 模块化系统,要求:
- 迁移过程不影响现有功能,支持逐步迁移;
- 先将核心模块(如model、dao)模块化,再迁移上层模块(如service、controller);
- 兼容项目中的传统 Jar 依赖(如 Spring、MyBatis)。
迁移步骤:
(1)环境准备
- 升级 JDK:将项目 JDK 从 8 升级到 17,确保代码兼容 Java 17 语法;
- 升级构建工具:将 Maven 升级到 3.8+,支持 Java 17 模块化。
(2)分步迁移核心模块
- 第一步:迁移model模块:
- 在model模块根目录创建module-info.java,定义模块名称和导出包;
- 修改 Maven 依赖,将model模块打包为模块化 Jar;
- 其他模块暂时通过classpath依赖model模块的模块化 Jar(自动模块)。
- 第二步:迁移dao模块:
- 为dao模块添加module-info.java,声明依赖model模块(模块化依赖);
- 导出 DAO 接口包,隐藏实现包;
- 其他模块(如service)改为通过模块路径依赖dao模块。
- 第三步:迁移上层模块:
- 依次迁移service、controller模块,逐步替换classpath依赖为模块路径依赖;
- 对传统 Jar 依赖(如 Spring),通过自动模块方式引入,后续逐步替换为官方提供的模块化 Jar(如 Spring 6 + 已支持模块化)。
(3)兼容传统依赖
- 自动模块处理:对于未模块化的传统 Jar(如 MyBatis 3.5.x),通过模块路径加载,Java 自动转换为自动模块,模块名由 Jar 包名推导(如mybatis-3.5.10.jar→mybatis);
- classpath兼容:若部分模块暂未迁移,可通过--class-path参数保留传统classpath,但建议优先使用模块路径。
(4)验证与测试
- 编译验证:使用javac --module-path编译模块化模块,确保依赖正确;
- 运行验证:使用java --module-path运行项目,验证功能正常;
- 兼容性测试:测试传统功能与模块化模块的交互,确保无兼容性问题。
迁移原则:
- 渐进式迁移:避免一次性迁移所有模块,优先迁移低耦合的核心模块,降低风险;
- 兼容性优先:对于无法模块化的传统依赖,通过自动模块兼容,不强制替换;
- 测试驱动:每迁移一个模块,执行单元测试和集成测试,确保功能正常。
四、Java 模块化系统的优势与注意事项
4.1 核心优势
- 依赖清晰:显式声明模块依赖,避免隐式依赖和版本冲突,解决 “Jar 地狱”;
- 封装性强:仅导出需要对外暴露的包,内部实现细节隐藏,降低 API 维护成本;
- 架构可控:编译期和运行期强制模块边界,防止跨模块非法访问,保障架构设计落地;
- 部署高效:支持模块化打包,可仅部署项目依赖的模块,减少冗余资源;
- 服务化协作:基于服务发现机制,实现 “接口与实现
