Java模块化系统深度解析:从JAR地狱到JPMS模块化

引言:模块化革命的必要性

在Java 9之前,Java生态系统面临着诸多挑战:庞大的类路径、版本冲突、复杂的依赖管理等。Java Platform Module System (JPMS) 的引入,标志着Java正式进入了模块化时代。这不仅改变了Java平台的内部结构,也彻底改变了我们构建和组织大型应用的方式。

一、模块化基础:从类路径到模块路径

1.1 模块的基本概念与结构

java 复制代码
// 1. 模块定义文件:module-info.java
// 这是模块系统的核心,每个模块都必须有module-info.java文件

/**
 * module-info.java示例
 * 
 * 基本语法:
 * module 模块名 {
 *     requires 其他模块;        // 声明依赖
 *     exports 包名;            // 导出包(公开API)
 *     opens 包名;             // 开放包(允许反射访问)
 *     provides 服务 with 实现; // 提供服务实现
 *     uses 服务;              // 使用服务
 * }
 */

// 示例:一个用户服务模块的定义
module com.example.userservice {
    // 依赖声明
    requires java.base;           // 隐式依赖,可省略
    requires java.sql;
    requires transitive org.slf4j; // 传递性依赖
    requires static lombok;       // 编译时依赖
    
    // 导出包:这些包可以被其他模块访问
    exports com.example.userservice.api;
    exports com.example.userservice.domain to com.example.app;
    
    // 开放包:允许通过反射访问(用于框架如Spring、Hibernate)
    opens com.example.userservice.internal;
    opens com.example.userservice.domain to spring.core, hibernate.core;
    
    // 服务提供声明
    provides com.example.userservice.spi.UserProvider
        with com.example.userservice.provider.DefaultUserProvider;
    
    // 服务使用声明
    uses com.example.userservice.spi.UserProvider;
}

1.2 构建第一个模块化应用

bash 复制代码
# 项目结构示例:
# modular-app/
# ├── module-a/
# │   ├── src/
# │   │   └── com.example.modulea/
# │   │       └── ModuleAClass.java
# │   └── module-info.java
# ├── module-b/
# │   ├── src/
# │   │   └── com.example.moduleb/
# │   │       └── ModuleBClass.java
# │   └── module-info.java
# └── app/
#     ├── src/
#     │   └── com.example.app/
#     │       └── MainApp.java
#     └── module-info.java
java 复制代码
// Module A: 提供服务接口
module com.example.modulea {
    exports com.example.modulea.api;
}

package com.example.modulea.api;
public interface GreetingService {
    String greet(String name);
}

// Module B: 提供服务实现
module com.example.moduleb {
    requires com.example.modulea;  // 依赖Module A
    provides com.example.modulea.api.GreetingService 
        with com.example.moduleb.impl.EnglishGreetingService;
}

package com.example.moduleb.impl;
import com.example.modulea.api.GreetingService;
public class EnglishGreetingService implements GreetingService {
    public String greet(String name) {
        return "Hello, " + name + "!";
    }
}

// 主应用模块
module com.example.app {
    requires com.example.modulea;  // 使用服务接口
    uses com.example.modulea.api.GreetingService;
}

package com.example.app;
import com.example.modulea.api.GreetingService;
import java.util.ServiceLoader;

public class MainApp {
    public static void main(String[] args) {
        // 通过ServiceLoader发现服务实现
        ServiceLoader<GreetingService> loader = 
            ServiceLoader.load(GreetingService.class);
        
        for (GreetingService service : loader) {
            System.out.println(service.greet("World"));
        }
    }
}

二、模块依赖与版本管理

2.1 模块依赖解析与冲突解决

java 复制代码
// 复杂的模块依赖场景
module com.example.application {
    requires com.example.utils;       // 依赖工具模块
    requires com.example.persistence; // 依赖持久化模块
    requires com.example.security;    // 依赖安全模块
    
    // 可选的动态依赖
    requires static com.example.experimental;
    
    // 排除传递依赖冲突
    requires com.example.database;
    // 如果com.example.database同时依赖了org.slf4j的不同版本
    // 可以使用--patch-module或升级模块化解决
}

// 模块版本管理策略
/**
 * 模块化系统的版本管理方式:
 * 1. 模块名称本身包含版本信息(不推荐)
 * 2. 使用module-info.java中的版本属性(Java 9+)
 * 3. 使用构建工具(Maven/Gradle)管理版本
 * 4. 使用JLink创建自定义运行时映像
 */

// module-info.java中的版本信息
@Version("1.2.0")
@Deprecated(since="2.0", forRemoval=true)
module com.example.legacy {
    requires transitive java.desktop version "11+";
    exports com.example.legacy.api;
}

// 构建多版本JAR(Multi-Release JAR)
// META-INF/MANIFEST.MF中声明:
// Multi-Release: true

// 目录结构:
// mymodule.jar
// ├── com/example/MyClass.class           # 主要类文件(Java 8)
// ├── META-INF/
// │   └── versions/
// │       └── 9/
// │           └── com/example/MyClass.class # Java 9版本
// │       └── 11/
// │           └── com/example/MyClass.class # Java 11版本
// └── module-info.class                   # 模块描述符

2.2 模块化依赖冲突解决实战

java 复制代码
import java.lang.module.*;
import java.util.*;

public class ModuleDependencyResolver {
    
    // 模拟模块依赖图
    static class ModuleGraph {
        Map<String, ModuleNode> modules = new HashMap<>();
        
        class ModuleNode {
            String name;
            String version;
            Set<ModuleNode> requires = new HashSet<>();
            Set<ModuleNode> requiredBy = new HashSet<>();
            
            ModuleNode(String name, String version) {
                this.name = name;
                this.version = version;
            }
            
            void addDependency(ModuleNode dependency) {
                requires.add(dependency);
                dependency.requiredBy.add(this);
            }
        }
        
        // 添加模块
        ModuleNode addModule(String name, String version) {
            String key = name + ":" + version;
            return modules.computeIfAbsent(key, k -> new ModuleNode(name, version));
        }
        
