はじめての Spring Boot

最近の Spring Framework ベースのアプリケーション開発で欠かせないのが Spring Boot である。Spring Boot とは、公式によれば:

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”. We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need very little Spring configuration.

であるとのこと。Spring Boot にはいろんな機能が備わっているわけだが、これらはひとえに Spring Framework ベースのアプリケーションをかんたんに作り、かんたんに運用するために用意されている。それでいて出来上がるアプリケーションはオモチャのようなものではなく、紛れもない本物の Spring Framework ベースのアプリケーションである。

このページでは、まずは Spring Boot ベースのかんたんな Web アプリケーションを作ってみることにする。その後、少し話題を変えて、Spring Boot の中身を追ってみたい。今回は起動プロセスをかんたんに調べてみることにした。

プロジェクトを作る

Spring Initializr でプロジェクトのひな形を作ることができる。

ここでは

として、ひな形を作る。fortune.zip が落ちてくるので、適当な場所に展開する。

$ unzip fortune.zip
$ tree fortune
fortune
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── org
    │   │       └── genva
    │   │           └── FortuneApplication.java
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── org
                └── genva
                    └── FortuneApplicationTests.java

12 directories, 6 files

Eclipse 用のプロジェクト設定を生成する。

$ cd fortune
$ ./mvnw eclipse:eclipse

Maven をインストールしていなくても、そのラッパースクリプトである mvnw を使うことで、自動的に Maven が導入される。

起動する

プロジェクトを Eclipse で開き、 org.genva.fortune.FortuneApplication を Java Application として実行する。

15:58:55.047 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Included patterns for restart : []
15:58:55.051 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Excluded patterns for restart : [/spring-boot-starter/target/classes/, /spring-boot-autoconfigure/target/classes/, /spring-boot-starter-[\w-]+/, /spring-boot/target/classes/, /spring-boot-actuator/target/classes/, /spring-boot-devtools/target/classes/]
15:58:55.052 [main] DEBUG org.springframework.boot.devtools.restart.ChangeableUrls - Matching URLs for reloading : [file:/home/kazuki/sources/writing/spring/fortune/target/test-classes/, file:/home/kazuki/sources/writing/spring/fortune/target/classes/]

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.2.RELEASE)

2017-03-11 15:58:55.478  INFO 10029 --- [  restartedMain] org.genva.FortuneApplication             : Starting FortuneApplication on carrot with PID 10029 (/home/kazuki/sources/writing/spring/fortune/target/classes started by kazuki in /home/kazuki/sources/writing/spring/fortune)
2017-03-11 15:58:55.479  INFO 10029 --- [  restartedMain] org.genva.FortuneApplication             : No active profile set, falling back to default profiles: default
2017-03-11 15:58:55.571  INFO 10029 --- [  restartedMain] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@38bed225: startup date [Sat Mar 11 15:58:55 JST 2017]; root of context hierarchy
2017-03-11 15:58:57.303  INFO 10029 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)

(snip)

2017-03-11 15:58:58.780  INFO 10029 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-03-11 15:58:58.785  INFO 10029 --- [  restartedMain] org.genva.FortuneApplication             : Started FortuneApplication in 3.707 seconds (JVM running for 4.236)

最後のメッセージが表示されれば、アプリケーションの起動が完了している。組み込み Tomcat は :8080 で待ち構えていることが分かる。また、メインスレッドの名前が restartedMain になっていることから、spring-boot-devtools が効いていることが分かる。

コントローラを追加する

コントローラを追加し、HTTP 経由でアプリケーションを叩けるようにする。

@RestController
public class FortuneController {
    @GetMapping
    public String index() {
        return LocalDateTime.now().toString();
    }
}

クラスを追加したり、ファイルを保存したりすると、アプリケーションが自動的に再起動する様子がログから分かる。これは spring-boot-devtools によるもの。アプリケーションのクラスローダーと依存ライブラリのクラスローダーが分かれていて、アプリケーションのソースが更新されると、自動的にアプリケーションのクラスローダーだけが破棄、再読み込みされる。アプリケーションのクラスだけが再読み込みされるので、再起動にかかる時間は短く済む。

