Spring Initializr で作成したプロジェクトが生成する WAR ファイルの中身を調べる

## 概要

- [Spring Initializr](https://start.spring.io/) で Gradle と Maven のプロジェクトを作成し、生成した WAR ファイルの中身をしらべる

## Spring Initializr で指定する項目

- Java 11 + Spring Boot 2.2.6
- Packaging は WAR を指定
- Dependencies に Spring Web を追加

## プロジェクトファイル構成

Spring Initializr で作成したプロジェクトからテストコード等を削って以下のファイル構成にする。

### Gradle

```
├── build.gradle
├── settings.gradle
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           ├── MyappApplication.java
        │           └── ServletInitializer.java
        └── resources
            ├── application.properties
            ├── static
            └── templates
```

### Maven

```
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           ├── MyappApplication.java
        │           └── ServletInitializer.java
        └── resources
            ├── application.properties
            ├── static
            └── templates
```

### 差異

設定ファイル以外の中身は同じ。

```
$ diff -r gradle/myapp maven/myapp
Only in gradle/myapp: build.gradle
Only in maven/myapp: pom.xml
Only in gradle/myapp: settings.gradle
```

## ビルド用設定ファイル

外部ライブラリの Apache PDFBox を追加しておく。
これは Spring 以外の外部ライブラリが WAR ファイルにどのように格納されるかを調べるため。

### Gradle (build.gradle)

```groovy
plugins {
  id 'org.springframework.boot' version '2.2.6.RELEASE'
  id 'io.spring.dependency-management' version '1.0.9.RELEASE'
  id 'java'
  id 'war'
}

group = 'com.example'
version = '0.0.1'
sourceCompatibility = '11'

repositories {
  mavenCentral()
}

dependencies {
  // https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox
  implementation 'org.apache.pdfbox:pdfbox:2.0.19'
  implementation 'org.springframework.boot:spring-boot-starter-web'
  providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
}
```

### Maven (pom.xml)

```xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.6.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>myapp</artifactId>
  <version>0.0.1</version>
  <packaging>war</packaging>
  <name>myapp</name>
  <description>MyApp project</description>

  <properties>
    <java.version>11</java.version>
  </properties>

  <dependencies>
    <!-- https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox -->
    <dependency>
      <groupId>org.apache.pdfbox</groupId>
      <artifactId>pdfbox</artifactId>
      <version>2.0.19</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <scope>provided</scope>
    </dependency>
  </dependencies>

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

</project>
```

## プロジェクトのビルドと WAR ファイル展開

### Gradle

Gradle 6.3 を使用。

```
$ gradle build
```

```
$ unzip build/libs/myapp-0.0.1.war -d dest
```

### Maven

Apache Maven 3.6.3 を使用。

```
$ mvn package
```

```
$ unzip target/myapp-0.0.1.war -d dest
```

## WAR ファイル内のファイル構成

- ソースコードをコンパイルしてできたクラスファイルは WEB-INF/classes に入る
- Spring 関連の JAR ファイルは WEB-INF/lib に入る
- 外部ライブラリの JAR ファイルは WEB-INF/lib に入る (Apache PDFBox の pdfbox-2.0.19.jar が WEB-INF/lib にある)
- tomcat-embed-core-9.0.33.jar など WAR として不要だと思われるファイルは WEB-INF/lib-provided に入る
- Spring Boot Loader のクラスファイルが org/springframework/boot/loader に入る

[Spring Boot 実行可能な Jar 形式 \- ドキュメント](https://spring.pleiades.io/spring-boot/docs/current/reference/html/appendix-executable-jar-format.html)

> 依存関係は、ネストされた WEB-INF/lib ディレクトリに配置する必要があります。組み込みの実行時に必要であるが、従来の Web コンテナーにデプロイする場合には不要な依存関係は、WEB-INF/lib-provided に配置する必要があります。

[Spring Boot 実行可能な Jar 形式 \- ドキュメント](https://spring.pleiades.io/spring-boot/docs/current/reference/html/appendix-executable-jar-format.html)

> Java は、ネストされた jar ファイル（つまり、jar 内に含まれる jar ファイル）をロードする標準的な方法を提供しません。これは、コマンドラインから展開せずに実行できる自己完結型アプリケーションを配布する必要がある場合に問題になる可能性があります。
> 
> この問題を解決するために、多くの開発者は「シェーディングされた」jar ファイルを使用します。シェーディングされた jar は、すべての jar からのすべてのクラスを単一の「uber jar」にパッケージ化します。シェーディングされた jar ファイルの問題は、どのライブラリが実際にアプリケーションに含まれているかを見にくくなることです。また、複数の jar で同じファイル名（ただし、異なるコンテンツ）が使用されている場合、問題が発生する可能性があります。Spring Boot は異なるアプローチを採用しており、実際に jar を直接ネストできます。

### Gradle

```
$ tree dest
dest
├── META-INF
│   └── MANIFEST.MF
├── WEB-INF
│   ├── classes
│   │   ├── application.properties
│   │   ├── com
│   │   │   └── example
│   │   │       ├── MyappApplication.class
│   │   │       └── ServletInitializer.class
│   │   ├── static
│   │   └── templates
│   ├── lib
│   │   ├── classmate-1.5.1.jar
│   │   ├── commons-logging-1.2.jar
│   │   ├── fontbox-2.0.19.jar
│   │   ├── hibernate-validator-6.0.18.Final.jar
│   │   ├── jackson-annotations-2.10.3.jar
│   │   ├── jackson-core-2.10.3.jar
│   │   ├── jackson-databind-2.10.3.jar
│   │   ├── jackson-datatype-jdk8-2.10.3.jar
│   │   ├── jackson-datatype-jsr310-2.10.3.jar
│   │   ├── jackson-module-parameter-names-2.10.3.jar
│   │   ├── jakarta.validation-api-2.0.2.jar
│   │   ├── jboss-logging-3.4.1.Final.jar
│   │   ├── jul-to-slf4j-1.7.30.jar
│   │   ├── log4j-api-2.12.1.jar
│   │   ├── log4j-to-slf4j-2.12.1.jar
│   │   ├── logback-classic-1.2.3.jar
│   │   ├── logback-core-1.2.3.jar
│   │   ├── pdfbox-2.0.19.jar
│   │   ├── slf4j-api-1.7.30.jar
│   │   ├── snakeyaml-1.25.jar
│   │   ├── spring-aop-5.2.5.RELEASE.jar
│   │   ├── spring-beans-5.2.5.RELEASE.jar
│   │   ├── spring-boot-2.2.6.RELEASE.jar
│   │   ├── spring-boot-autoconfigure-2.2.6.RELEASE.jar
│   │   ├── spring-boot-starter-2.2.6.RELEASE.jar
│   │   ├── spring-boot-starter-json-2.2.6.RELEASE.jar
│   │   ├── spring-boot-starter-logging-2.2.6.RELEASE.jar
│   │   ├── spring-boot-starter-validation-2.2.6.RELEASE.jar
│   │   ├── spring-boot-starter-web-2.2.6.RELEASE.jar
│   │   ├── spring-context-5.2.5.RELEASE.jar
│   │   ├── spring-core-5.2.5.RELEASE.jar
│   │   ├── spring-expression-5.2.5.RELEASE.jar
│   │   ├── spring-jcl-5.2.5.RELEASE.jar
│   │   ├── spring-web-5.2.5.RELEASE.jar
│   │   └── spring-webmvc-5.2.5.RELEASE.jar
│   └── lib-provided
│       ├── jakarta.annotation-api-1.3.5.jar
│       ├── spring-boot-starter-tomcat-2.2.6.RELEASE.jar
│       ├── tomcat-embed-core-9.0.33.jar
│       ├── tomcat-embed-el-9.0.33.jar
│       └── tomcat-embed-websocket-9.0.33.jar
└── 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$1.class
                │   ├── CentralDirectoryEndRecord$Zip64End.class
                │   ├── CentralDirectoryEndRecord$Zip64Locator.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$2.class
                │   ├── JarURLConnection$CloseAction.class
                │   ├── JarURLConnection$JarEntryName.class
                │   ├── JarURLConnection.class
                │   ├── StringSequence.class
                │   └── ZipInflaterInputStream.class
                └── util
                    └── SystemPropertyUtils.class

17 directories, 99 files
```

### Maven

```
$ tree dest
dest
├── META-INF
│   ├── MANIFEST.MF
│   └── maven
│       └── com.example
│           └── myapp
│               ├── pom.properties
│               └── pom.xml
├── WEB-INF
│   ├── classes
│   │   ├── application.properties
│   │   └── com
│   │       └── example
│   │           ├── MyappApplication.class
│   │           └── ServletInitializer.class
│   ├── lib
│   │   ├── classmate-1.5.1.jar
│   │   ├── commons-logging-1.2.jar
│   │   ├── fontbox-2.0.19.jar
│   │   ├── hibernate-validator-6.0.18.Final.jar
│   │   ├── jackson-annotations-2.10.3.jar
│   │   ├── jackson-core-2.10.3.jar
│   │   ├── jackson-databind-2.10.3.jar
│   │   ├── jackson-datatype-jdk8-2.10.3.jar
│   │   ├── jackson-datatype-jsr310-2.10.3.jar
│   │   ├── jackson-module-parameter-names-2.10.3.jar
│   │   ├── jakarta.annotation-api-1.3.5.jar
│   │   ├── jakarta.validation-api-2.0.2.jar
│   │   ├── jboss-logging-3.4.1.Final.jar
│   │   ├── jul-to-slf4j-1.7.30.jar
│   │   ├── log4j-api-2.12.1.jar
│   │   ├── log4j-to-slf4j-2.12.1.jar
│   │   ├── logback-classic-1.2.3.jar
│   │   ├── logback-core-1.2.3.jar
│   │   ├── pdfbox-2.0.19.jar
│   │   ├── slf4j-api-1.7.30.jar
│   │   ├── snakeyaml-1.25.jar
│   │   ├── spring-aop-5.2.5.RELEASE.jar
│   │   ├── spring-beans-5.2.5.RELEASE.jar
│   │   ├── spring-boot-2.2.6.RELEASE.jar
│   │   ├── spring-boot-autoconfigure-2.2.6.RELEASE.jar
│   │   ├── spring-boot-starter-2.2.6.RELEASE.jar
│   │   ├── spring-boot-starter-json-2.2.6.RELEASE.jar
│   │   ├── spring-boot-starter-logging-2.2.6.RELEASE.jar
│   │   ├── spring-boot-starter-validation-2.2.6.RELEASE.jar
│   │   ├── spring-boot-starter-web-2.2.6.RELEASE.jar
│   │   ├── spring-context-5.2.5.RELEASE.jar
│   │   ├── spring-core-5.2.5.RELEASE.jar
│   │   ├── spring-expression-5.2.5.RELEASE.jar
│   │   ├── spring-jcl-5.2.5.RELEASE.jar
│   │   ├── spring-web-5.2.5.RELEASE.jar
│   │   └── spring-webmvc-5.2.5.RELEASE.jar
│   └── lib-provided
│       ├── spring-boot-starter-tomcat-2.2.6.RELEASE.jar
│       ├── tomcat-embed-core-9.0.33.jar
│       ├── tomcat-embed-el-9.0.33.jar
│       └── tomcat-embed-websocket-9.0.33.jar
└── 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$1.class
                │   ├── CentralDirectoryEndRecord$Zip64End.class
                │   ├── CentralDirectoryEndRecord$Zip64Locator.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$2.class
                │   ├── JarURLConnection$CloseAction.class
                │   ├── JarURLConnection$JarEntryName.class
                │   ├── JarURLConnection.class
                │   ├── StringSequence.class
                │   └── ZipInflaterInputStream.class
                └── util
                    └── SystemPropertyUtils.class

18 directories, 101 files
```

### 差異

大きくは変わらない。

```
$ diff -r gradle/myapp/dest maven/myapp/dest
diff -r gradle/myapp/dest/META-INF/MANIFEST.MF maven/myapp/dest/META-INF/MANIFEST.MF
1a2,5
> Created-By: Maven Archiver 3.4.0
> Build-Jdk-Spec: 11
> Implementation-Title: myapp
> Implementation-Version: 0.0.1
Only in maven/myapp/dest/META-INF: maven
Only in gradle/myapp/dest/WEB-INF/classes: static
Only in gradle/myapp/dest/WEB-INF/classes: templates
Only in maven/myapp/dest/WEB-INF/lib: jakarta.annotation-api-1.3.5.jar
Only in gradle/myapp/dest/WEB-INF/lib-provided: jakarta.annotation-api-1.3.5.jar
```