        // 解析依赖冲突
        List<Conflict> resolveConflicts() {
            List<Conflict> conflicts = new ArrayList<>();
            Map<String, List<ModuleNode>> nameToNodes = new HashMap<>();
            
            // 按模块名分组
            for (ModuleNode node : modules.values()) {
                nameToNodes.computeIfAbsent(node.name, k -> new ArrayList<>())
                          .add(node);
            }
            
            // 检查每个模块的多个版本
            for (Map.Entry<String, List<ModuleNode>> entry : nameToNodes.entrySet()) {
                if (entry.getValue().size() > 1) {
                    Conflict conflict = new Conflict(entry.getKey(), entry.getValue());
                    conflicts.add(conflict);
                }
            }
            
            return conflicts;
        }
    }
    
    static class Conflict {
        String moduleName;
        List<ModuleGraph.ModuleNode> versions;
        
        Conflict(String moduleName, List<ModuleGraph.ModuleNode> versions) {
            this.moduleName = moduleName;
            this.versions = versions;
        }
        
        @Override
        public String toString() {
            return String.format("模块 %s 有 %d 个版本: %s", 
                moduleName, versions.size(), 
                versions.stream()
                    .map(v -> v.version)
                    .collect(java.util.stream.Collectors.joining(", ")));
        }
    }
    
    public static void main(String[] args) {
        System.out.println("=== 模块依赖冲突解析 ===\n");
        
        ModuleGraph graph = new ModuleGraph();
        
        // 模拟一个复杂的依赖场景
        ModuleGraph.ModuleNode app = graph.addModule("com.example.app", "1.0");
        ModuleGraph.ModuleNode utilsV1 = graph.addModule("com.example.utils", "1.0");
        ModuleGraph.ModuleNode utilsV2 = graph.addModule("com.example.utils", "2.0");
        ModuleGraph.ModuleNode database = graph.addModule("com.example.database", "1.0");
        ModuleGraph.ModuleNode security = graph.addModule("com.example.security", "1.0");
        
        // 建立依赖关系
        app.addDependency(utilsV1);
        app.addDependency(database);
        database.addDependency(utilsV2);
        security.addDependency(utilsV1);
        
        // 解析冲突
        List<Conflict> conflicts = graph.resolveConflicts();
        
        System.out.println("发现的依赖冲突:");
        conflicts.forEach(System.out::println);
        
        System.out.println("\n可能的解决方案:");
        System.out.println("1. 升级所有模块使用相同版本的com.example.utils");
        System.out.println("2. 使用模块重命名(--patch-module)");
        System.out.println("3. 使用自定义类加载器隔离");
        System.out.println("4. 重构模块避免传递依赖");
        
        // 演示模块层(ModuleLayer)实现隔离
        System.out.println("\n=== 使用ModuleLayer实现隔离 ===");
        demonstrateModuleLayer();
    }
    
    private static void demonstrateModuleLayer() {
        try {
            // 创建父层(基础模块)
            ModuleLayer parent = ModuleLayer.boot();
            
            // 配置模块查找器
            Path modulesDir = Paths.get("modules");
            ModuleFinder finder = ModuleFinder.of(modulesDir);
            
            // 解析模块
            Configuration config = parent.configuration()
                .resolve(finder, ModuleFinder.of(), Set.of("com.example.app"));
            
            // 创建新的模块层
            ModuleLayer layer = parent.defineModulesWithOneLoader(
                config, ClassLoader.getSystemClassLoader());
            
            // 在新层中查找模块
            Optional<Module> appModule = layer.findModule("com.example.app");
            if (appModule.isPresent()) {
                System.out.println("成功加载模块: " + appModule.get().getName());
                
                // 加载模块中的类
                Class<?> mainClass = Class.forName(
                    appModule.get(), "com.example.app.Main");
                
                // 调用方法
                Method mainMethod = mainClass.getMethod("main", String[].class);
                mainMethod.invoke(null, (Object) new String[]{});
            }
            
        } catch (Exception e) {
            System.err.println("模块层创建失败: " + e.getMessage());
        }
    }
}

三、服务加载器与SPI增强

3.1 模块化服务提供者接口

java 复制代码
// 服务提供者接口定义模块
module com.example.storage.spi {
    exports com.example.storage.spi;
    
    // 定义服务接口
    public interface StorageService {
        void save(String key, byte[] data);
        byte[] load(String key);
        boolean exists(String key);
    }
}

// 文件存储实现模块
module com.example.storage.file {
    requires com.example.storage.spi;
    provides com.example.storage.spi.StorageService 
        with com.example.storage.file.FileStorageService;
    
    // 实现类
    class FileStorageService implements StorageService {
        private final Path basePath;
        
        public FileStorageService() {
            this.basePath = Paths.get("storage");
            Files.createDirectories(basePath);
        }
        
        public void save(String key, byte[] data) {
            Path filePath = basePath.resolve(key);
            Files.write(filePath, data);
        }
        
        public byte[] load(String key) {
            Path filePath = basePath.resolve(key);
            return Files.readAllBytes(filePath);
        }
        
        public boolean exists(String key) {
            return Files.exists(basePath.resolve(key));
        }
    }
}

// 数据库存储实现模块
module com.example.storage.database {
    requires com.example.storage.spi;
    requires java.sql;
    provides com.example.storage.spi.StorageService 
        with com.example.storage.database.DatabaseStorageService;
    
    // 实现类
    class DatabaseStorageService implements StorageService {
        private final Connection connection;
        
        public DatabaseStorageService() throws SQLException {
            this.connection = DriverManager.getConnection("jdbc:h2:mem:test");
            // 初始化表结构
            try (Statement stmt = connection.createStatement()) {
                stmt.execute("CREATE TABLE storage (key VARCHAR PRIMARY KEY, data BLOB)");
            }
        }
        
        public void save(String key, byte[] data) throws SQLException {
            String sql = "MERGE INTO storage(key, data) VALUES(?, ?)";
            try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
                pstmt.setString(1, key);
                pstmt.setBytes(2, data);
                pstmt.executeUpdate();
            }
        }
        
        public byte[] load(String key) throws SQLException {
            String sql = "SELECT data FROM storage WHERE key = ?";
            try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
                pstmt.setString(1, key);
                ResultSet rs = pstmt.executeQuery();
                if (rs.next()) {
                    return rs.getBytes("data");
                }
            }
            return null;
        }
        
        public boolean exists(String key) throws SQLException {
            String sql = "SELECT 1 FROM storage WHERE key = ?";
            try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
                pstmt.setString(1, key);
                ResultSet rs = pstmt.executeQuery();
                return rs.next();
            }
        }
    }
}