さて、コントローラにアクセスしてみよう。

$ curl http://localhost:8080/
2017-03-11T16:05:26.669
$ curl http://localhost:8080/
2017-03-11T16:05:46.557

現在日時が返ってきている。ちゃんと機能しているようだ。

fortune の出力を返すようにする

現在日時を返すだけではつまらないし、せっかくの Spring なので依存性注入も使いたい。そこで fortune (/usr/games/fortune) の出力を応答として返すようにする。このような業務処理 (fortune の出力を得ることを、業務と呼ぶべきかどうかはさておき) は、コントローラのようなプレゼンテーション層ではなくサービスなどのモデル層に書くべき処理である。今回はサービスクラスとして作ることにした。

@Service
public class FortuneService {
    public String get() {
        ProcessBuilder pb = new ProcessBuilder("/usr/games/fortune");
        Process p = null;
        try {
            p = pb.start();
            p.waitFor();
            try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
                return br.lines().collect(Collectors.joining("\n"));
            }
        }
        catch (IOException | InterruptedException e) {
            return "MOU DAMEPO";
        }
        finally {
            if (p != null) {
                closeQuietly(p.getErrorStream());
                closeQuietly(p.getOutputStream());
                closeQuietly(p.getInputStream());
                p.destroy();
            }
        }
    }
}

/usr/games/fortune のプロセスを起動し、その出力を得るだけのプログラムである。Java でのプロセスの扱いはとても面倒で、かつ間違いが起きやすいので、なるべくなら避けたいものである。closeQuietly は例外を握りつぶしつつ Closeable.close を呼ぶメソッドで、そういうユーティリティがあるものと思ってほしい。

これを使うよう、コントローラを修正する。層をまたいだ依存関係の設定は、外部 (ここでは Spring) にまかせてしまう (これが依存性注入と呼ばれるもの)。

@RestController
public class FortuneController {
    private FortuneService fortune;

    public FortuneController(FortuneService fortune) {
        this.fortune = fortune;
    }

    @GetMapping
    public String index() {
        return fortune.get();
    }
}

改めて curl で叩く。

$ curl http://localhost:8080/
You are not dead yet.  But watch for further reports.
$ curl http://localhost:8080/
Wagner's music is better than it sounds.
                -- Mark Twain

ありがたいお言葉が返ってくるようになった。

なお、最初に FortuneApplication を起動してから、fortune の結果を得られるようになるまで、Eclipse を使ったアプリケーションの再起動は一度も行わなかった。spring-boot-devtools は素晴らしい。

パッケージング、デプロイ

パッケージングは Maven で package すればおしまい。

$ ./mvnw package
$ file target/fortune-0.0.1-SNAPSHOT.jar
target/fortune-0.0.1-SNAPSHOT.jar: Java Jar file data (zip)

この jar ファイルは Maven の spring-boot プラグインによって後処理 (repackage) されたもので、特殊なレイアウトになっている。

$ unzip -l target/fortune-0.0.1-SNAPSHOT.jar
Archive:  target/fortune-0.0.1-SNAPSHOT.jar
  Length      Date    Time    Name
