杰瑞科技汇

java jar 多个main

要在一个 JAR 文件中支持多个 main 方法,你有以下几种主流的解决方案,每种方案都有其优缺点和适用场景。

java jar 多个main-图1
(图片来源网络,侵删)

核心问题回顾

当你运行 java -jar myapp.jar 时,JVM 会:

  1. 读取 myapp.jar 中的 META-INF/MANIFEST.MF 文件。
  2. 找到 Main-Class: com.example.MainApp 这一行。
  3. 加载 com.example.MainApp 类,并调用其 public static void main(String[] args) 方法。

如果你想运行另一个 main 方法,com.example.AnotherTool.main(),直接 java -jar 是行不通的。


使用 JAR 文件清单的 Main-Class 属性(不推荐,但可行)

你可以在构建时动态修改 MANIFEST.MF 文件,指定不同的 Main-Class

缺点:

java jar 多个main-图2
(图片来源网络,侵删)
  • 每次只能有一个默认的 Main-Class
  • 非常不灵活,每次想切换启动类都需要重新打包或修改 JAR 文件。
  • 容易出错,不推荐用于生产环境。

示例: 假设你有两个主类: com.example.MainApp.java com.example.DataProcessor.java

  1. 打包时指定 Main-Class:

    # 打包成 App.jar,默认启动 MainApp
    jar cvfe App.jar com.example.MainApp -C target/classes .
    # 打包成 Processor.jar,默认启动 DataProcessor
    jar cvfe Processor.jar com.example.DataProcessor -C target/classes .

    这样你就得到了两个不同的 JAR 文件。

  2. 修改现有 JAR 的 MANIFEST (不推荐): 你可以使用 jar 命令的 u (update) 选项来更新 MANIFEST。

    java jar 多个main-图3
    (图片来源网络,侵删)
    # 假设 App.jar 的 Main-Class 是 com.example.MainApp
    # 现在想把它改成 com.example.DataProcessor
    jar umf manifest.txt App.jar
    # manifest.txt 内容只有一行:
    # Main-Class: com.example.DataProcessor

    然后运行 java -jar App.jar 就会启动 DataProcessor

这种方法本质上是为每个入口点创建一个独立的 JAR,或者手动切换入口点,非常笨拙。


使用启动脚本(Shell/Batch Script) - 最简单直接的方法

这是最常见、最简单的解决方案,你创建一个脚本文件,它负责调用 java 命令,并通过 -cp (classpath) 参数来指定 JAR 文件,然后在命令行中直接指定要运行的类。

优点:

  • 简单,无需任何特殊构建工具配置。
  • 非常灵活,可以轻松切换任意主类。
  • 适用于任何标准的 Java 应用。

缺点:

  • 需要额外维护脚本文件。
  • JAR 依赖很多其他 JAR,-cp 参数会变得很长。

示例:

假设你的项目结构如下:

my-app/
├── lib/
│   ├── library1.jar
│   └── library2.jar
├── myapp.jar          (你的主程序 JAR)
└── start.sh           (启动脚本)
  1. 创建启动脚本 start.sh (Linux/macOS):

    #!/bin/bash
    # 设置类路径,包含主JAR和所有依赖的JAR
    # 注意: lib/*.jar 会匹配 lib 目录下所有 .jar 文件
    CP="myapp.jar:lib/*"
    # 运行指定的 main 方法
    # 格式: java -cp <classpath> <fully.qualified.ClassName> [args...]
    java -cp $CP com.example.MainApp --arg1 value1
    # 如果想运行另一个 main 方法,只需修改类名即可
    # java -cp $CP com.example.DataProcessor --input data.csv
  2. 创建启动脚本 start.bat (Windows):

    @echo off
    rem 设置类路径,Windows下用分号;分隔
    set CP="myapp.jar;lib\*"
    rem 运行指定的 main 方法
    java -cp %CP% com.example.MainApp --arg1 value1
    rem 如果想运行另一个 main 方法
    rem java -cp %CP% com.example.DataProcessor --input data.csv

如何使用:

  1. start.sh 添加可执行权限:chmod +x start.sh
  2. 直接运行脚本:./start.sh

使用构建工具(Maven/Gradle)的 exec 插件 - 开发时首选

如果你使用 Maven 或 Gradle,你可以利用它们的插件在开发或构建阶段直接运行任意 main 方法,而无需手动编写脚本。

优点:

  • 与构建流程深度集成。
  • 自动管理类路径,非常方便。
  • IDE(如 IntelliJ IDEA, Eclipse)通常也支持直接运行单个 main 方法。

缺点:

  • 主要用于开发和测试,生产环境部署仍可能需要方案二。