// 应用模块使用服务
module com.example.application {
    requires com.example.storage.spi;
    uses com.example.storage.spi.StorageService;
    
    // 动态服务加载器
    public class StorageManager {
        private final List<StorageService> services = new ArrayList<>();
        
        public StorageManager() {
            // 加载所有可用的存储服务
            ServiceLoader<StorageService> loader = 
                ServiceLoader.load(StorageService.class);
            
            for (StorageService service : loader) {
                services.add(service);
                System.out.println("加载存储服务: " + service.getClass().getName());
            }
            
            if (services.isEmpty()) {
                throw new IllegalStateException("未找到可用的存储服务");
            }
        }
        
        // 使用第一个可用的服务
        public StorageService getDefaultService() {
            return services.get(0);
        }
        
        // 根据条件选择服务
        public Optional<StorageService> findService(Predicate<StorageService> predicate) {
            return services.stream().filter(predicate).findFirst();
        }
    }
}

3.2 增强型服务注册与发现

java 复制代码
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;

// 增强的服务注册表
public class EnhancedServiceRegistry {
    
    // 服务描述符
    static class ServiceDescriptor<T> {
        private final Class<T> serviceType;
        private final String name;
        private final String version;
        private final int priority; // 优先级,数值越小优先级越高
        private final Map<String, String> metadata;
        
        ServiceDescriptor(Class<T> serviceType, String name, 
                         String version, int priority, 
                         Map<String, String> metadata) {
            this.serviceType = serviceType;
            this.name = name;
            this.version = version;
            this.priority = priority;
            this.metadata = Map.copyOf(metadata);
        }
        
        public Class<T> getServiceType() { return serviceType; }
        public String getName() { return name; }
        public String getVersion() { return version; }
        public int getPriority() { return priority; }
        public Map<String, String> getMetadata() { return metadata; }
    }
    
    // 服务实例包装器
    static class ServiceInstance<T> {
        private final ServiceDescriptor<T> descriptor;
        private final T instance;
        private final long registrationTime;
        private volatile long lastUsedTime;
        
        ServiceInstance(ServiceDescriptor<T> descriptor, T instance) {
            this.descriptor = descriptor;
            this.instance = instance;
            this.registrationTime = System.currentTimeMillis();
            this.lastUsedTime = registrationTime;
        }
        
        public T getInstance() {
            lastUsedTime = System.currentTimeMillis();
            return instance;
        }
        
        public ServiceDescriptor<T> getDescriptor() { return descriptor; }
        public long getAge() { return System.currentTimeMillis() - registrationTime; }
        public long getIdleTime() { return System.currentTimeMillis() - lastUsedTime; }
    }
    
    // 模块感知的服务注册表
    public static class ModuleAwareServiceRegistry {
        private final Map<Class<?>, List<ServiceInstance<?>>> registry = new ConcurrentHashMap<>();
        private final Map<String, Module> moduleCache = new ConcurrentHashMap<>();
        
        // 注册服务(自动发现模块信息)
        public <T> void register(Class<T> serviceType, T instance) {
            register(serviceType, instance, createDescriptor(serviceType, instance));
        }
        
        public <T> void register(Class<T> serviceType, T instance, 
                                 ServiceDescriptor<T> descriptor) {
            ServiceInstance<T> serviceInstance = new ServiceInstance<>(descriptor, instance);
            
            registry.computeIfAbsent(serviceType, k -> new CopyOnWriteArrayList<>())
                   .add(serviceInstance);
            
            // 按优先级排序
            registry.get(serviceType).sort(
                Comparator.comparingInt(si -> ((ServiceInstance<T>)si).getDescriptor().getPriority())
            );
            
            System.out.printf("注册服务: %s [%s v%s]%n",
                serviceType.getSimpleName(),
                descriptor.getName(),
                descriptor.getVersion());
        }
        
        // 自动创建服务描述符
        private <T> ServiceDescriptor<T> createDescriptor(Class<T> serviceType, T instance) {
            Module module = instance.getClass().getModule();
            String moduleName = module.getName();
            
            // 尝试从模块注解获取元数据
            String name = moduleName != null ? moduleName : serviceType.getSimpleName();
            String version = "1.0.0";
            int priority = 100;
            
            if (module != null && module.isNamed()) {
                // 检查模块注解
                Optional<Object> moduleVersion = module.getDescriptor().version();
                if (moduleVersion.isPresent()) {
                    version = moduleVersion.get().toString();
                }
            }
            
            Map<String, String> metadata = new HashMap<>();
            metadata.put("module", moduleName != null ? moduleName : "unnamed");
            metadata.put("class", instance.getClass().getName());
            
            return new ServiceDescriptor<>(serviceType, name, version, priority, metadata);
        }
        
        // 获取服务(支持过滤和选择策略)
        public <T> Optional<T> getService(Class<T> serviceType) {
            return getService(serviceType, descriptor -> true);
        }
        
        public <T> Optional<T> getService(Class<T> serviceType, 
                                         Predicate<ServiceDescriptor<T>> filter) {
            return getService(serviceType, filter, instances -> {
                // 默认选择策略:最高优先级
                return instances.stream()
                    .min(Comparator.comparingInt(si -> si.getDescriptor().getPriority()))
                    .orElse(null);
            });
        }
        
        public <T> Optional<T> getService(Class<T> serviceType,
                                         Predicate<ServiceDescriptor<T>> filter,
                                         Function<List<ServiceInstance<T>>, 
                                                 ServiceInstance<T>> selector) {
            List<ServiceInstance<?>> instances = registry.get(serviceType);
            if (instances == null || instances.isEmpty()) {
                return Optional.empty();
            }
            
            @SuppressWarnings("unchecked")
            List<ServiceInstance<T>> typedInstances = (List<ServiceInstance<T>>) instances;
            
            // 应用过滤器
            List<ServiceInstance<T>> filtered = typedInstances.stream()
                .filter(si -> filter.test(si.getDescriptor()))
                .collect(Collectors.toList());
            
            if (filtered.isEmpty()) {
                return Optional.empty();
            }
            
            // 应用选择策略
            ServiceInstance<T> selected = selector.apply(filtered);
            return selected != null ? 
                Optional.of(selected.getInstance()) : Optional.empty();
        }
        
