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

核心问题回顾
当你运行 java -jar myapp.jar 时,JVM 会:
- 读取
myapp.jar中的META-INF/MANIFEST.MF文件。 - 找到
Main-Class: com.example.MainApp这一行。 - 加载
com.example.MainApp类,并调用其public static void main(String[] args)方法。
如果你想运行另一个 main 方法,com.example.AnotherTool.main(),直接 java -jar 是行不通的。
使用 JAR 文件清单的 Main-Class 属性(不推荐,但可行)
你可以在构建时动态修改 MANIFEST.MF 文件,指定不同的 Main-Class。
缺点:

- 每次只能有一个默认的
Main-Class。 - 非常不灵活,每次想切换启动类都需要重新打包或修改 JAR 文件。
- 容易出错,不推荐用于生产环境。
示例:
假设你有两个主类:
com.example.MainApp.java
com.example.DataProcessor.java
-
打包时指定 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 文件。
-
修改现有 JAR 的 MANIFEST (不推荐): 你可以使用
jar命令的u(update) 选项来更新 MANIFEST。
(图片来源网络,侵删)# 假设 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 (启动脚本)
-
创建启动脚本
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
-
创建启动脚本
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
如何使用:
- 给
start.sh添加可执行权限:chmod +x start.sh。 - 直接运行脚本:
./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-plugin 或 gradle.shadow 插件将所有依赖和引导类打包成一个“胖”JAR(Fat JAR)。
优点:
- 部署简单,只有一个 JAR 文件。
- 用户体验好,通过命令行参数控制功能。
- 类似于 Spring Boot 的
java -jar myapp.jar --command=process。
缺点:
- 实现相对复杂,需要编写引导逻辑。
- 构建配置更繁琐。
示例实现:
-
创建一个引导类
Bootstrap.javapackage 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); } } } -
配置 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> -
如何使用:
打包后,你会得到一个
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 开发: 务必掌握 方案三(构建工具插件),这会极大提升你的开发效率。
- 如果你的应用需要像一个“工具箱”一样被部署,并且只有一个入口点: 考虑 方案四(引导类),它能提供最优雅的最终用户体验。