---------  ---------- -----   ----
        0  2017-03-11 17:03   META-INF/
      563  2017-03-11 17:03   META-INF/MANIFEST.MF
        0  2017-03-11 17:03   BOOT-INF/
        0  2017-03-11 17:03   BOOT-INF/classes/
        0  2017-03-11 16:30   BOOT-INF/classes/org/
        0  2017-03-11 16:30   BOOT-INF/classes/org/genva/
        0  2017-03-11 17:03   BOOT-INF/classes/application.properties
      717  2017-03-11 17:03   BOOT-INF/classes/org/genva/FortuneController.class
      156  2017-03-11 17:03   BOOT-INF/classes/org/genva/FortuneService.class
     2504  2017-03-11 17:03   BOOT-INF/classes/org/genva/UnixFortuneService.class
      699  2017-03-11 17:03   BOOT-INF/classes/org/genva/FortuneApplication.class
        0  2017-03-11 17:03   META-INF/maven/
        0  2017-03-11 17:03   META-INF/maven/org.genva/
        0  2017-03-11 17:03   META-INF/maven/org.genva/fortune/
     1571  2017-03-11 06:53   META-INF/maven/org.genva/fortune/pom.xml
      117  2017-03-11 17:03   META-INF/maven/org.genva/fortune/pom.properties
        0  2017-03-11 17:03   BOOT-INF/lib/
     2346  2017-03-03 15:47   BOOT-INF/lib/spring-boot-starter-web-1.5.2.RELEASE.jar
     2289  2017-03-03 15:46   BOOT-INF/lib/spring-boot-starter-1.5.2.RELEASE.jar
     2310  2017-03-03 15:46   BOOT-INF/lib/spring-boot-starter-logging-1.5.2.RELEASE.jar
   309130  2017-03-01 20:40   BOOT-INF/lib/logback-classic-1.1.11.jar
   475477  2017-03-01 20:39   BOOT-INF/lib/logback-core-1.1.11.jar
    16516  2017-02-24 12:09   BOOT-INF/lib/jcl-over-slf4j-1.7.24.jar
     4597  2017-02-24 12:09   BOOT-INF/lib/jul-to-slf4j-1.7.24.jar
    23647  2017-02-24 12:09   BOOT-INF/lib/log4j-over-slf4j-1.7.24.jar
   273599  2016-02-19 13:13   BOOT-INF/lib/snakeyaml-1.17.jar
     2294  2017-03-03 15:47   BOOT-INF/lib/spring-boot-starter-tomcat-1.5.2.RELEASE.jar
  3015953  2017-01-10 21:03   BOOT-INF/lib/tomcat-embed-core-8.5.11.jar
   239791  2017-01-10 21:03   BOOT-INF/lib/tomcat-embed-el-8.5.11.jar
   241640  2017-01-10 21:03   BOOT-INF/lib/tomcat-embed-websocket-8.5.11.jar
   725129  2016-12-08 10:48   BOOT-INF/lib/hibernate-validator-5.3.4.Final.jar
    63777  2013-04-10 15:02   BOOT-INF/lib/validation-api-1.1.0.Final.jar
    66802  2015-05-28 09:49   BOOT-INF/lib/jboss-logging-3.3.0.Final.jar
    64982  2016-09-27 22:24   BOOT-INF/lib/classmate-1.3.3.jar
  1237433  2017-02-21 01:07   BOOT-INF/lib/jackson-databind-2.8.7.jar
    55784  2016-07-03 22:20   BOOT-INF/lib/jackson-annotations-2.8.0.jar
   282314  2017-02-20 17:01   BOOT-INF/lib/jackson-core-2.8.7.jar
   817936  2017-03-01 08:34   BOOT-INF/lib/spring-web-4.3.7.RELEASE.jar
   380004  2017-03-01 08:32   BOOT-INF/lib/spring-aop-4.3.7.RELEASE.jar
   762747  2017-03-01 08:31   BOOT-INF/lib/spring-beans-4.3.7.RELEASE.jar
  1139269  2017-03-01 08:32   BOOT-INF/lib/spring-context-4.3.7.RELEASE.jar
   915615  2017-03-01 08:34   BOOT-INF/lib/spring-webmvc-4.3.7.RELEASE.jar
   263286  2017-03-01 08:32   BOOT-INF/lib/spring-expression-4.3.7.RELEASE.jar
   667710  2017-03-03 15:34   BOOT-INF/lib/spring-boot-1.5.2.RELEASE.jar
  1045757  2017-03-03 15:41   BOOT-INF/lib/spring-boot-autoconfigure-1.5.2.RELEASE.jar
    41205  2017-02-24 12:08   BOOT-INF/lib/slf4j-api-1.7.24.jar
  1118609  2017-03-01 08:31   BOOT-INF/lib/spring-core-4.3.7.RELEASE.jar
        0  2017-03-11 17:03   org/
        0  2017-03-11 17:03   org/springframework/
        0  2017-03-11 17:03   org/springframework/boot/
        0  2017-03-11 17:03   org/springframework/boot/loader/
     2415  2017-03-03 15:28   org/springframework/boot/loader/LaunchedURLClassLoader$1.class
     1454  2017-03-03 15:28   org/springframework/boot/loader/PropertiesLauncher$ArchiveEntryFilter.class
     1807  2017-03-03 15:28   org/springframework/boot/loader/PropertiesLauncher$PrefixMatchingArchiveFilter.class
     4599  2017-03-03 15:28   org/springframework/boot/loader/Launcher.class
     1165  2017-03-03 15:28   org/springframework/boot/loader/ExecutableArchiveLauncher$1.class
        0  2017-03-11 17:03   org/springframework/boot/loader/jar/
     2002  2017-03-03 15:28   org/springframework/boot/loader/jar/JarFile$1.class
     9657  2017-03-03 15:28   org/springframework/boot/loader/jar/Handler.class
     3350  2017-03-03 15:28   org/springframework/boot/loader/jar/JarEntry.class
     1427  2017-03-03 15:28   org/springframework/boot/loader/jar/JarFile$3.class
     2943  2017-03-03 15:28   org/springframework/boot/loader/jar/CentralDirectoryEndRecord.class
      430  2017-03-03 15:28   org/springframework/boot/loader/jar/CentralDirectoryVisitor.class
     1300  2017-03-03 15:28   org/springframework/boot/loader/jar/JarFile$JarFileType.class
    10924  2017-03-03 15:28   org/springframework/boot/loader/jar/JarFileEntries.class
    12697  2017-03-03 15:28   org/springframework/boot/loader/jar/JarFile.class
     1540  2017-03-03 15:28   org/springframework/boot/loader/jar/JarFileEntries$1.class
      672  2017-03-03 15:28   org/springframework/boot/loader/jar/JarURLConnection$1.class
     1199  2017-03-03 15:28   org/springframework/boot/loader/jar/JarFile$2.class
      262  2017-03-03 15:28   org/springframework/boot/loader/jar/JarEntryFilter.class
     4457  2017-03-03 15:28   org/springframework/boot/loader/jar/AsciiBytes.class
     4602  2017-03-03 15:28   org/springframework/boot/loader/jar/CentralDirectoryParser.class
     2169  2017-03-03 15:28   org/springframework/boot/loader/jar/Bytes.class
     1629  2017-03-03 15:28   org/springframework/boot/loader/jar/ZipInflaterInputStream.class
     1967  2017-03-03 15:28   org/springframework/boot/loader/jar/JarFileEntries$EntryIterator.class
      306  2017-03-03 15:28   org/springframework/boot/loader/jar/FileHeader.class
     3641  2017-03-03 15:28   org/springframework/boot/loader/jar/JarURLConnection$JarEntryName.class
     9111  2017-03-03 15:28   org/springframework/boot/loader/jar/JarURLConnection.class
     5449  2017-03-03 15:28   org/springframework/boot/loader/jar/CentralDirectoryFileHeader.class
        0  2017-03-11 17:03   org/springframework/boot/loader/data/
     1531  2017-03-03 15:28   org/springframework/boot/loader/data/ByteArrayRandomAccessData.class
     3534  2017-03-03 15:28   org/springframework/boot/loader/data/RandomAccessDataFile$DataInputStream.class
     2051  2017-03-03 15:28   org/springframework/boot/loader/data/RandomAccessDataFile$FilePool.class
     1341  2017-03-03 15:28   org/springframework/boot/loader/data/RandomAccessData$ResourceAccess.class
     3390  2017-03-03 15:28   org/springframework/boot/loader/data/RandomAccessDataFile.class
      551  2017-03-03 15:28   org/springframework/boot/loader/data/RandomAccessData.class
     4698  2017-03-03 15:28   org/springframework/boot/loader/LaunchedURLClassLoader.class
     1533  2017-03-03 15:28   org/springframework/boot/loader/JarLauncher.class
     1468  2017-03-03 15:28   org/springframework/boot/loader/MainMethodRunner.class
     1382  2017-03-03 15:28   org/springframework/boot/loader/PropertiesLauncher$1.class
     3128  2017-03-03 15:28   org/springframework/boot/loader/ExecutableArchiveLauncher.class
     1669  2017-03-03 15:28   org/springframework/boot/loader/WarLauncher.class
        0  2017-03-11 17:03   org/springframework/boot/loader/archive/
     1749  2017-03-03 15:28   org/springframework/boot/loader/archive/JarFileArchive$EntryIterator.class
     3792  2017-03-03 15:28   org/springframework/boot/loader/archive/ExplodedArchive$FileEntryIterator.class
     1068  2017-03-03 15:28   org/springframework/boot/loader/archive/ExplodedArchive$FileEntry.class
     1051  2017-03-03 15:28   org/springframework/boot/loader/archive/JarFileArchive$JarFileEntry.class
      302  2017-03-03 15:28   org/springframework/boot/loader/archive/Archive$Entry.class
     7016  2017-03-03 15:28   org/springframework/boot/loader/archive/JarFileArchive.class
     4974  2017-03-03 15:28   org/springframework/boot/loader/archive/ExplodedArchive.class
      906  2017-03-03 15:28   org/springframework/boot/loader/archive/Archive.class
     1438  2017-03-03 15:28   org/springframework/boot/loader/archive/ExplodedArchive$FileEntryIterator$EntryComparator.class
      399  2017-03-03 15:28   org/springframework/boot/loader/archive/Archive$EntryFilter.class
      273  2017-03-03 15:28   org/springframework/boot/loader/archive/ExplodedArchive$1.class
    17141  2017-03-03 15:28   org/springframework/boot/loader/PropertiesLauncher.class
        0  2017-03-11 17:03   org/springframework/boot/loader/util/
     4887  2017-03-03 15:28   org/springframework/boot/loader/util/SystemPropertyUtils.class