        // 获取所有服务实例
        public <T> List<T> getAllServices(Class<T> serviceType) {
            List<ServiceInstance<?>> instances = registry.get(serviceType);
            if (instances == null) {
                return Collections.emptyList();
            }
            
            @SuppressWarnings("unchecked")
            List<ServiceInstance<T>> typedInstances = (List<ServiceInstance<T>>) instances;
            
            return typedInstances.stream()
                .map(ServiceInstance::getInstance)
                .collect(Collectors.toList());
        }
        
        // 动态服务发现(通过ServiceLoader)
        public <T> void discoverServices(Class<T> serviceType) {
            ServiceLoader<T> loader = ServiceLoader.load(serviceType);
            for (T service : loader) {
                register(serviceType, service);
            }
        }
        
        // 按模块卸载服务
        public void unloadServicesFromModule(String moduleName) {
            for (List<ServiceInstance<?>> services : registry.values()) {
                services.removeIf(service -> {
                    String serviceModule = service.getDescriptor()
                        .getMetadata().get("module");
                    return moduleName.equals(serviceModule);
                });
            }
            System.out.println("已卸载模块 " + moduleName + " 的所有服务");
        }
        
        // 健康检查和清理
        public void cleanup(long maxIdleTimeMs) {
            long cleaned = 0;
            for (List<ServiceInstance<?>> services : registry.values()) {
                cleaned += services.removeIf(
                    service -> service.getIdleTime() > maxIdleTimeMs
                );
            }
            if (cleaned > 0) {
                System.out.println("清理了 " + cleaned + " 个空闲服务");
            }
        }
    }
    
    public static void main(String[] args) {
        System.out.println("=== 增强型服务注册表演示 ===\n");
        
        ModuleAwareServiceRegistry registry = new ModuleAwareServiceRegistry();
        
        // 定义服务接口
        interface Calculator {
            double calculate(double a, double b);
            String getName();
        }
        
        // 注册一些服务实现
        registry.register(Calculator.class, new Calculator() {
            public double calculate(double a, double b) { return a + b; }
            public String getName() { return "加法计算器"; }
        });
        
        registry.register(Calculator.class, new Calculator() {
            public double calculate(double a, double b) { return a * b; }
            public String getName() { return "乘法计算器"; }
        });
        
        // 使用自定义描述符注册
        ServiceDescriptor<Calculator> advancedDesc = new ServiceDescriptor<>(
            Calculator.class, 
            "科学计算器", 
            "2.0.0", 
            50, // 更高优先级
            Map.of("category", "scientific", "author", "MathLab")
        );
        
        registry.register(Calculator.class, new Calculator() {
            public double calculate(double a, double b) { return Math.pow(a, b); }
            public String getName() { return "幂运算计算器"; }
        }, advancedDesc);
        
        // 获取默认服务(最高优先级)
        Optional<Calculator> defaultCalc = registry.getService(Calculator.class);
        defaultCalc.ifPresent(calc -> {
            System.out.println("默认计算器: " + calc.getName());
            System.out.println("计算 2^3 = " + calc.calculate(2, 3));
        });
        
        // 使用过滤器获取特定服务
        Optional<Calculator> scientificCalc = registry.getService(
            Calculator.class,
            desc -> "scientific".equals(desc.getMetadata().get("category"))
        );
        
        scientificCalc.ifPresent(calc -> {
            System.out.println("\n科学计算器: " + calc.getName());
            System.out.println("计算 2^3 = " + calc.calculate(2, 3));
        });
        
        // 使用自定义选择策略(选择最旧的服务)
        Optional<Calculator> oldestCalc = registry.getService(
            Calculator.class,
            desc -> true,
            instances -> instances.stream()
                .max(Comparator.comparingLong(ServiceInstance::getAge))
                .orElse(null)
        );
        
        oldestCalc.ifPresent(calc -> 
            System.out.println("\n最旧的计算器: " + calc.getName()));
        
        // 获取所有服务
        System.out.println("\n所有可用计算器:");
        registry.getAllServices(Calculator.class).forEach(calc -> 
            System.out.println("  - " + calc.getName()));
        
        // 动态服务发现演示
        System.out.println("\n=== 动态服务发现 ===");
        registry.discoverServices(Calculator.class);
        
        // 清理空闲服务
        System.out.println("\n=== 服务清理 ===");
        registry.cleanup(60000); // 清理空闲超过60秒的服务
    }
}

四、模块化应用打包与部署

4.1 JLink:创建自定义运行时映像

java 复制代码
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

// JLink工具封装
public class JLinkPackager {
    
    // JLink配置
    static class JLinkConfig {
        String modulePath;
        List<String> modules;
        String outputDir;
        String launcherName;
        Map<String, String> launcherArgs;
        boolean compress = true;
        boolean stripDebug = true;
        boolean includeHeaderFiles = false;
        List<String> additionalOptions = new ArrayList<>();
        
        JLinkConfig(String modulePath, List<String> modules, String outputDir) {
            this.modulePath = modulePath;
            this.modules = modules;
            this.outputDir = outputDir;
        }
        
        // 生成JLink命令参数
        List<String> buildArguments() {
            List<String> args = new ArrayList<>();
            
            // 基本参数
            args.add("--module-path");
            args.add(modulePath);
            
            args.add("--add-modules");
            args.add(String.join(",", modules));
            
            // 输出目录
            args.add("--output");
            args.add(outputDir);
            
            // 可选参数
            if (compress) {
                args.add("--compress=2"); // 0=无, 1=常量字符串, 2=ZIP
            }
            
            if (stripDebug) {
                args.add("--strip-debug");
            }
            
            if (!includeHeaderFiles) {
                args.add("--no-header-files");
                args.add("--no-man-pages");
            }
            
            // 启动器配置
            if (launcherName != null && !modules.isEmpty()) {
                args.add("--launcher");
                String launcherCmd = launcherName + "=" + modules.get(0);
                if (launcherArgs != null && !launcherArgs.isEmpty()) {
                    launcherCmd += launcherArgs.entrySet().stream()
                        .map(e -> e.getKey() + "=" + e.getValue())
                        .reduce("", (a, b) -> a.isEmpty() ? b : a + " " + b);
                }
                args.add(launcherCmd);
            }
            
            // 额外选项
            args.addAll(additionalOptions);
            
            return args;
        }
    }
    
    // 自定义映像构建器
    public static class CustomRuntimeBuilder {
        private final JLinkConfig config;
        private final List<Path> moduleJars = new ArrayList<>();
        
