概要
- Spring Initializr で 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)
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 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 形式 - ドキュメント
依存関係は、ネストされた WEB-INF/lib ディレクトリに配置する必要があります。組み込みの実行時に必要であるが、従来の Web コンテナーにデプロイする場合には不要な依存関係は、WEB-INF/lib-provided に配置する必要があります。
Spring Boot 実行可能な Jar 形式 - ドキュメント
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