LoginSignup
15
17

More than 5 years have passed since last update.

spring-boot 1.3.0 getting-started メモ

Last updated at Posted at 2015-11-24

Spring はある程度使った経験があるものの Spring Boot はまともに触ったことがありませんでした。公式ドキュメントを見ながら Spring Boot を初めて試してみたメモをスクリーンキャスト風にまとめたメモです。

Spring Boot のバージョンはこの記事時点で最新の 1.3.0.RELEASE です。Mac OS X で作業しています。Getting Started あたりから順にやっていっただけですが、ドキュメントが充実していてよいですね。
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#getting-started

gradle を使ってプロジェクトをセットアップ

まず gradle が入ってなければ入れる。この記事時点で 2.9 が最新。

brew install gradle

build.gradle の例を貼り付けて gradle wrapper 実行。

buildscript {
    repositories {
        jcenter()
        maven { url "http://repo.spring.io/snapshot" }
        maven { url "http://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.0.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'spring-boot'

jar {
    baseName = 'myproject'
    version =  '0.0.1-SNAPSHOT'
}

repositories {
    jcenter()
    maven { url "http://repo.spring.io/snapshot" }
    maven { url "http://repo.spring.io/milestone" }
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    testCompile("org.springframework.boot:spring-boot-starter-test")
}

この時点でこんな感じ。

$ tree
.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat

と、ビルド時間の削減に Gradle Daemon を使ってみたら?と。

BUILD SUCCESSFUL

Total time: 16.058 secs

This build could be faster, please consider using the Gradle Daemon: https://docs.gradle.org/2.9/userguide/gradle_daemon.html

The Gradle Daemon is a background process that does the heavy lifting of running builds, then stays alive between builds waiting for the next build. This allows data and code that is likely to be required in the next build to be kept in memory, ready to go. This dramatically improves the performance of subsequent builds. Enabling the Gradle Daemon is an extremely cheap way to decrease build times.

早速セットアップしてみる。Mac OS では以下のようにする。

touch ~/.gradle/gradle.properties && echo "org.gradle.daemon=true" >> ~/.gradle/gradle.properties

Spring Boot CLI をセットアップ

次にプロトタイプをさくっとつくるのにおすすめとのことなので Spring Boot の CLI を入れてみる。

The Spring Boot CLI is a command line tool that can be used if you want to quickly prototype with Spring

zip をダウンロードして PATH 通すのかーと思ったが、sdkman というもので入れられるらしい。よく知らなかったが、これはかつては RVM / rbenv に影響された Groovy の GVM (Groovy enVironment Manager) というものだったようだ。
http://sdkman.io/
http://qiita.com/saba1024/items/967ee3d8a79440a97336

とりあえずこれでやってみる。

$ curl -s http://get.sdkman.io | bash

Thanks for using...


     SSSSSSSSSSSSSSS DDDDDDDDDDDDD       KKKKKKKKK    KKKKKKK
   SS:::::::::::::::SD::::::::::::DDD    K:::::::K    K:::::K
  S:::::SSSSSS::::::SD:::::::::::::::DD  K:::::::K    K:::::K
  S:::::S     SSSSSSSDDD:::::DDDDD:::::D K:::::::K   K::::::K
  S:::::S              D:::::D    D:::::DKK::::::K  K:::::KKK
  S:::::S              D:::::D     D:::::D K:::::K K:::::K
   S::::SSSS           D:::::D     D:::::D K::::::K:::::K
    SS::::::SSSSS      D:::::D     D:::::D K:::::::::::K
      SSS::::::::SS    D:::::D     D:::::D K:::::::::::K
         SSSSSS::::S   D:::::D     D:::::D K::::::K:::::K
              S:::::S  D:::::D     D:::::D K:::::K K:::::K
              S:::::S  D:::::D    D:::::DKK::::::K  K:::::KKK
  SSSSSSS     S:::::SDDD:::::DDDDD:::::D K:::::::K   K::::::K
  S::::::SSSSSS:::::SD:::::::::::::::DD  K:::::::K    K:::::K
  S:::::::::::::::SS D::::::::::::DDD    K:::::::K    K:::::K
   SSSSSSSSSSSSSSS   DDDDDDDDDDDDD       KKKKKKKKK    KKKKKKK


                      mmmmmmm    mmmmmmm     aaaaaaaaaaaaa  nnnn  nnnnnnnn
                    mm:::::::m  m:::::::mm   a::::::::::::a n:::nn::::::::nn
                   m::::::::::mm::::::::::m  aaaaaaaaa:::::an::::::::::::::nn
                   m::::::::::::::::::::::m           a::::ann:::::::::::::::n
                   m:::::mmm::::::mmm:::::m    aaaaaaa:::::a  n:::::nnnn:::::n
                   m::::m   m::::m   m::::m  aa::::::::::::a  n::::n    n::::n
                   m::::m   m::::m   m::::m a::::aaaa::::::a  n::::n    n::::n
                   m::::m   m::::m   m::::ma::::a    a:::::a  n::::n    n::::n
                   m::::m   m::::m   m::::ma::::a    a:::::a  n::::n    n::::n
                   m::::m   m::::m   m::::ma:::::aaaa::::::a  n::::n    n::::n
                   m::::m   m::::m   m::::m a::::::::::aa:::a n::::n    n::::n
                   mmmmmm   mmmmmm   mmmmmm  aaaaaaaaaa  aaaa nnnnnn    nnnnnn


                                                 Now attempting installation...

Looking for a previous installation of SDKMAN...
Looking for unzip...
Looking for curl...
Looking for sed...
Installing SDKMAN scripts...
Create distribution directories...
Getting available candidates...
Prime the config file...
Download script archive...
Extract script archive...
Install scripts...
Set version to 3.2.4 ...
Attempt update of bash profiles...
Updated existing /Users/seratch/.bash_profile
Created and initialised /Users/seratch/.bashrc
Attempt update of zsh profiles...
Created and initialised /Users/seratch/.zshrc



All done!


Please open a new terminal, or run the following in the existing one:

    source "/Users/seratch/.sdkman/bin/sdkman-init.sh"

Then issue the following command:

    sdk help

Enjoy!!!

$ source "/Users/seratch/.sdkman/bin/sdkman-init.sh"

$ sdk help
==== BROADCAST =================================================================
* 17/11/15: Gradle 2.9 released on SDKMAN! #gradle
* 16/11/15: Springboot 1.3.0.RELEASE released on SDKMAN! #springboot
* 05/11/15: Vertx 3.1.0 released on SDKMAN! #vertx
================================================================================

Usage: sdk <command> [candidate] [version]
       sdk offline <enable|disable>

   commands:
       install   or i    <candidate> [version]
       uninstall or rm   <candidate> <version>
       list      or ls   [candidate]
       use       or u    <candidate> [version]
       default   or d    <candidate> [version]
       current   or c    [candidate]
       outdated  or o    [candidate]
       version   or v
       broadcast or b
       help      or h
       offline           <enable|disable>
       selfupdate        [force]
       flush             <candidates|broadcast|archives|temp>

   candidate  :  the SDK to install: groovy, scala, grails, akka, etc.
                 use list command for comprehensive list of candidates
                 eg: $ sdk list

   version    :  where optional, defaults to latest stable if not provided
                 eg: $ sdk install groovy

$

ともあれ sdk コマンドが使えるようになったので sdk install してみる。

$ sdk install springboot

Downloading: springboot 1.3.0.RELEASE

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 9094k  100 9094k    0     0   748k      0  0:00:12  0:00:12 --:--:-- 1088k

Installing: springboot 1.3.0.RELEASE
Done installing!

Do you want springboot 1.3.0.RELEASE to be set as default? (Y/n): y

Setting springboot 1.3.0.RELEASE as default.
$

セットアップできたようだ。

$ spring
usage: spring [--help] [--version]
       <command> [<args>]

Available commands are:

  run [options] <files> [--] [args]
    Run a spring groovy script

  test [options] <files> [--] [args]
    Run a spring groovy script test

  grab
    Download a spring groovy script's dependencies to ./repository

  jar [options] <jar-name> <files>
    Create a self-contained executable jar file from a Spring Groovy script

  war [options] <war-name> <files>
    Create a self-contained executable war file from a Spring Groovy script

  install [options] <coordinates>
    Install dependencies to the lib directory

  uninstall [options] <coordinates>
    Uninstall dependencies from the lib directory

  init [options] [location]
    Initialize a new project using Spring Initialzr (start.spring.io)

  shell
    Start a nested shell

Common options:

  -d, --debug Verbose mode
    Print additional status information for the command you are running


See 'spring help <command>' for more information on a specific command.

ところで sdkman の BROADCAST というお知らせ欄?に「gradle やら vert.x やらがリリースされているよ!」と出ていたが、他に何があるのか sdk list で一覧を見ると以下のような感じ。

================================================================================
Available Candidates
================================================================================
q-quit                                  /-search down
j-down                                  ?-search up
k-up                                    h-help
--------------------------------------------------------------------------------
AsciidoctorJ (1.5.2)                                     http://asciidoctor.org/

AsciidoctorJ is the official library for running Asciidoctor on the JVM. Using
AsciidoctorJ, you can convert AsciiDoc content or analyze the structure of a
parsed AsciiDoc document from Java and other JVM languages.

                                                      $ sdk install asciidoctorj
--------------------------------------------------------------------------------
Ceylon (1.2.0)                                           http://ceylon-lang.org/

Ceylon is a modern, modular, statically typed programming language for the Java
and JavaScript virtual machines. The language features a flexible and very
readable syntax, a unique and uncommonly elegant static type system, a powerful
module architecture, and excellent tooling.

                                                            $ sdk install ceylon
--------------------------------------------------------------------------------
CRaSH (1.3.0)                                            http://www.crashub.org/

The Common Reusable SHell (CRaSH) deploys in a Java runtime and provides
interactions with the JVM. Commands are written in Groovy or Java and can be
developed at runtime making the extension of the shell very easy with fast
development cycle.

                                                             $ sdk install crash
--------------------------------------------------------------------------------
Gaiden (1.0)                                       http://kobo.github.io/gaiden/

Gaiden is a tool that makes it easy to create documentation with Markdown.

                                                            $ sdk install gaiden
--------------------------------------------------------------------------------
Glide (0.3.3)                                      http://glide-gae.appspot.com/

Glide makes it incredibly easy to develop apps that harness the power of Google
App Engine for Java using expressiveness of Groovy and sweetness of Gaelyk's
syntactic sugar.

                                                             $ sdk install glide
--------------------------------------------------------------------------------
Gradle (2.9)                                                  http://gradle.org/

Gradle is a build automation tool that builds upon the concepts of Apache Ant
and Apache Maven and introduces a Groovy-based domain-specific language (DSL)
instead of the more traditional XML form of declaring the project
configuration. Gradle uses a directed acyclic graph (DAG) to determine the
order in which tasks can be run.

                                                            $ sdk install gradle
--------------------------------------------------------------------------------
Grails (3.0.9)                                               https://grails.org/

Grails is a powerful web framework, for the Java platform aimed at multiplying
developers productivity thanks to a Convention-over-Configuration, sensible
defaults and opinionated APIs. It integrates smoothly with the JVM, allowing
you to be immediately productive whilst providing powerful features, including
integrated ORM, Domain-Specific Languages, runtime and compile-time
meta-programming and Asynchronous programming.

                                                            $ sdk install grails
--------------------------------------------------------------------------------
Griffon (1.5.0)                                    http://griffon-framework.org/

Griffon is desktop application development platform for the JVM.Inspired by
Grails, Griffon leverages the use of the Groovy language and concepts like
Convention over Configuration. The Swing toolkit is the default UI toolkit of
choice however others may be used, for example JavaFX.

                                                           $ sdk install griffon
--------------------------------------------------------------------------------
Groovy (2.4.5)                                       http://www.groovy-lang.org/

Groovy is a powerful, optionally typed and dynamic language, with static-typing
and static compilation capabilities, for the Java platform aimed at multiplying
developers' productivity thanks to a concise, familiar and easy to learn
syntax. It integrates smoothly with any Java program, and immediately delivers
to your application powerful features, including scripting capabilities,
Domain-Specific Language authoring, runtime and compile-time meta-programming
and functional programming.

                                                            $ sdk install groovy
--------------------------------------------------------------------------------
GroovyServ (1.0.0)                            https://kobo.github.io/groovyserv/

GroovyServ reduces startup time of the JVM for runnning Groovy significantly.
It depends on your environments, but in most cases, it’s 10 to 20 times faster
than regular Groovy.

                                                        $ sdk install groovyserv
--------------------------------------------------------------------------------
JBake (2.4.0)                                                  http://jbake.org/

JBake is a Java based, open source, static site/blog generator for developers
and designers.

                                                             $ sdk install jbake
--------------------------------------------------------------------------------
JBoss Forge (2.17.0.Final)                               http://forge.jboss.org/

JBoss Forge is the Fastest way to build Maven-based Java EE projects, and
anything else you fancy.

                                                        $ sdk install jbossforge
--------------------------------------------------------------------------------
Lazybones (0.8.1)                        https://github.com/pledbrook/lazybones/

Lazybones allows you to create a new project structure for any framework or
library for which the tool has a template.

                                                         $ sdk install lazybones
--------------------------------------------------------------------------------
Maven (3.3.3)                                          https://maven.apache.org/

Apache Maven is a software project management and comprehension tool. Based on
the concept of a project object model (POM), Maven can manage a project's
build, reporting and documentation from a central piece of information.

                                                             $ sdk install maven
--------------------------------------------------------------------------------
sbt (0.13.9)                                           http://www.scala-sbt.org/

SBT is an open source build tool for Scala and Java projects, similar to Java's
Maven or Ant. Its main features are: native support for compiling Scala code
and integrating with many Scala test frameworks; build descriptions written in
Scala using a DSL; dependency management using Ivy (which supports Maven-format
repositories); continuous compilation, testing, and deployment; integration
with the Scala interpreter for rapid iteration and debugging; support for mixed
Java/Scala projects

                                                               $ sdk install sbt
--------------------------------------------------------------------------------
Scala (2.11.7)                                        http://www.scala-lang.org/

Scala is a programming language for general software applications. Scala has
full support for functional programming and a very strong static type system.
This allows programs written in Scala to be very concise and thus smaller in
size than other general-purpose programming languages. Scala source code is
intended to be compiled to Java bytecode, so that the resulting executable code
runs on a Java virtual machine. Java libraries may be used directly in Scala
code and vice versa. Scala is object-oriented, and uses a curly-brace syntax.
Scala has many features of functional programming languages, including
currying, type inference, immutability, lazy evaluation, and pattern matching.
It also has an advanced type system supporting algebraic data types, covariance
and contravariance, higher-order types, and anonymous types. Other features of
Scala include operator overloading, optional parameters, named parameters, raw
strings, and no checked exceptions.

                                                             $ sdk install scala
--------------------------------------------------------------------------------
Spring Boot (1.3.0.RELEASE)               http://projects.spring.io/spring-boot/

Spring Boot takes an opinionated view of building production-ready Spring
applications. It favors convention over configuration and is designed to get
you up and running as quickly as possible.

                                                        $ sdk install springboot
--------------------------------------------------------------------------------
Vert.x (3.1.0)                                                  http://vertx.io/

Vert.x is a tool-kit for building reactive applications on the JVM.

                                                             $ sdk install vertx
--------------------------------------------------------------------------------

このラインナップだと使っているもの(Maven、Gradle、sbt、scala)は基本的に全部 Homebrew にあるし、これだけのために sdkman 入れるのは微妙かなと思っていたら、次のセクション「10.2.3 OSX Homebrew installation」で Homebrew でのインストールが紹介されていた。

Homebrew で入れるためにまず sdkman で入れたものを uninstall して

$ sdk uninstall springboot 1.3.0.RELEASE

Unselecting springboot 1.3.0.RELEASE...

Uninstalling springboot 1.3.0.RELEASE...

次に Homebrew で入れ直す。Homebrew の本家にはなく Pivotal の GitHub リポジトリを brew tap して入れるようだ。

$ brew tap pivotal/tap
$ brew install springboot

Spring Boot CLI のセットアップはこの辺で。

Spring Boot の Web アプリケーションを動かす

「10.2.6 Quick start Spring CLI example」を見ると

@RestController
class ThisWillActuallyRun {
  @RequestMapping("/")
  String home() {
    "Hello World!"
  }
}

このような Groovy のコードを app.groovy として保存して spring run app.groovy とするだけで動くらしい・・マジか。必要最低限の依存ライブラリを Grape で解決するだけでなく import もデフォルトである程度解決されているということなのだろう。

$ spring run app.groovy
Resolving dependencies...........................

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

2015-11-23 16:06:26.616  INFO 53470 --- [       runner-0] o.s.boot.SpringApplication               : Starting application on seratchs-MacBook-Pro.local with PID 53470 (/Users/seratch/.m2/repository/org/springframework/boot/spring-boot/1.3.0.RELEASE/spring-boot-1.3.0.RELEASE.jar started by seratch in /Users/seratch/Documents/github/spring-boot-example)
2015-11-23 16:06:26.619  INFO 53470 --- [       runner-0] o.s.boot.SpringApplication               : No profiles are active
2015-11-23 16:06:27.004  INFO 53470 --- [       runner-0] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@517a4e29: startup date [Mon Nov 23 16:06:27 JST 2015]; root of context hierarchy
2015-11-23 16:06:28.414  INFO 53470 --- [       runner-0] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'beanNameViewResolver' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2015-11-23 16:06:29.166  INFO 53470 --- [       runner-0] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2015-11-23 16:06:29.181  INFO 53470 --- [       runner-0] o.apache.catalina.core.StandardService   : Starting service Tomcat
2015-11-23 16:06:29.183  INFO 53470 --- [       runner-0] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.28
2015-11-23 16:06:29.267  INFO 53470 --- [ost-startStop-1] org.apache.catalina.loader.WebappLoader  : Unknown loader org.springframework.boot.cli.compiler.ExtendedGroovyClassLoader$DefaultScopeParentClassLoader@6598a08d class org.springframework.boot.cli.compiler.ExtendedGroovyClassLoader$DefaultScopeParentClassLoader
2015-11-23 16:06:29.288  INFO 53470 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2015-11-23 16:06:29.288  INFO 53470 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2284 ms
2015-11-23 16:06:29.754  INFO 53470 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2015-11-23 16:06:29.763  INFO 53470 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'characterEncodingFilter' to: [/*]
2015-11-23 16:06:29.763  INFO 53470 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2015-11-23 16:06:29.764  INFO 53470 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2015-11-23 16:06:29.764  INFO 53470 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'requestContextFilter' to: [/*]
2015-11-23 16:06:30.114  INFO 53470 --- [       runner-0] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@517a4e29: startup date [Mon Nov 23 16:06:27 JST 2015]; root of context hierarchy
2015-11-23 16:06:30.200  INFO 53470 --- [       runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto public java.lang.String ThisWillActuallyRun.home()
2015-11-23 16:06:30.203  INFO 53470 --- [       runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2015-11-23 16:06:30.203  INFO 53470 --- [       runner-0] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2015-11-23 16:06:30.246  INFO 53470 --- [       runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-11-23 16:06:30.246  INFO 53470 --- [       runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-11-23 16:06:30.300  INFO 53470 --- [       runner-0] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-11-23 16:06:30.816  INFO 53470 --- [       runner-0] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2015-11-23 16:06:30.921  INFO 53470 --- [       runner-0] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-11-23 16:06:30.926  INFO 53470 --- [       runner-0] o.s.boot.SpringApplication               : Started application in 4.858 seconds (JVM running for 36.749)

確かに起動した。デフォルトでは Tomcat 8 が起動する様子。localhost:8080 にアクセスすると「Hello World!」というボディで応答が返ってくる。

$ curl -v http://localhost:8080/
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 12
< Date: Mon, 23 Nov 2015 07:08:10 GMT
<
* Connection #0 to host localhost left intact
Hello World!$

しばらく特に手を動かすところがないので、ちょっと飛ばして次は「11.3 Writing the code」の Java サンプルを動かしてみる。

mkdir -p src/main/java/

echo 'import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;
@RestController
@EnableAutoConfiguration
public class Example {
    @RequestMapping("/")
    String home() {
        return "Hello World!";
    }
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Example.class, args);
    }
}' > src/main/java/Example.java

最初に最低限の Spring Boot プロジェクトを Gradle でセットアップ済だったので、ソースコードを置いて bootRun という Gradle タスクで起動できる。なお Gradle のタスク一覧は tasks で確認できる。

$ ./gradlew tasks
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Application tasks
-----------------
bootRun - Run the project with support for auto-detecting main class and reloading static resources

(以下略)

bootRun は main class の検知と静的ファイルの hot reloading をサポートしているようだ。

./gradlew bootRun

これも全く同じように動作した。

$ curl -v localhost:8080
* Rebuilt URL to: localhost:8080/
*   Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: Apache-Coyote/1.1
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 12
< Date: Mon, 23 Nov 2015 07:15:28 GMT
<
* Connection #0 to host localhost left intact
Hello World!$

ここから先のドキュメントは Gradle だけでなく Maven、Ant でのビルド方法や上記で貼り付けた build.gradle についての説明などが続くのでスキップして「III. Using Spring Boot」に入り、「14.2 Locating the main application class」のサンプルを試す。

src/main/java/Example.java を削除して src/main/java/myproject/Application.java を追加。とりあえず Example.java で動いていたものをそのまま持ってきて動くようにするとこんな感じになる。

package myproject;
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.context.annotation.*;
import org.springframework.web.bind.annotation.*;

@Configuration
@EnableAutoConfiguration
@ComponentScan
@RestController // TODO: move
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

  @RequestMapping("/") String home() { return "Hello World!"; } // TODO: move
}

./gradlew bootRun を立ち上げ直して、これはこれで動作することを確認する。

次は RestController を分離する。myproject.Application は main と Spring 全体設定の初期化だけにして

// src/main/java/myproject/Application.java
package myproject;
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.context.annotation.*;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

ルーティングや処理メソッドは myproject.web.HomeController に全て移動。上記のように myproject.Application に ComponentScan アノテーションを指定しておくと myproject を root package として自動でコンポーネントスキャンされるので、クラスに RestController アノテーションをつけておけば、ルーティング情報が設定されているコンポーネントとして自動で認識してくれる。

// src/main/java/myproject/web/HomeController.java
package myproject.web;
import org.springframework.web.bind.annotation.*;

@RestController
public class HomeController {
  @RequestMapping("/") String home() { return "Hello World!"; }
}

./gradlew bootRun を立ち上げ直して動作確認。

これまで Java コードの変更の度に Gradle を立ち上げ直していたが、hot reloading できるものはないかというと Spring Loaded というものがあるようだ。
https://github.com/spring-projects/spring-loaded

普段関わる Spring のプロジェクトでは JUnit のユニットテストで動作確認していることが多く、Servlet コンテナを自動しっぱなしにして自動的に class を reload しなくてもさほど困らず Spring Loaded は使ったことがなかった(とはいえ、実際不便ではあった)。

このへんの記事を拝見すると簡単に連携できるようなので試してみることに。
http://qiita.com/Sa2/items/c3150e3d43698cd67ff1

また、この辺でそろそろ IDE を使うべきタイミングのようなのでいつものように IntelliJ IDEA を使うことにする。

IntelliJ IDEA + Spring Loaded

まず idea plugin を追加。idea ブロックは IDEA が class ファイルを出力する先を Gralde の出力先と揃えるためとのことでそのままコピペする。

apply plugin: 'idea'

idea {
    module {
        inheritOutputDirs = false
        outputDir = file("$buildDir/classes/main/")
    }
}

idea タスクが追加される。

$ ./gradlew tasks
(中略)

IDE tasks
---------
cleanIdea - Cleans IDEA project files (IML, IPR)
idea - Generates IDEA project files (IML, IPR, IWS)

(以下略)

早速実行して IDEA の設定ファイルを出力する。

$ ./gradlew idea
Starting a new Gradle Daemon for this build (subsequent builds will be faster).
:ideaModule
Download https://jcenter.bintray.com/org/springframework/boot/spring-boot-starter-test/1.3.0.RELEASE/spring-boot-starter-test-1.3.0.RELEASE.pom
Download https://jcenter.bintray.com/org/mockito/mockito-core/1.10.19/mockito-core-1.10.19.pom
Download https://jcenter.bintray.com/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3.pom
Download https://jcenter.bintray.com/org/springframework/spring-test/4.2.3.RELEASE/spring-test-4.2.3.RELEASE.pom
Download https://jcenter.bintray.com/org/objenesis/objenesis/2.1/objenesis-2.1.pom
Download https://jcenter.bintray.com/org/objenesis/objenesis-parent/2.1/objenesis-parent-2.1.pom
Download https://jcenter.bintray.com/org/springframework/boot/spring-boot-starter-test/1.3.0.RELEASE/spring-boot-starter-test-1.3.0.RELEASE.jar
Download https://jcenter.bintray.com/org/mockito/mockito-core/1.10.19/mockito-core-1.10.19.jar
Download https://jcenter.bintray.com/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3.jar
Download https://jcenter.bintray.com/org/springframework/spring-test/4.2.3.RELEASE/spring-test-4.2.3.RELEASE.jar
Download https://jcenter.bintray.com/org/objenesis/objenesis/2.1/objenesis-2.1.jar
Download https://jcenter.bintray.com/org/springframework/boot/spring-boot-starter-web/1.3.0.RELEASE/spring-boot-starter-web-1.3.0.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/springframework/boot/spring-boot-devtools/1.3.0.RELEASE/spring-boot-devtools-1.3.0.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/springframework/boot/spring-boot-starter-test/1.3.0.RELEASE/spring-boot-starter-test-1.3.0.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/springframework/boot/spring-boot-starter/1.3.0.RELEASE/spring-boot-starter-1.3.0.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/springframework/boot/spring-boot-starter-tomcat/1.3.0.RELEASE/spring-boot-starter-tomcat-1.3.0.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/springframework/boot/spring-boot-starter-validation/1.3.0.RELEASE/spring-boot-starter-validation-1.3.0.RELEASE-sources.jar
Download https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-databind/2.6.3/jackson-databind-2.6.3-sources.jar
Download https://jcenter.bintray.com/org/springframework/spring-web/4.2.3.RELEASE/spring-web-4.2.3.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/springframework/spring-webmvc/4.2.3.RELEASE/spring-webmvc-4.2.3.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/springframework/boot/spring-boot/1.3.0.RELEASE/spring-boot-1.3.0.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/springframework/boot/spring-boot-autoconfigure/1.3.0.RELEASE/spring-boot-autoconfigure-1.3.0.RELEASE-sources.jar
Download https://jcenter.bintray.com/junit/junit/4.12/junit-4.12-sources.jar
Download https://jcenter.bintray.com/org/mockito/mockito-core/1.10.19/mockito-core-1.10.19-sources.jar
Download https://jcenter.bintray.com/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar
Download https://jcenter.bintray.com/org/hamcrest/hamcrest-library/1.3/hamcrest-library-1.3-sources.jar
Download https://jcenter.bintray.com/org/springframework/spring-core/4.2.3.RELEASE/spring-core-4.2.3.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/springframework/spring-test/4.2.3.RELEASE/spring-test-4.2.3.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/springframework/boot/spring-boot-starter-logging/1.3.0.RELEASE/spring-boot-starter-logging-1.3.0.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/yaml/snakeyaml/1.16/snakeyaml-1.16-sources.jar
Download https://jcenter.bintray.com/org/apache/tomcat/embed/tomcat-embed-core/8.0.28/tomcat-embed-core-8.0.28-sources.jar
Download https://jcenter.bintray.com/org/apache/tomcat/embed/tomcat-embed-el/8.0.28/tomcat-embed-el-8.0.28-sources.jar
Download https://jcenter.bintray.com/org/apache/tomcat/embed/tomcat-embed-logging-juli/8.0.28/tomcat-embed-logging-juli-8.0.28-sources.jar
Download https://jcenter.bintray.com/org/apache/tomcat/embed/tomcat-embed-websocket/8.0.28/tomcat-embed-websocket-8.0.28-sources.jar
Download https://jcenter.bintray.com/org/hibernate/hibernate-validator/5.2.2.Final/hibernate-validator-5.2.2.Final-sources.jar
Download https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-annotations/2.6.3/jackson-annotations-2.6.3-sources.jar
Download https://jcenter.bintray.com/com/fasterxml/jackson/core/jackson-core/2.6.3/jackson-core-2.6.3-sources.jar
Download https://jcenter.bintray.com/org/springframework/spring-aop/4.2.3.RELEASE/spring-aop-4.2.3.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/springframework/spring-beans/4.2.3.RELEASE/spring-beans-4.2.3.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/springframework/spring-context/4.2.3.RELEASE/spring-context-4.2.3.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/springframework/spring-expression/4.2.3.RELEASE/spring-expression-4.2.3.RELEASE-sources.jar
Download https://jcenter.bintray.com/org/objenesis/objenesis/2.1/objenesis-2.1-sources.jar
Download https://jcenter.bintray.com/ch/qos/logback/logback-classic/1.1.3/logback-classic-1.1.3-sources.jar
Download https://jcenter.bintray.com/org/slf4j/jcl-over-slf4j/1.7.13/jcl-over-slf4j-1.7.13-sources.jar
Download https://jcenter.bintray.com/org/slf4j/jul-to-slf4j/1.7.13/jul-to-slf4j-1.7.13-sources.jar
Download https://jcenter.bintray.com/org/slf4j/log4j-over-slf4j/1.7.13/log4j-over-slf4j-1.7.13-sources.jar
Download https://jcenter.bintray.com/javax/validation/validation-api/1.1.0.Final/validation-api-1.1.0.Final-sources.jar
Download https://jcenter.bintray.com/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final-sources.jar
Download https://jcenter.bintray.com/com/fasterxml/classmate/1.1.0/classmate-1.1.0-sources.jar
Download https://jcenter.bintray.com/aopalliance/aopalliance/1.0/aopalliance-1.0-sources.jar
Download https://jcenter.bintray.com/ch/qos/logback/logback-core/1.1.3/logback-core-1.1.3-sources.jar
Download https://jcenter.bintray.com/org/slf4j/slf4j-api/1.7.13/slf4j-api-1.7.13-sources.jar
:ideaProject
:ideaWorkspace
:idea

BUILD SUCCESSFUL

Total time: 58.284 secs

この辺は先の qiita 記事の受け売りになるが、IDEA の設定で「Build, Execution, Deloyment」>「Compiler」の「Make project automatically」にチェックを入れた状態で ./gradlew bootRun を再度立ち上げ。

2015-11-23 19:49:40.575  INFO 56854 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2015-11-23 19:49:40.706  INFO 56854 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2015-11-23 19:49:40.714  INFO 56854 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-11-23 19:49:40.716  INFO 56854 --- [  restartedMain] myproject.Application                    : Started Application in 1.015 seconds (JVM running for 54.864)
2015-11-23 19:49:40.801  INFO 56854 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2015-11-23 19:49:40.801  INFO 56854 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2015-11-23 19:49:40.803  INFO 56854 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 2 ms
> Building 80% > :bootRun

この状態で src/main/java/**.java を変更すると Tomcat を再起動している様子・・

2015-11-23 19:50:15.605  INFO 56854 --- [      Thread-23] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@3e05e4a6: startup date [Mon Nov 23 19:49:39 JST 2015]; root of context hierarchy
2015-11-23 19:50:15.606  INFO 56854 --- [      Thread-23] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

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

2015-11-23 19:50:15.875  INFO 56854 --- [  restartedMain] myproject.Application                    : Starting Application on seratchs-MacBook-Pro.local with PID 56854 (/Users/seratch/Documents/github/spring-boot-example/build/classes/main started by seratch in /Users/seratch/Documents/github/spring-boot-example)
2015-11-23 19:50:15.875  INFO 56854 --- [  restartedMain] myproject.Application                    : No profiles are active
2015-11-23 19:50:15.878  INFO 56854 --- [  restartedMain] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@160bf3cd: startup date [Mon Nov 23 19:50:15 JST 2015]; root of context hierarchy
2015-11-23 19:50:16.372  INFO 56854 --- [  restartedMain] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'beanNameViewResolver' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2015-11-23 19:50:16.524  INFO 56854 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2015-11-23 19:50:16.527  INFO 56854 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service Tomcat
2015-11-23 19:50:16.527  INFO 56854 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.28
2015-11-23 19:50:16.534  INFO 56854 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2015-11-23 19:50:16.535  INFO 56854 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 657 ms
2015-11-23 19:50:16.615  INFO 56854 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2015-11-23 19:50:16.617  INFO 56854 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'characterEncodingFilter' to: [/*]
2015-11-23 19:50:16.624  INFO 56854 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2015-11-23 19:50:16.624  INFO 56854 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2015-11-23 19:50:16.624  INFO 56854 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'requestContextFilter' to: [/*]
2015-11-23 19:50:16.682  INFO 56854 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@160bf3cd: startup date [Mon Nov 23 19:50:15 JST 2015]; root of context hierarchy
2015-11-23 19:50:16.694  INFO 56854 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto java.lang.String myproject.web.HomeController.home()
2015-11-23 19:50:16.696  INFO 56854 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2015-11-23 19:50:16.697  INFO 56854 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2015-11-23 19:50:16.704  INFO 56854 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-11-23 19:50:16.704  INFO 56854 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-11-23 19:50:16.722  INFO 56854 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-11-23 19:50:16.738  INFO 56854 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2015-11-23 19:50:16.794  INFO 56854 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2015-11-23 19:50:16.808  INFO 56854 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-11-23 19:50:16.809  INFO 56854 --- [  restartedMain] myproject.Application                    : Started Application in 1.0 seconds (JVM running for 90.958)
2015-11-23 19:50:17.098  INFO 56854 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2015-11-23 19:50:17.098  INFO 56854 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2015-11-23 19:50:17.101  INFO 56854 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 3 ms

Twitter で教えていただいたが、これは Spring Loaded ではなく spring-boot-devtools による挙動だった。

原因は単に Spring Loaded が追加されていないだけというポカミス。何か変なこと書いてすみません(&ありがとうございます)。

ということで build.gradle の buildscript.dependencies に「classpath("org.springframework:springloaded:1.2.4.RELEASE")」を追加して

buildscript {
    repositories {
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.0.RELEASE")
        classpath("org.springframework:springloaded:1.2.4.RELEASE")
    }
}

depedencies にある spring-boot-devtools を無効化。

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    //compile("org.springframework.boot:spring-boot-devtools")
    testCompile("org.springframework.boot:spring-boot-starter-test")
}

そして Gradle を ./gradlew bootRun で再起動。

これで、Java コードを書き換えたり、class を追加・移動などしてみると、反映まで少しラグはあるが、再起動せずに hot reloading される。すばらしい。

しかし RequestMapping や Spring に登録されている Bean の情報は hot reloading されないらしく、確かに手元で試してもそういう挙動をしていた。この辺は確かにちょっと不便そうではある。

その後、この辺も参考にした。
http://teppeis.hatenablog.com/entry/2015/11/spring-boot-hot-swapping
http://www.bunkei-programmer.net/entry/2015/06/16/012524

あと Spring Loaded が hot reloading したときに何かログは出ないのかな?と思ったが
https://github.com/spring-projects/spring-loaded/wiki/Basic-usage-information

build.gradle に以下を設定するとモリモリとログが出るようになった。

bootRun {
    systemProperty("springloaded", "verbose;explain")
}

ファイルを変更してコンパイル結果が出力されると検知して以下のようなログが標準出力に出た。

2015-11-24 12:56:48.262  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.agent.Watcher    : Observed last modification time change for /Users/k-sera/Documents/github/spring-boot-example/build/classes/main/myproject/web/HomeController.class (lastScanTime=1448337407162)
2015-11-24 12:56:48.262  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.agent.Watcher    : Firing file changed event /Users/k-sera/Documents/github/spring-boot-example/build/classes/main/myproject/web/HomeController.class
2015-11-24 12:56:48.263  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.ReloadableType   : Loading new version of myproject/web/HomeController, identifying suffix PUvM9u4, new data length is 593bytes
2015-11-24 12:56:48.264  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=org/springsource/loaded/TypeDiffComputer classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.264  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springsource/loaded/TypeDiffComputer is using a package name 'org/springsource/loaded/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.265  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=sl/org/objectweb/asm/tree/ClassNode classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.266  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=sl/org/objectweb/asm/tree/AnnotationNode classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.267  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=sl/org/objectweb/asm/tree/MethodNode classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.269  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=sl/org/objectweb/asm/tree/InsnList classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.270  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=sl/org/objectweb/asm/tree/LabelNode classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.270  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=sl/org/objectweb/asm/tree/AbstractInsnNode classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.271  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=sl/org/objectweb/asm/tree/LineNumberNode classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.271  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=sl/org/objectweb/asm/tree/VarInsnNode classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.272  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=sl/org/objectweb/asm/tree/MethodInsnNode classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.273  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=sl/org/objectweb/asm/tree/InsnNode classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.273  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=sl/org/objectweb/asm/tree/LocalVariableNode classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.274  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=sl/org/objectweb/asm/tree/LdcInsnNode classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.275  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=org/springsource/loaded/TypeDelta classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.275  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springsource/loaded/TypeDelta is using a package name 'org/springsource/loaded/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.276  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=org/springsource/loaded/MethodDelta classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.276  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springsource/loaded/MethodDelta is using a package name 'org/springsource/loaded/' which is considered infrastructure and types within it are not made reloadable
Reloading: Loading new version of myproject.web.HomeController [PUvM9u4]
2015-11-24 12:56:48.277  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=org/springsource/loaded/CurrentLiveVersion classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.277  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springsource/loaded/CurrentLiveVersion is using a package name 'org/springsource/loaded/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.278  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=org/springsource/loaded/IncrementalTypeDescriptor classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.278  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springsource/loaded/IncrementalTypeDescriptor is using a package name 'org/springsource/loaded/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.279  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=org/springsource/loaded/ExecutorBuilder$ExecutorBuilderVisitor classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.279  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springsource/loaded/ExecutorBuilder$ExecutorBuilderVisitor is using a package name 'org/springsource/loaded/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.280  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=org/springsource/loaded/ExecutorBuilder$ExecutorBuilderVisitor$CopyingAnnotationVisitor classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.280  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springsource/loaded/ExecutorBuilder$ExecutorBuilderVisitor$CopyingAnnotationVisitor is using a package name 'org/springsource/loaded/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.281  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=org/springsource/loaded/ConstructorCopier classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.281  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springsource/loaded/ConstructorCopier is using a package name 'org/springsource/loaded/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.282  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=org/springsource/loaded/MethodCopier classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.282  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springsource/loaded/MethodCopier is using a package name 'org/springsource/loaded/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.283  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=org/springsource/loaded/DispatcherBuilder classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.283  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springsource/loaded/DispatcherBuilder is using a package name 'org/springsource/loaded/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.284  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=org/springsource/loaded/DispatcherBuilder$DispatcherBuilderVisitor classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.284  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springsource/loaded/DispatcherBuilder$DispatcherBuilderVisitor is using a package name 'org/springsource/loaded/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.285  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=myproject/web/HomeController$$DPUvM9u4 classloader=ChildClassLoader typeRegistry=null
2015-11-24 12:56:48.286  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=org/springsource/loaded/__DynamicallyDispatchable classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.286  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springsource/loaded/__DynamicallyDispatchable is using a package name 'org/springsource/loaded/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.286  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=myproject/web/HomeController$$EPUvM9u4 classloader=ChildClassLoader typeRegistry=null
2015-11-24 12:56:48.287  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=java/io/ObjectStreamClass$Caches classloader=null typeRegistry=null
2015-11-24 12:56:48.287  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=com/sun/beans/finder/PropertyEditorFinder classloader=null typeRegistry=null
2015-11-24 12:56:48.288  INFO 40741 --- [Loader@14dad5dc] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=org/springframework/util/ConcurrentReferenceHashMap$2 classloader=Launcher$AppClassLoader typeRegistry=TypeRegistry(id=455659002,loader=sun.misc.Launcher$AppClassLoader)
2015-11-24 12:56:48.288  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springframework/util/ConcurrentReferenceHashMap$2 is using a package name 'org/springframework/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.288  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springframework/util/ConcurrentReferenceHashMap$2 is using a package name 'org/springframework/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.289  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springframework/util/ConcurrentReferenceHashMap$Task is using a package name 'org/springframework/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.289  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springframework/util/ConcurrentReferenceHashMap$Reference is using a package name 'org/springframework/' which is considered infrastructure and types within it are not made reloadable
2015-11-24 12:56:48.289  INFO 40741 --- [Loader@14dad5dc] org.springsource.loaded.TypeRegistry     : WhyNotReloadable? The type org/springframework/util/ConcurrentReferenceHashMap$Entry is using a package name 'org/springframework/' which is considered infrastructure and types within it are not made reloadable
> Building 80% > :bootRun

ただ、この設定を入れているときはなぜか割と高確率で exit 1 で異常終了してしまう。

015-11-24 12:57:48.662  INFO 40758 --- [  DestroyJavaVM] o.s.l.agent.SpringLoadedPreProcessor     : SpringLoaded preprocessing: classname=java/util/IdentityHashMap$KeyIterator classloader=null typeRegistry=null
:bootRun FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':bootRun'.
> Process 'command '/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/bin/java'' finished with non-zero exit value 1

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 10.923 secs

ちなみに ./graldew bootRun --stacktrace で起動するとこんなログが出ていたが、これだけでは原因がよくわからなかった。

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':bootRun'.
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:69)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:46)
    at org.gradle.api.internal.tasks.execution.PostExecutionAnalysisTaskExecuter.execute(PostExecutionAnalysisTaskExecuter.java:35)
    at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:64)
    at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:58)
    at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:52)
    at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:52)
    at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:53)
    at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:203)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:185)
    at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.processTask(AbstractTaskPlanExecutor.java:66)
    at org.gradle.execution.taskgraph.AbstractTaskPlanExecutor$TaskExecutorWorker.run(AbstractTaskPlanExecutor.java:50)
    at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor.process(DefaultTaskPlanExecutor.java:25)
    at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter.execute(DefaultTaskGraphExecuter.java:110)
    at org.gradle.execution.SelectedTaskExecutionAction.execute(SelectedTaskExecutionAction.java:37)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
    at org.gradle.execution.DefaultBuildExecuter.access$000(DefaultBuildExecuter.java:23)
    at org.gradle.execution.DefaultBuildExecuter$1.proceed(DefaultBuildExecuter.java:43)
    at org.gradle.execution.DryRunBuildExecutionAction.execute(DryRunBuildExecutionAction.java:32)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:37)
    at org.gradle.execution.DefaultBuildExecuter.execute(DefaultBuildExecuter.java:30)
    at org.gradle.initialization.DefaultGradleLauncher$4.run(DefaultGradleLauncher.java:154)
    at org.gradle.internal.Factories$1.create(Factories.java:22)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:52)
    at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:151)
    at org.gradle.initialization.DefaultGradleLauncher.access$200(DefaultGradleLauncher.java:32)
    at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:99)
    at org.gradle.initialization.DefaultGradleLauncher$1.create(DefaultGradleLauncher.java:93)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:90)
    at org.gradle.internal.progress.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:62)
    at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:93)
    at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:82)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:94)
    at org.gradle.tooling.internal.provider.ExecuteBuildActionRunner.run(ExecuteBuildActionRunner.java:28)
    at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:43)
    at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:28)
    at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:77)
    at org.gradle.launcher.exec.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:47)
    at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:52)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:37)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:26)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:34)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:74)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:72)
    at org.gradle.util.Swapper.swap(Swapper.java:38)
    at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:72)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.health.DaemonHealthTracker.execute(DaemonHealthTracker.java:47)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:66)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:72)
    at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:36)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.health.HintGCAfterBuild.execute(HintGCAfterBuild.java:41)
    at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:120)
    at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:50)
    at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:246)
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54)
    at org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40)
Caused by: org.gradle.process.internal.ExecException: Process 'command '/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/bin/java'' finished with non-zero exit value 1
    at org.gradle.process.internal.DefaultExecHandle$ExecResultImpl.assertNormalExitValue(DefaultExecHandle.java:367)
    at org.gradle.process.internal.DefaultJavaExecAction.execute(DefaultJavaExecAction.java:31)
    at org.gradle.api.tasks.JavaExec.exec(JavaExec.java:75)
    at org.springframework.boot.gradle.run.BootRunTask.exec(BootRunTask.java:63)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:75)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.doExecute(AnnotationProcessingTaskFactory.java:227)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:220)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:209)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:585)
    at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:568)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:80)
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:61)
    ... 68 more

既知の事象として issue が挙がっているのかもしれないが、ともあれ Spring Loaded は使えるようになった。

devtools の automatic restart

先ほど Tomcat 再起動をさせていたのは spring-boot-devtools の所作だった。これまで最初にドキュメントからコピペした build.gradle で何となく有効になっていたが、どういったものは把握していなかった。さらに進んだ「20. Developer tools」に説明がある。

「20.2 Automatic restart」にあるようにクラスパス上のファイルに変更があると devtools は Tomcat を再起動するようだ。デフォルトではクラスパス上のすべてのリソースの変更を検知して再起動をかける。この変更検知対象は設定で変更できるようだ。

devtools の再起動の仕組みは単に再起動しているわけではなかった。base と restart という二つのクラスローダを使い分けていて jar ファイルの中にいるような変更されないクラスについては base の方でロードし、src/main/java 配下のように頻繁に変更されるクラスは restart の方でロードしている。再起動するときはこの restart というクラスローダだけを作りなおすことで、単純に再起動するよりもかなりスピーディに起動できているとのこと。

そして restart よりもやはり reload が良いという場合は、Spring Loaded だけでなく ZeroTurnaround 社の JRebel も検討すると良いとのこと(これも実は使ったことがないので、機を見て試してみたい)。

とりあえずこの辺で

ということで、だいぶ長くなったので、この記事はこの辺で終わろうかと思います。

Spring Boot はとにかく手軽に試せるところが良いと思いました。Spring をこれまで使った経験があればアノテーションが何をしてくれるか想像がつくし、頑張って初期セットアップする必要がないのはスムーズで良いですね。

目下、テスト関連や Swagger 連携、OAuth2 あたりに関心があるので、まずはその辺を試してみたいと思っています。あとは、一応 Scala で使ったらどうかというのも一回自分でやってみるかなと。ただ、Gradle も Groovy で書いているわけなので Groovy でやってみるのもアリなのかなと思ったりしました。

15
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
17