        public CustomRuntimeBuilder(String outputDir) {
            this.config = new JLinkConfig("", new ArrayList<>(), outputDir);
        }
        
        // 添加模块JAR
        public CustomRuntimeBuilder addModule(Path moduleJar) {
            moduleJars.add(moduleJar);
            return this;
        }
        
        // 添加模块目录
        public CustomRuntimeBuilder addModuleDirectory(Path moduleDir) {
            try {
                Files.list(moduleDir)
                    .filter(p -> p.toString().endsWith(".jar"))
                    .forEach(moduleJars::add);
            } catch (Exception e) {
                throw new RuntimeException("无法读取模块目录: " + moduleDir, e);
            }
            return this;
        }
        
        // 设置模块路径
        public CustomRuntimeBuilder setModulePath(String modulePath) {
            config.modulePath = modulePath;
            return this;
        }
        
        // 添加模块
        public CustomRuntimeBuilder addModule(String moduleName) {
            config.modules.add(moduleName);
            return this;
        }
        
        // 设置启动器
        public CustomRuntimeBuilder withLauncher(String name, 
                                                Map<String, String> args) {
            config.launcherName = name;
            config.launcherArgs = args;
            return this;
        }
        
        // 构建运行时映像
        public Path build() throws Exception {
            // 如果未显式设置模块路径,则自动构建
            if (config.modulePath.isEmpty() && !moduleJars.isEmpty()) {
                config.modulePath = buildModulePath();
            }
            
            // 执行JLink
            ToolProvider jlink = ToolProvider.findFirst("jlink")
                .orElseThrow(() -> new RuntimeException("JLink工具不可用"));
            
            List<String> args = config.buildArguments();
            System.out.println("执行JLink命令:");
            System.out.println("jlink " + String.join(" ", args));
            
            // 创建输出目录
            Path outputPath = Paths.get(config.outputDir);
            if (Files.exists(outputPath)) {
                deleteDirectory(outputPath);
            }
            
            // 运行JLink
            int exitCode = jlink.run(System.out, System.err, 
                args.toArray(new String[0]));
            
            if (exitCode != 0) {
                throw new RuntimeException("JLink执行失败,退出码: " + exitCode);
            }
            
            // 验证生成的运行时
            validateRuntime(outputPath);
            
            return outputPath;
        }
        
        private String buildModulePath() {
            return moduleJars.stream()
                .map(Path::toString)
                .collect(Collectors.joining(File.pathSeparator));
        }
        
        private void validateRuntime(Path runtimeDir) throws Exception {
            System.out.println("\n验证运行时映像:");
            
            // 检查必要的目录
            Path binDir = runtimeDir.resolve("bin");
            Path libDir = runtimeDir.resolve("lib");
            
            if (!Files.exists(binDir) || !Files.exists(libDir)) {
                throw new RuntimeException("运行时映像结构不正确");
            }
            
            System.out.println("✓ 目录结构验证通过");
            
            // 检查可执行文件
            if (config.launcherName != null) {
                Path launcher = getLauncherPath(binDir, config.launcherName);
                if (Files.exists(launcher) && Files.isExecutable(launcher)) {
                    System.out.println("✓ 启动器文件创建成功: " + launcher);
                }
            }
            
            // 检查模块库
            try (DirectoryStream<Path> stream = 
                    Files.newDirectoryStream(libDir, "*.so")) {
                if (stream.iterator().hasNext()) {
                    System.out.println("✓ 本地库文件存在");
                }
            }
            
            // 计算映像大小
            long size = calculateDirectorySize(runtimeDir);
            System.out.printf("✓ 运行时映像大小: %.2f MB%n", size / 1024.0 / 1024.0);
        }
        
        private Path getLauncherPath(Path binDir, String launcherName) {
            String osName = System.getProperty("os.name").toLowerCase();
            String extension = osName.contains("win") ? ".exe" : "";
            return binDir.resolve(launcherName + extension);
        }
        
        private long calculateDirectorySize(Path dir) throws Exception {
            try (var stream = Files.walk(dir)) {
                return stream.filter(Files::isRegularFile)
                    .mapToLong(p -> {
                        try { return Files.size(p); }
                        catch (Exception e) { return 0L; }
                    })
                    .sum();
            }
        }
        
        private void deleteDirectory(Path dir) throws Exception {
            if (Files.exists(dir)) {
                try (var stream = Files.walk(dir)) {
                    stream.sorted(Comparator.reverseOrder())
                        .forEach(p -> {
                            try { Files.delete(p); }
                            catch (Exception e) { /* 忽略 */ }
                        });
                }
            }
        }
    }
    
    // 模块依赖分析器
    public static class ModuleDependencyAnalyzer {
        public static Set<String> analyzeDependencies(String mainModule, 
                                                     String modulePath) {
            Set<String> allModules = new LinkedHashSet<>();
            allModules.add(mainModule);
            
            // 递归收集传递依赖
            collectDependencies(mainModule, modulePath, allModules);
            
            return allModules;
        }
        
        private static void collectDependencies(String moduleName, 
                                               String modulePath, 
                                               Set<String> collected) {
            try {
                // 使用java命令分析模块
                ProcessBuilder pb = new ProcessBuilder(
                    "java",
                    "--module-path", modulePath,
                    "--module", "java.base/jdk.internal.module.ModuleAnalyzer",
                    moduleName
                );
                
                Process process = pb.start();
                String output = new String(process.getInputStream().readAllBytes());
                
                // 解析输出,获取依赖模块
                // 这里简化处理,实际需要解析详细的模块依赖信息
                String[] lines = output.split("\n");
                for (String line : lines) {
                    if (line.contains("requires") && !line.contains("java.base")) {
                        String depModule = extractModuleName(line);
                        if (depModule != null && !collected.contains(depModule)) {
                            collected.add(depModule);
                            collectDependencies(depModule, modulePath, collected);
                        }
                    }
                }
                
            } catch (Exception e) {
                System.err.println("模块依赖分析失败: " + e.getMessage());
            }
        }
        
        private static String extractModuleName(String line) {
            // 简单提取模块名,实际需要更复杂的解析
            String[] parts = line.trim().split("\s+");
            for (String part : parts) {
                if (!part.isEmpty() && !part.equals("requires") && 
                    !part.equals("transitive") && !part.equals("static")) {
                    return part;
                }
            }
            return null;
        }
    }
    