---------                     -------
 14428721                     107 files

依存ライブラリが jar ファイルのまま内包されていることが分かる。内包する jar ファイルをクラスパスに含めつつアプリケーションの main メソッド (ここでは FortuneApplication.main) を叩くため、spring-boot-loader のクラス群があらかじめ jar ファイル内に展開されている。

この jar ファイルを使ってアプリケーションを起動するには、java コマンドを叩けばいい。

$ java -jar target/fortune-0.0.1-SNAPSHOT.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.2.RELEASE)

2017-03-11 17:08:05.071  INFO 12061 --- [           main] org.genva.FortuneApplication             : Starting FortuneApplication v0.0.1-SNAPSHOT on carrot with PID 12061 (/home/kazuki/sources/writing/spring/fortune/target/fortune-0.0.1-SNAPSHOT.jar started by kazuki in /home/kazuki/sources/writing/spring/fortune)
(snip)
2017-03-11 17:08:07.998  INFO 12061 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-03-11 17:08:08.002  INFO 12061 --- [           main] org.genva.FortuneApplication             : Started FortuneApplication in 3.364 seconds (JVM running for 3.902)

Eclipse から起動したときと異なり、メインスレッドの名前が main となっていることが分かる。spring-boot:repackage により、spring-boot-devtools が除外されているため。特に難しいことは考えることなく、リリースする jar ファイルでは spring-boot-devtools による余計な制御が行われないようになっている。

