模块化系统(JPMS)

210 阅读3分钟
  • Java模块化系统(Java Platform Module System),Java9引入的核心特性,为了解决传统Java的依赖混乱、代码封装性差和运行时臃肿。

  • 模块化的核心目标:

    • 强封装性:模块声明外部暴漏的(exports),隐藏内部实现
    • 显式依赖管理:模块需声明依赖的其他模块(requires),避免隐式依赖冲突
    • 优化运行时:只加载必要的模块,减少占用内容和启动时长
    • 解决JAR地狱(依赖管理冲突,版本不一致):通过版本隔离和依赖声明,避免类路径冲突。
  • 模块定义(module-info.java):每个模块必须包含一个module-info.java文件,定义模块的元数据:

  module com.example.myapp {
      requires java.sql; // 依赖其他模块
      rqeuires transitive com.example.tuils; //传递依赖(依赖此模块的模块自动继承)
      exports com.example.api; //对外暴漏的包
      opens com.example.internal; //允许反射访问(如Spring/Hibernate)
      provides com.example.Service with com.example.SeviceImpl; //服务提供
      uses com.exmaple.spi.Provider; //服务消费
  }
  • 关键概念与语法
关键字作用
requires声明对其他模块的依赖
requires transitive传递依赖,依赖此模块的模块自动继承此依赖
exports导出包,允许其他模块访问此包中的公共类
opens开放包,允许其他模块通过反射访问此包中的类(即使未导出)
provides...with声明服务接口的实现类
uses声明需要消费的服务接口
  • 模块类型

    • 命名模块(Named Module):显式声明module-info.java的模块,依赖显式声明
    • 自动模块(Auto Module): ① 传统JAR放在模块路径上,自动转化为模块,依赖其他所有模块(隐式requires)② 模块名基于jAR名推导(如my-library-1.0.jar → 模块名my.library
    • 未命名模块(Uname Module): 类路径上的JAR被视为未命名模块,可以访问所有模块,但无法被其他模块依赖。
  • 服务加载机制(Service Loader): 通过provides 和 uses 实现解耦的服务发现

    • 服务接口定义
        package com.example.spi; 
            public interface Provider { void execute(); 
        }
    
    • 服务提供者模块
        module com.example.provider { 
            provides com.example.spi.Provider with com.example.ProviderImpl; 
        }
    
    • 服务消费者模块:
      module com.example.consumer { 
          requires com.example.spi; 
          uses com.example.spi.Provider; 
      }  
    
    • 使用服务
        ServiceLoader<Provider> loader = ServiceLoader.load(Provider.class); 
        loader.forEach(Provider::execute);
    
  • 模块化迁移的挑战:

    • 反射访问限制:未opens的包无法通过反射访问(常见于Spring/Hibernate), 解决方案:使用Opens或命令选项 --add-opens。
    • 依赖冲突:自动模块可能会引入隐式依赖
    • 动态加载类:Class.forName() 可能会失败,需要改用模块感知的加载方式。

面试常见问题与答案

  1. 为什么需要模块化?

    • 解决类路径混乱、增强封装性、优化运行时性能。
  2. 如何让其他模块访问本模块的非导出类?

    • 使用opens开放包,或通过--add-opens命令行参数。
  3. 模块化如何与服务加载机制结合?

    • 通过providesuses声明服务提供者和消费者,实现解耦的插件化架构。
  4. 模块化对反射的影响是什么?

    • 默认禁止反射访问未开放的包,需显式配置。
  5. 如何将传统JAR转换为模块?

    • 放在模块路径上成为自动模块,或添加module-info.java定义为命名模块。但自动模块可能会引入隐式依赖