    public static void main(String[] args) throws Exception {
        System.out.println("=== JLink自定义运行时映像构建 ===\n");
        
        // 示例:构建一个简单的控制台应用运行时
        
        // 1. 准备模块
        Path modulesDir = Paths.get("modules");
        if (!Files.exists(modulesDir)) {
            Files.createDirectories(modulesDir);
            
            // 这里应该放置编译好的模块JAR文件
            // 例如:com.example.app.jar, com.example.utils.jar 等
        }
        
        // 2. 配置JLink
        CustomRuntimeBuilder builder = new CustomRuntimeBuilder("myruntime");
        
        builder.addModuleDirectory(modulesDir)
               .addModule("com.example.app") // 主模块
               .withLauncher("myapp", Map.of("main-class", 
                   "com.example.app.Main"));
        
        // 3. 分析依赖(可选)
        Set<String> dependencies = ModuleDependencyAnalyzer.analyzeDependencies(
            "com.example.app", 
            modulesDir.toString()
        );
        
        System.out.println("模块依赖分析结果:");
        dependencies.forEach(mod -> System.out.println("  - " + mod));
        
        // 4. 构建运行时
        Path runtime = builder.build();
        
        System.out.println("\n=== 运行时映像构建完成 ===");
        System.out.println("位置: " + runtime.toAbsolutePath());
        
        // 5. 创建启动脚本
        createLaunchScripts(runtime);
        
        // 6. 测试运行时
        testRuntime(runtime);
    }
    
    private static void createLaunchScripts(Path runtimeDir) throws Exception {
        Path binDir = runtimeDir.resolve("bin");
        
        // 创建Windows批处理脚本
        Path windowsScript = binDir.resolve("launch.bat");
        String windowsContent = "@echo off\n" +
            "setlocal\n" +
            "set JAVA_HOME=%~dp0..\n" +
            "set PATH=%%JAVA_HOME%%\bin;%%PATH%%\n" +
            "java --module-path ..\lib --module com.example.app/com.example.app.Main %%*\n";
        Files.writeString(windowsScript, windowsContent);
        
        // 创建Unix/Linux Shell脚本
        Path unixScript = binDir.resolve("launch.sh");
        String unixContent = "#!/bin/bash\n" +
            "DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"\n" +
            "export JAVA_HOME="$DIR/.."\n" +
            "export PATH="$JAVA_HOME/bin:$PATH"\n" +
            "java --module-path ../lib --module com.example.app/com.example.app.Main "$@"\n";
        Files.writeString(unixScript, unixContent);
        Files.setPosixFilePermissions(unixScript, 
            Set.of(PosixFilePermission.OWNER_EXECUTE,
                   PosixFilePermission.OWNER_READ,
                   PosixFilePermission.OWNER_WRITE));
        
        System.out.println("\n启动脚本已创建:");
        System.out.println("  Windows: launch.bat");
        System.out.println("  Unix/Linux: launch.sh");
    }
    
    private static void testRuntime(Path runtimeDir) throws Exception {
        System.out.println("\n=== 测试运行时映像 ===");
        
        Path javaExecutable;
        String osName = System.getProperty("os.name").toLowerCase();
        
        if (osName.contains("win")) {
            javaExecutable = runtimeDir.resolve("bin").resolve("java.exe");
        } else {
            javaExecutable = runtimeDir.resolve("bin").resolve("java");
        }
        
        if (!Files.exists(javaExecutable)) {
            System.err.println("Java可执行文件不存在: " + javaExecutable);
            return;
        }
        
        // 测试Java版本
        ProcessBuilder pb = new ProcessBuilder(
            javaExecutable.toString(),
            "--version"
        );
        
        Process process = pb.start();
        String output = new String(process.getInputStream().readAllBytes());
        
        System.out.println("Java版本信息:");
        System.out.println(output);
        
        // 测试模块列表
        pb = new ProcessBuilder(
            javaExecutable.toString(),
            "--list-modules"
        );
        
        process = pb.start();
        output = new String(process.getInputStream().readAllBytes());
        
        System.out.println("包含的模块:");
        System.out.println(output);
        
        System.out.println("✓ 运行时映像测试通过");
    }
}

五、模块化迁移策略与最佳实践

5.1 从传统应用到模块化应用的迁移

java 复制代码
import java.nio.file.*;
import java.util.*;

// 模块化迁移助手
public class ModularMigrationHelper {
    
    // 迁移步骤跟踪器
    static class MigrationTracker {
        private final Path projectRoot;
        private final Map<String, MigrationStatus> modules = new HashMap<>();
        private final List<String> migrationSteps = Arrays.asList(
            "分析现有依赖",
            "创建模块描述符",
            "解决依赖冲突",
            "重构包结构",
            "测试模块隔离",
            "构建模块化JAR",
            "创建运行时映像"
        );
        
        enum MigrationStatus {
            NOT_STARTED,
            ANALYZING,
            IN_PROGRESS,
            COMPLETED,
            BLOCKED
        }
        
        MigrationTracker(Path projectRoot) {
            this.projectRoot = projectRoot;
        }
        
        // 分析项目结构
        public ProjectAnalysis analyze() throws Exception {
            ProjectAnalysis analysis = new ProjectAnalysis();
            
            // 扫描项目目录
            try (var stream = Files.walk(projectRoot)) {
                stream.filter(Files::isRegularFile)
                    .filter(p -> p.toString().endsWith(".java"))
                    .forEach(p -> analysis.addJavaFile(p));
                
                stream.filter(Files::isRegularFile)
                    .filter(p -> p.toString().endsWith(".jar"))
                    .forEach(p -> analysis.addJarFile(p));
            }
            
            // 分析依赖
            analysis.analyzeDependencies();
            
            return analysis;
        }
        
        // 生成迁移计划
        public MigrationPlan generatePlan(ProjectAnalysis analysis) {
            MigrationPlan plan = new MigrationPlan();
            
            // 识别潜在的模块
            Set<String> packageRoots = analysis.getPackageRoots();
            for (String pkgRoot : packageRoots) {
                MigrationModule module = new MigrationModule(pkgRoot);
                
                // 分析模块依赖
                Set<String> dependencies = analysis.getDependenciesForPackage(pkgRoot);
                module.setDependencies(dependencies);
                
                // 确定迁移优先级
                int priority = calculatePriority(pkgRoot, dependencies);
                module.setPriority(priority);
                
                plan.addModule(module);
            }
            
            // 排序模块(从依赖少的开始)
            plan.sortModules();
            
            return plan;
        }
        