デプロイするには、この jar ファイルを適当な場所におき、systemd なりから叩くようにすればよい。詳しくは Spring Boot のリファレンス参照。

ここでは紹介しないが、war ファイルとしてパッケージングし、任意の Servlet コンテナにデプロイすることもできる。詳しくは (やはり) リファレンスを参照。

組み込み Tomcat の起動プロセスを追う

少し Spring Boot の中を覗いてみることにする。

Spring Boot は、Auto Configuration とそこから駆動される便利クラス群から成り立っている。まずは組み込み Tomcat が使われることを決定付ける Auto Configuration を見てみる。

Eclipse で org.apache.catalina.startup.Tomcat クラスの参照を探すと、 org.springframework.boot.autoconfigure.web.EmbeddedTomcat というクラスが見つかる。パッケージ名に autoconfigure とあることから、これが Tomcat を使うよう決定づける Auto Configuration であると推測できる。

/**
 * Nested configuration if Tomcat is being used.
 */
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {

	@Bean
	public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
		return new TomcatEmbeddedServletContainerFactory();
	}

}

EmbeddedServletContainerFactory の実装として、 TomcatEmbeddedServletContainerFactory を Bean 定義している。こいつが Tomcat のインスタンスを作っているものと推測できる。なお、この設定 (EmbeddedTomcat) が有効になるのは、 javax.servlet.Servletorg.apache.catalina.startup.Tomcat がクラスパス内に存在し、かつ EmbeddedServletContainerFactory の Bean 定義がない場合に限る。独自に EmbeddedServletContainerFactory の Bean を定義したり、Web 環境でない場合には、この設定は有効とならない。このように、実行時の環境、条件に応じて設定の有効/無効が自動的に切り替わることから、Auto Configuration と呼ばれている。

