一、一个很真实的困惑:为什么 Java 这么多要“自己配”?

刚开始用 IDEA 写 Java 后端时,我第一反应其实是懵的:

  • 为什么新建 Java 项目没有 pom.xml
  • 为什么打出来的 jar 不能直接跑?
  • 为什么还要写什么 Main-Class
  • 为什么 Android 从来没纠结过这些?

后来一步一步走完这条链路,我才意识到:

Android 不是“不用配”,
而是工程体系已经被 Android Studio + Gradle 插件全部替你配好了
纯 Java + Maven 更接近“裸 JVM”,工程能力本身就是你要补齐的一层。

二、普通 Java 项目 vs Maven 工程:程序和工程的分水岭

IDEA 默认 Java 项目,本质只是“代码容器”:

src/Main.java
out/
.idea

它解决的是:

👉 代码能不能跑

但 Maven 工程解决的是:

👉 软件如何被构建、管理、交付

当项目里出现:

  • pom.xml
  • src/main/java
  • src/test/java
  • target/

这一步,其实已经完成了身份变化:

👉 从“写 Java 程序”
👉 变成“构建软件工程”。

三、mvn package 到底干了什么?

当我第一次执行:

mvn clean package

Maven 做了完整一条工程流水线:

  • 编译源码 → class
  • 编译测试 → test-class
  • 处理资源
  • 打包 → jar
  • 产物输出到 target/

此时出现的这个文件:

target/myFirstDemo-1.0-SNAPSHOT.jar

在工程意义上非常重要:

👉 这是交付物
👉 不是源码,不是 IDE 项目
👉 是可以被部署、分发、运行的软件实体

这一步,等价于 Android 的:

assembleRelease → 生成 apk/aab

四、为什么 jar 默认不能 java -jar 跑?

我第一次 java -jar xxx.jar 直接报:

没有主清单属性

原因只有一个:

👉 jar 里没有声明入口类

Java 规定:

java -jar 只认 jar 内部一个文件:

META-INF/MANIFEST.MF

里面必须有一行:

Main-Class: com.xxx.Main

否则 JVM 不知道从哪个 main() 开始执行。

五、maven-jar-plugin 那段配置,本质在干嘛?

<build>
  <plugins>
    <plugin>
      <artifactId>maven-jar-plugin</artifactId>
      <configuration>
        <archive>
          <manifest>
            <mainClass>com.wulong.myfirstdemo.Main</mainClass>
          </manifest>
        </archive>
      </configuration>
    </plugin>
  </plugins>
</build>

这段不是“魔法”。

它只干了一件事:

👉 在打 jar 时,往 META-INF/MANIFEST.MF 里写:

Main-Class: com.wulong.myfirstdemo.Main

结果就是:

👉 jar 从“库文件”升级成“应用程序”
👉 可以独立 java -jar 启动

六、这一步的 Android 对照非常清晰

Android Java 后端
apk / aab jar
assembleRelease mvn package
AndroidManifest.xml MANIFEST.MF
LAUNCHER Activity Main-Class
点图标启动 java -jar 启动

所以更严谨的类比是:

Main-Class 就是后端 jar 的“启动入口声明”,
和 AndroidManifest 里的 LAUNCHER 本质完全一样。

七、为什么 IDEA 点 ▶ 能跑,但 jar 不能跑?

这是一个非常容易误解的点。

IDEA 点 ▶ 运行:

👉 IDE 帮你指定了入口类
👉 用 classpath 启动 JVM
👉 跟 jar 本身没关系

而 java -jar 是:

👉 完全脱离 IDE
👉 只认 jar 自身携带的信息
👉 所以必须在 MANIFEST 里写入口

这也是:

👉 “能在 IDE 跑” ≠ “能交付”

八、MANIFEST.MF 在哪?我怎么验证?(工程必会)

Main-Class 并不是写在 pom.xml 里就“存在了”,
它真正存在的地方,是在 jar 包内部的一个固定文件中:

META-INF/MANIFEST.MF

这是 JVM 在执行:

java -jar xxx.jar

唯一会读取的入口说明文件

1️⃣ MANIFEST.MF 不在工程目录里,它在 jar 里面

你在项目目录中是看不到 MANIFEST.MF 的,因为它是 Maven 在 package 阶段动态生成并打进 jar 的。

它的真实位置永远是:

xxx.jar!/META-INF/MANIFEST.MF

2️⃣ 用一条命令把它“打印出来”(最推荐)

unzip -p /Users/lingpei/Desktop/myFirstDemo-1.0-SNAPSHOT.jar META-INF/MANIFEST.MF

你会看到类似:

Manifest-Version: 1.0
Main-Class: com.wulong.myfirstdemo.Main

这一步非常重要:

👉 这是验证交付物是否具备启动能力
👉 而不是“我感觉 pom 写对了”。

3️⃣ 如果你想“解剖 jar”来看结构

jar tf myFirstDemo-1.0-SNAPSHOT.jar | grep MANIFEST

看到:

META-INF/MANIFEST.MF

说明入口文件确实存在于产物中。

4️⃣ 这一步的工程意义

很多初学者以为:

“我在 pom 里写了 mainClass,所以 jar 能跑。”

但工程上真正严谨的说法是:

👉 Maven 把配置编译进了产物
👉 你必须在产物里验证它存在

这一步非常像 Android 里你去解包 apk,看:

AndroidManifest.xml

是否真的写进安装包。

5️⃣ 一个非常重要的结论

  • pom.xml:工程构建说明书

  • MANIFEST.MF:交付物运行说明书

java -jar 不看 pom,
只看 MANIFEST。

九、我这一轮真正补上的,不是 Maven,而是工程层

这条链路打通后,我对后端工程有了一个非常清晰的底座:

源码 → Maven → 编译 → 打包 → 产物 → 独立运行

这一步解决的从来不是语法,而是:

  • 工程交付形态
  • 构建系统角色
  • 入口与运行模型
  • IDE 与产物的边界

也终于理解了:

👉 Android 工程不是“简单”
👉 是工程复杂度被插件体系完全封装了
👉 后端更接近“直面工程本身”。

十、结语

很多人学后端,从 Controller 开始。
但我越来越确认一件事:

架构的起点不是接口,是工程。

而 Maven、jar、MANIFEST、Main-Class 这些看似“配置”的东西,
其实才是后端世界真正的地基。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