        private int calculatePriority(String pkgRoot, Set<String> dependencies) {
            // 依赖越少,优先级越高(越容易迁移)
            int dependencyCount = dependencies.size();
            
            // 核心包优先级更高
            if (pkgRoot.startsWith("com.example.core")) {
                return 1;
            }
            
            return Math.max(2, 10 - dependencyCount);
        }
    }
    
    static class ProjectAnalysis {
        private final List<Path> javaFiles = new ArrayList<>();
        private final List<Path> jarFiles = new ArrayList<>();
        private final Map<String, Set<String>> packageDependencies = new HashMap<>();
        
        void addJavaFile(Path file) { javaFiles.add(file); }
        void addJarFile(Path file) { jarFiles.add(file); }
        
        void analyzeDependencies() throws Exception {
            // 简化的依赖分析
            for (Path javaFile : javaFiles) {
                String content = Files.readString(javaFile);
                String packageName = extractPackageName(content);
                
                if (packageName != null) {
                    Set<String> imports = extractImports(content);
                    packageDependencies.put(packageName, imports);
                }
            }
        }
        
        Set<String> getPackageRoots() {
            Set<String> roots = new HashSet<>();
            for (String pkg : packageDependencies.keySet()) {
                String root = pkg.split("\.")[0]; // 简单的根包提取
                roots.add(root);
            }
            return roots;
        }
        
        Set<String> getDependenciesForPackage(String pkgRoot) {
            Set<String> deps = new HashSet<>();
            for (Map.Entry<String, Set<String>> entry : packageDependencies.entrySet()) {
                if (entry.getKey().startsWith(pkgRoot)) {
                    deps.addAll(entry.getValue());
                }
            }
            return deps;
        }
        
        private String extractPackageName(String content) {
            // 简单的包名提取
            int packageIndex = content.indexOf("package ");
            if (packageIndex >= 0) {
                int endIndex = content.indexOf(";", packageIndex);
                if (endIndex >= 0) {
                    return content.substring(packageIndex + 8, endIndex).trim();
                }
            }
            return null;
        }
        
        private Set<String> extractImports(String content) {
            Set<String> imports = new HashSet<>();
            String[] lines = content.split("\n");
            
            for (String line : lines) {
                line = line.trim();
                if (line.startsWith("import ")) {
                    String importStmt = line.substring(7, line.indexOf(";"));
                    imports.add(importStmt);
                }
            }
            
            return imports;
        }
    }
    
    static class MigrationPlan {
        private final List<MigrationModule> modules = new ArrayList<>();
        
        void addModule(MigrationModule module) {
            modules.add(module);
        }
        
        void sortModules() {
            modules.sort(Comparator.comparingInt(MigrationModule::getPriority));
        }
        
        public List<MigrationModule> getModules() {
            return Collections.unmodifiableList(modules);
        }
    }
    
    static class MigrationModule {
        private final String name;
        private Set<String> dependencies = new HashSet<>();
        private int priority;
        private String moduleInfoTemplate;
        
        MigrationModule(String name) {
            this.name = name;
        }
        
        void setDependencies(Set<String> dependencies) {
            this.dependencies = dependencies;
        }
        
        void setPriority(int priority) {
            this.priority = priority;
        }
        
        String getName() { return name; }
        Set<String> getDependencies() { return dependencies; }
        int getPriority() { return priority; }
        
        // 生成module-info.java模板
        String generateModuleInfo() {
            StringBuilder sb = new StringBuilder();
            sb.append("module ").append(name).append(" {\n");
            
            // 添加依赖
            for (String dep : dependencies) {
                // 简化处理:假设所有依赖都是模块
                sb.append("    requires ").append(extractModuleName(dep)).append(";\n");
            }
            
            // 自动导出包(根据实际包结构调整)
            sb.append("\n");
            sb.append("    exports ").append(name).append(";\n");
            
            sb.append("}\n");
            return sb.toString();
        }
        
        private String extractModuleName(String importStmt) {
            // 简化处理:从import语句提取模块名
            String[] parts = importStmt.split("\.");
            if (parts.length > 1) {
                return parts[0] + "." + parts[1];
            }
            return importStmt;
        }
    }
    
    // 自动迁移工具
    public static class AutoMigrator {
        
        public static void migrateProject(Path projectDir) throws Exception {
            System.out.println("开始迁移项目: " + projectDir);
            
            MigrationTracker tracker = new MigrationTracker(projectDir);
            
            // 1. 分析现有项目
            System.out.println("\n1. 分析项目结构...");
            ProjectAnalysis analysis = tracker.analyze();
            System.out.println("   找到 " + analysis.javaFiles.size() + " 个Java文件");
            System.out.println("   找到 " + analysis.jarFiles.size() + " 个JAR文件");
            
            // 2. 生成迁移计划
            System.out.println("\n2. 生成迁移计划...");
            MigrationPlan plan = tracker.generatePlan(analysis);
            
            System.out.println("   识别出 " + plan.getModules().size() + " 个潜在模块:");
            for (MigrationModule module : plan.getModules()) {
                System.out.printf("   - %s (优先级: %d, 依赖: %d)%n",
                    module.getName(), module.getPriority(), 
                    module.getDependencies().size());
            }
            
            // 3. 逐步迁移
            System.out.println("\n3. 开始迁移...");
            for (MigrationModule module : plan.getModules()) {
                migrateModule(projectDir, module);
            }
            
            System.out.println("\n迁移完成!");
            System.out.println("\n后续步骤:");
            System.out.println("1. 审查生成的module-info.java文件");
            System.out.println("2. 运行测试确保功能正常");
            System.out.println("3. 使用JLink创建自定义运行时");
            System.out.println("4. 优化模块边界和依赖");
        }
        