次に TomcatEmbeddedServletContainerFactory を追う。

@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
		ServletContextInitializer... initializers) {
	Tomcat tomcat = new Tomcat();
    // (snip)
	return getTomcatEmbeddedServletContainer(tomcat);
}

Tomcat のインスタンスを作り、TomcatEmbeddedServletContainer でラップして返している。

このメソッドの参照を Eclipse で探すと、EmbeddedWebApplicationContext が見つかる。ソースを読むと、onRefresh メソッド経由で getEmbeddedServletContainer を呼び出していることが分かる。

@Override
protected void onRefresh() {
	super.onRefresh();
	try {
		createEmbeddedServletContainer();
	}
	catch (Throwable ex) {
		throw new ApplicationContextException("Unable to start embedded container",
				ex);
	}
}

onRefresh は、Spring の IoC コンテナを refresh したときに呼び出されるフックである。ApplicationContext の実装ごとの、特別な Bean の準備するために呼び出される。アプリケーションの起動時はもちろん、spring-boot-devtools でアプリケーションのクラスローダーを再読み込みするときにも呼び出される。以上が Servlet コンテナの構築の流れとなる。

続けて、起動の流れを追う。TomcatEmbeddedServletContainer.start の参照を探すと、先ほどと同様に EmbeddedWebApplicationContext が見つかる。さらに追っていくと、finishRefresh 経由で呼び出していることが分かる。

@Override
protected void finishRefresh() {
	super.finishRefresh();
	EmbeddedServletContainer localContainer = startEmbeddedServletContainer();
	if (localContainer != null) {
		publishEvent(
				new EmbeddedServletContainerInitializedEvent(this, localContainer));
	}
}

finishRefresh は IoC コンテナの refresh が完了するタイミングで呼び出されるフックである。すなわち Bean 定義の読み込みや設定がひと通り終わったタイミングで Servlet コンテナが動き始める。

FortuneApplication の main メソッドで呼び出している SpringApplication.run では、実際に利用する ApplicationContext の決定が行われる。Web 環境で使われるデフォルトの ApplicationContext の実装は、org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext となっている。AnnotationConfigEmbeddedWebApplicationContextEmbeddedWebApplicationContext のサブクラスである。

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
		+ "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

(snip)

protected ConfigurableApplicationContext createApplicationContext() {
	Class<?> contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
			contextClass = Class.forName(this.webEnvironment
					? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
		}
		catch (ClassNotFoundException ex) {
			throw new IllegalStateException(
					"Unable create a default ApplicationContext, "
							+ "please specify an ApplicationContextClass",
					ex);
		}
	}
	return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

これにより、

ということが分かった。

Tomcat の起動プロセスがわかったところで「だからなんだ」という話なのだが、黒魔術だらけの Spring Boot であっても、順を追って調べれば、どうにかその中身を理解できるということを試したかった。中身についての知識や、解析方法がわかっていれば、何か問題や課題にぶつかったとき、落ち着いて対処することができる。