Maven 示例 (pom.xml)

确保你的 pom.xml 中有 maven-exec-plugin

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>3.1.0</version>
            <configuration>
                <mainClass>com.example.MainApp</mainClass>
                <!-- <arguments>
                    <argument>--arg1</argument>
                    <argument>value1</argument>
                </arguments> -->
            </configuration>
        </plugin>
    </plugins>
</build>

如何使用: 在项目根目录下运行 Maven 命令:

# 运行 pom.xml 中配置的 MainApp
mvn exec:java
# 运行另一个 main 方法 (通过命令行参数覆盖)
mvn exec:java -Dexec.mainClass="com.example.DataProcessor" -Dexec.args="--input data.csv"

Gradle 示例 (build.gradle)

Gradle 内置了 application 插件,或者你可以直接使用 JavaExec 任务。

方法 A: 使用 application 插件 (推荐)

plugins {
    id 'application'
}
application {
    // 设置默认的主类
    mainClass = 'com.example.MainApp'
    // 可以定义多个 "应用" 来管理不同的入口
    applicationDistribution.from('src/dist') {
        // ...
    }
}
// 定义一个独立的任务来运行另一个 main 类
task runDataProcessor(type: JavaExec) {
    classpath = sourceSets.main.runtimeClasspath
    main = 'com.example.DataProcessor'
    args '--input', 'data.csv'
}

如何使用: 在项目根目录下运行 Gradle 命令:

# 运行 application 插件中定义的默认 MainApp
gradle run
# 运行我们自定义的 runDataProcessor 任务
gradle runDataProcessor

将多个主类打包成可执行 JAR(Spring Boot 风格)

这种方法比较高级,它通过一个“引导”类(Bootstrap)来启动真正的应用,这个引导类会根据传入的参数来决定调用哪个 main 方法,你使用 maven-shade-plugingradle.shadow 插件将所有依赖和引导类打包成一个“胖”JAR(Fat JAR)。

优点:

  • 部署简单,只有一个 JAR 文件。
  • 用户体验好,通过命令行参数控制功能。
  • 类似于 Spring Boot 的 java -jar myapp.jar --command=process

缺点:

  • 实现相对复杂,需要编写引导逻辑。
  • 构建配置更繁琐。

示例实现:

  1. 创建一个引导类 Bootstrap.java

    package com.example;
    public class Bootstrap {
        public static void main(String[] args) {
            if (args.length == 0) {
                System.err.println("Usage: java -jar myapp.jar <command> [options]");
                System.err.println("Available commands: app, process");
                System.exit(1);
            }
            String command = args[0];
            String[] remainingArgs = new String[args.length - 1];
            System.arraycopy(args, 1, remainingArgs, 0, args.length - 1);
            switch (command) {
                case "app":
                    com.example.MainApp.main(remainingArgs);
                    break;
                case "process":
                    com.example.DataProcessor.main(remainingArgs);
                    break;
                default:
                    System.err.println("Unknown command: " + command);
                    System.exit(1);
            }
        }
    }
  2. 配置 Maven Shade Plugin (pom.xml)

    确保 Bootstrap 类是 Main-Class,并且它依赖的类都被包含进来。

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.example.Bootstrap</mainClass>
                                </transformer>
                            </transformers>
                            <!-- 避免包含重复的 META-INF 信息 -->
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
  3. 如何使用:

    打包后,你会得到一个 myapp.jar,运行方式如下:

    # 运行 MainApp
    java -jar myapp.jar app --arg1 value1
    # 运行 DataProcessor
    java -jar myapp.jar process --input data.csv

总结与选择建议

方案 优点 缺点 适用场景
修改 MANIFEST 简单概念 极不灵活,容易出错 快速临时测试,不推荐
启动脚本 简单直接,灵活 需维护脚本,-cp 可能很长 通用首选,生产环境部署,简单应用
构建工具插件 与构建集成,自动管理类路径 主要用于开发环境 开发调试,CI/CD 流程,Maven/Gradle 项目
引导类 + 胖 JAR 部署简单(单JAR),用户体验好 实现复杂,构建配置繁琐 需要提供统一命令行接口的复杂应用,类似 Spring Boot

给你的建议:

  • 对于大多数项目:方案二(启动脚本) 开始,它简单、可靠且足够灵活。
  • 如果你在用 Maven/Gradle 开发: 务必掌握 方案三(构建工具插件),这会极大提升你的开发效率。
  • 如果你的应用需要像一个“工具箱”一样被部署,并且只有一个入口点: 考虑 方案四(引导类),它能提供最优雅的最终用户体验。
分享:
扫描分享到社交APP
上一篇
下一篇