        private static void migrateModule(Path projectDir, MigrationModule module) 
                throws Exception {
            System.out.println("\n迁移模块: " + module.getName());
            
            // 创建模块目录
            Path moduleDir = projectDir.resolve(module.getName());
            Files.createDirectories(moduleDir);
            
            // 生成module-info.java
            Path moduleInfo = moduleDir.resolve("module-info.java");
            String content = module.generateModuleInfo();
            Files.writeString(moduleInfo, content);
            
            System.out.println("   创建 module-info.java:");
            System.out.println("   ```");
            System.out.println(content);
            System.out.println("   ```");
            
            // 移动源文件(简化示例)
            // 实际中需要根据包结构移动文件
            System.out.println("   提示:需要将包 " + module.getName() + 
                             " 下的源文件移动到模块目录中");
        }
    }
    
    // 兼容性包装器(用于混合模块/非模块环境)
    public static class CompatibilityLayer {
        
        // 自动模块检测器
        public static boolean isAutomaticModule(Path jarFile) {
            try {
                // 检查JAR是否包含module-info.class
                FileSystem fs = FileSystems.newFileSystem(jarFile, 
                    (ClassLoader) null);
                
                Path moduleInfo = fs.getPath("/", "module-info.class");
                boolean isModule = Files.exists(moduleInfo);
                
                fs.close();
                return !isModule; // 没有module-info.class的是自动模块
            } catch (Exception e) {
                // 读取失败,假设是自动模块
                return true;
            }
        }
        
        // 为传统JAR生成自动模块名称
        public static String generateAutomaticModuleName(Path jarFile) {
            String fileName = jarFile.getFileName().toString();
            
            // 移除.jar扩展名
            String baseName = fileName.substring(0, fileName.length() - 4);
            
            // 处理版本号(如myapp-1.2.3.jar -> myapp)
            baseName = baseName.replaceAll("-\d+(\.\d+)*", "");
            
            // 转换为有效的模块名(替换非法字符)
            baseName = baseName.replaceAll("[^A-Za-z0-9.]", ".");
            baseName = baseName.replaceAll("\.+", ".");
            baseName = baseName.replaceAll("^\.|\.$", "");
            
            // 确保至少有一个点
            if (!baseName.contains(".")) {
                baseName = "auto." + baseName;
            }
            
            return baseName;
        }
        
        // 创建桥接模块(连接模块化和非模块化代码)
        public static String createBridgeModule(String moduleName, 
                                               Set<String> automaticModules) {
            StringBuilder sb = new StringBuilder();
            sb.append("module ").append(moduleName).append(" {\n");
            sb.append("    // 桥接模块:连接模块化和传统代码\n");
            sb.append("    requires java.base;\n");
            sb.append("\n");
            
            for (String autoModule : automaticModules) {
                sb.append("    requires ").append(autoModule).append(";\n");
            }
            
            sb.append("\n");
            sb.append("    // 重新导出所有包\n");
            sb.append("    exports * to all;\n");
            sb.append("    opens * to all;\n");
            
            sb.append("}\n");
            return sb.toString();
        }
    }
    
    public static void main(String[] args) throws Exception {
        System.out.println("=== Java模块化迁移助手 ===\n");
        
        if (args.length == 0) {
            System.out.println("用法: java ModularMigrationHelper <项目目录>");
            System.out.println("示例: java ModularMigrationHelper ./myproject");
            return;
        }
        
        Path projectDir = Paths.get(args[0]);
        if (!Files.exists(projectDir)) {
            System.err.println("目录不存在: " + projectDir);
            return;
        }
        
        // 演示自动迁移
        AutoMigrator.migrateProject(projectDir);
        
        // 演示兼容性处理
        System.out.println("\n=== 兼容性处理演示 ===");
        
        // 扫描项目中的JAR文件
        try (var stream = Files.walk(projectDir)) {
            List<Path> jarFiles = stream
                .filter(p -> p.toString().endsWith(".jar"))
                .collect(Collectors.toList());
            
            System.out.println("\n分析传统JAR文件:");
            for (Path jarFile : jarFiles) {
                boolean isAutoModule = CompatibilityLayer.isAutomaticModule(jarFile);
                String moduleName = CompatibilityLayer.generateAutomaticModuleName(jarFile);
                
                System.out.printf("  %s: %s (自动模块名: %s)%n",
                    jarFile.getFileName(),
                    isAutoModule ? "自动模块" : "显式模块",
                    moduleName);
            }
        }
        
        System.out.println("\n迁移最佳实践:");
        System.out.println("1. 从下到上迁移(依赖少的模块先迁移)");
        System.out.println("2. 使用自动模块作为过渡");
        System.out.println("3. 逐步替换传统依赖为模块化版本");
        System.out.println("4. 使用桥接模块处理复杂的依赖关系");
        System.out.println("5. 利用JLink优化部署包大小");
    }
}

总结:模块化编程的核心价值

模块化带来的好处:

  1. 强封装性:明确API边界,隐藏内部实现
  2. 可靠的配置:编译时检查依赖,避免运行时错误
  3. 更好的可维护性:模块化架构更清晰,便于理解和维护
  4. 改进的性能:JLink可以创建更小的运行时映像
  5. 增强的安全性:通过限制反射访问增强安全性

迁移路径建议:

  1. 评估阶段:分析现有项目的依赖关系
  2. 渐进迁移:使用自动模块作为过渡
  3. 模块化核心:先迁移基础模块和工具模块
  4. 全面模块化:逐步迁移所有模块
  5. 优化部署:使用JLink创建最小化运行时

重要工具和命令:

工具/命令 用途
javac --module-source-path 编译模块
java --module-path 运行模块化应用
jlink 创建自定义运行时映像
jmod 创建JMOD文件
jdeps 分析依赖关系
相关推荐
dddaidai1236 小时前
深入JVM(三):JVM执行引擎
java·jvm
Hui Baby6 小时前
saga文件使用
java
墨夶6 小时前
交易所安全保卫战:从冷钱包到零知识证明,让黑客连边都摸不着!
java·安全·区块链·零知识证明
山风wind6 小时前
Tomcat三步搭建局域网文件共享
java·tomcat
a努力。6 小时前
网易Java面试被问:偏向锁在什么场景下反而降低性能?如何关闭?
java·开发语言·后端·面试·架构·c#
小新1106 小时前
Spring boot 之 Hello World 番外:如何修改端口号
java·spring boot·后端
百花~6 小时前
Spring Boot 日志~
java·spring boot·后端
李白的粉6 小时前
基于springboot的火锅店管理系统(全套)
java·spring boot·毕业设计·课程设计·源代码·火锅店管理系统
狂奔小菜鸡6 小时前
Day32 | Java Stream流式编程详解
java·后端·java ee