配置 spring-boot 生成 jar 包

背景

从 maven 到 springboot 我们介绍了怎么从一个空的 maven 项目,一步步配置成一个 spring-boot 项目。但是还留了一个问题没有讲,那就是当我们把项目编译成 jar 包后并运行不起来。

# 编译 OK{@class=h5 text-secondary mb-4}
mvn package

[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------< com.sqlpy:setup-spring-boot >---------------------
[INFO] Building setup-spring-boot 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:3.0.1:resources (default-resources) @ setup-spring-boot ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/jianglexing/temps/setup-spring-boot/src/main/resources
[INFO] skip non existing resourceDirectory /Users/jianglexing/temps/setup-spring-boot/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ setup-spring-boot ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/jianglexing/temps/setup-spring-boot/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:3.0.1:testResources (default-testResources) @ setup-spring-boot ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/jianglexing/temps/setup-spring-boot/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.7.0:testCompile (default-testCompile) @ setup-spring-boot ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/jianglexing/temps/setup-spring-boot/target/test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:2.21.0:test (default-test) @ setup-spring-boot ---
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.sqlpy.AppTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.019 s - in com.sqlpy.AppTest
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ setup-spring-boot ---
[INFO] Building jar: /Users/jianglexing/temps/setup-spring-boot/target/setup-spring-boot-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.076 s
[INFO] Finished at: 2021-09-04T13:46:18+08:00
[INFO] ------------------------------------------------------------------------

# 运行时报错{@class=h5 text-secondary mb-4}
jianglexing@NEEKYJIANG-MB2 setup-spring-boot % java -jar target/setup-spring-boot-1.0-SNAPSHOT.jar
target/setup-spring-boot-1.0-SNAPSHOT.jar中没有主清单属性


解决办法

spring 官方提供了一个编译插件,这个插件会帮我们配置 jar 的主类,所以我们只要改一下 POM 文件就行了。

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.sqlpy</groupId>
  <artifactId>setup-spring-boot</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>setup-spring-boot</name>
  <url>https://www.sqlpy.com</url>

  <parent>
    <artifactId>spring-boot-starter-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.0.2.RELEASE</version>
  </parent>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>2.0.2.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

spring-boot 编译后生成的 jar 文件

jar 本质上就是一个 zip 文件,就上面的 jar 包,我们解压后可以得到如下的目录结构。

tree .
.
├── BOOT-INF
│   ├── classes
│   │   └── com
│   │       └── sqlpy
│   │           └── App.class
│   └── lib
│       ├── classmate-1.3.4.jar
│       ├── hibernate-validator-6.0.9.Final.jar
│       ├── jackson-annotations-2.9.0.jar
│       ├── jackson-core-2.9.5.jar
│       ├── jackson-databind-2.9.5.jar
│       ├── jackson-datatype-jdk8-2.9.5.jar
│       ├── jackson-datatype-jsr310-2.9.5.jar
│       ├── jackson-module-parameter-names-2.9.5.jar
│       ├── javax.annotation-api-1.3.2.jar
│       ├── jboss-logging-3.3.2.Final.jar
│       ├── jul-to-slf4j-1.7.25.jar
│       ├── log4j-api-2.10.0.jar
│       ├── log4j-to-slf4j-2.10.0.jar
│       ├── logback-classic-1.2.3.jar
│       ├── logback-core-1.2.3.jar
│       ├── slf4j-api-1.7.25.jar
│       ├── snakeyaml-1.19.jar
│       ├── spring-aop-5.0.6.RELEASE.jar
│       ├── spring-beans-5.0.6.RELEASE.jar
│       ├── spring-boot-2.0.2.RELEASE.jar
│       ├── spring-boot-autoconfigure-2.0.2.RELEASE.jar
│       ├── spring-boot-starter-2.0.2.RELEASE.jar
│       ├── spring-boot-starter-json-2.0.2.RELEASE.jar
│       ├── spring-boot-starter-logging-2.0.2.RELEASE.jar
│       ├── spring-boot-starter-tomcat-2.0.2.RELEASE.jar
│       ├── spring-boot-starter-web-2.0.2.RELEASE.jar
│       ├── spring-context-5.0.6.RELEASE.jar
│       ├── spring-core-5.0.6.RELEASE.jar
│       ├── spring-expression-5.0.6.RELEASE.jar
│       ├── spring-jcl-5.0.6.RELEASE.jar
│       ├── spring-web-5.0.6.RELEASE.jar
│       ├── spring-webmvc-5.0.6.RELEASE.jar
│       ├── tomcat-embed-core-8.5.31.jar
│       ├── tomcat-embed-el-8.5.31.jar
│       ├── tomcat-embed-websocket-8.5.31.jar
│       └── validation-api-2.0.1.Final.jar
├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── com.sqlpy
│           └── setup-spring-boot
│               ├── pom.properties
│               └── pom.xml
└── org
    └── springframework
        └── boot
            └── loader
                ├── ExecutableArchiveLauncher.class
                ├── JarLauncher.class
                ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
                ├── LaunchedURLClassLoader.class
                ├── Launcher.class
                ├── MainMethodRunner.class
                ├── PropertiesLauncher$1.class
                ├── PropertiesLauncher$ArchiveEntryFilter.class
                ├── PropertiesLauncher$PrefixMatchingArchiveFilter.class
                ├── PropertiesLauncher.class
                ├── WarLauncher.class
                ├── archive
                │   ├── Archive$Entry.class
                │   ├── Archive$EntryFilter.class
                │   ├── Archive.class
                │   ├── ExplodedArchive$1.class
                │   ├── ExplodedArchive$FileEntry.class
                │   ├── ExplodedArchive$FileEntryIterator$EntryComparator.class
                │   ├── ExplodedArchive$FileEntryIterator.class
                │   ├── ExplodedArchive.class
                │   ├── JarFileArchive$EntryIterator.class
                │   ├── JarFileArchive$JarFileEntry.class
                │   └── JarFileArchive.class
                ├── data
                │   ├── RandomAccessData.class
                │   ├── RandomAccessDataFile$1.class
                │   ├── RandomAccessDataFile$DataInputStream.class
                │   ├── RandomAccessDataFile$FileAccess.class
                │   └── RandomAccessDataFile.class
                ├── jar
                │   ├── AsciiBytes.class
                │   ├── Bytes.class
                │   ├── CentralDirectoryEndRecord.class
                │   ├── CentralDirectoryFileHeader.class
                │   ├── CentralDirectoryParser.class
                │   ├── CentralDirectoryVisitor.class
                │   ├── FileHeader.class
                │   ├── Handler.class
                │   ├── JarEntry.class
                │   ├── JarEntryFilter.class
                │   ├── JarFile$1.class
                │   ├── JarFile$2.class
                │   ├── JarFile$JarFileType.class
                │   ├── JarFile.class
                │   ├── JarFileEntries$1.class
                │   ├── JarFileEntries$EntryIterator.class
                │   ├── JarFileEntries.class
                │   ├── JarURLConnection$1.class
                │   ├── JarURLConnection$JarEntryName.class
                │   ├── JarURLConnection.class
                │   ├── StringSequence.class
                │   └── ZipInflaterInputStream.class
                └── util
                    └── SystemPropertyUtils.class

META-INF/MANIFEST.MF 配置文件的内容如下

Manifest-Version: 1.0
Implementation-Title: setup-spring-boot
Implementation-Version: 1.0-SNAPSHOT
Built-By: jianglexing
Implementation-Vendor-Id: com.sqlpy
Spring-Boot-Version: 2.0.2.RELEASE
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.sqlpy.App
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.5.4
Build-Jdk: 1.8.0_282
Implementation-URL: https://www.sqlpy.com

可以看到 spring-boot 帮我们把 Main-Class 设置成了它内部的一个包 org.springframework.boot.loader.JarLauncher


在我们不添加编译插件的情况下 MANIFEST.MF 的内容是这样的。

Manifest-Version: 1.0
Implementation-Title: setup-spring-boot
Implementation-Version: 1.0-SNAPSHOT
Built-By: jianglexing
Implementation-Vendor-Id: com.sqlpyl
Created-By: Apache Maven 3.5.4
Build-Jdk: 1.8.0_282
Implementation-URL: https://www.sqlpy.com