Building an Application with Spring Boot

f:id:Naotsugu:20150110034558p:plain

このガイドでは Spting Boot が迅速なアプリケーション開発にどのように役立つかをサンプルを提供します。 ちなみに http://start.spring.io/ で入力項目埋めるとプロジェクトの雛形がつくれます。

What you’ll build

これから Spring Boot で簡単なWebアプリケーションを構築して、いくつかの便利なサービスを追加していきます。

What you’ll need

How to complete this guide

以下から雛形をダウンロードできます。

git clone https://github.com/spring-guides/gs-spring-boot.git

が、ここではスクラッチで Gradle 使って進めることにします。

Build with Gradle

プロジェクト用のディレクトリを用意して、その中にソースディレクトリ作成します。

mkdir -p src/main/java/hello

build.gradle 作成していきます。

touch build.gradle

内容は以下。

    buildscript {
        repositories {
            mavenCentral()
        }
        dependencies {
            classpath("org.springframework.boot:spring-boot-gradle-plugin:1.1.10.RELEASE")
        }
    }
    
    apply plugin: 'java'
    apply plugin: 'eclipse'
    apply plugin: 'idea'
    apply plugin: 'spring-boot'

    jar {
        baseName = 'gs-spring-boot'
        version =  '0.1.0'
    }
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        // tag::jetty[]
        compile("org.springframework.boot:spring-boot-starter-web") {
            exclude module: "spring-boot-starter-tomcat"
        }
        compile("org.springframework.boot:spring-boot-starter-jetty")
        // end::jetty[]
        // tag::actuator[]
        compile("org.springframework.boot:spring-boot-starter-actuator")
        // end::actuator[]
        testCompile("junit:junit")
    }
    
    task wrapper(type: Wrapper) {
        gradleVersion = '1.11'
    }

spring-boot-gradle-plugin が以下の面倒を見てくれます。

  • 必要な jar あつめて fat-jar を作る
  • main メソッド見つけてjar のマニフェストに自動的に書いて実行可能jarにする
  • Spring Boot の依存を(書き換えることもできるけど)勝手に設定する

Learn what you can do with Spring Boot

Spring Boot はクラスパスと Beans の設定を見て必要な設定を自動的おこないます。

といっても、コード生成したり自動的に設定ファイル作るのではなくて、アプリケーションの起動時にコンテキストに応じて動的に設定を組み立てています。

Create a simple web application

作成するクラスのファイルを用意します。

touch src/main/java/hello/HelloController.java
touch src/main/java/hello/Application.java

HelloController.java はこんな感じになります。

package hello;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class HelloController {

    @RequestMapping("/")
    public String index() {
        return "Greetings from Spring Boot!";
    }

}

@RestController は Spring MVC が web リクエストを扱うことを定義しています。

@RequestMapping は / のリクエストがこのメソッドマッピングされる定義です。 このメソッド呼ばれるとプレーンテキストを返しますが、これは @RestController@Controller@ResponseBody を合わせた定義になってるためです。

では Application.java

package hello;

import java.util.Arrays;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {

    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(Application.class, args);

        System.out.println("Let's inspect the beans provided by Spring Boot:");

        String[] beanNames = ctx.getBeanDefinitionNames();
        Arrays.sort(beanNames);
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
    }
    
}

@Configuration はアプリケーションコンテキストのソースファイルのしるし。

@EnableAutoConfiguration は Spring Boot にクラスパスにあるBeanをよろしく設定してねというしるし。

@ComponentScan で hello パッケージのコンポーネントやら設定やらサービスやらを自動で見つけてねというしるし。

SpringApplication.run() でアプリケーションが launch されるけど、web.xml とかは全く不要です。

Run the application

起動してみます。最初だけ wrapper します。

gradle wrapper

あとはこれだけ。

./gradlew build && java -jar build/libs/gs-spring-boot-0.1.0.jar
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v1.1.10.RELEASE)

ロゴが出て起動します。

localhost:8080 でアクセスすると、Greetings from Spring Boot! が返れば成功です。

Add Unit Tests

次テスト。依存関係に以下を追加。

testCompile("org.springframework.boot:spring-boot-starter-test")

単体テストクラス作ります。

mkdir -p src/test/java/hello
touch src/test/java/hello/HelloControllerTest.java

テストコードは以下

package hello;

import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MockServletContext.class)
@WebAppConfiguration
public class HelloControllerTest {

    private MockMvc mvc;

    @Before
    public void setUp() throws Exception {
        mvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
    }

    @Test
    public void getHello() throws Exception {
        mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(content().string(is("Greetings from Spring Boot!")));
    }
}

フルスタックのインテグレーションテストも簡単に書けます。

テストクラス用意して、

touch src/test/java/hello/HelloControllerIT.java

中身はこんな感じ

package hello;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

import java.net.URL;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.client.RestTemplate;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@IntegrationTest({"server.port=0"})
public class HelloControllerIT {

    @Value("${local.server.port}")
    private int port;

    private URL base;
    private RestTemplate template;

    @Before
    public void setUp() throws Exception {
        this.base = new URL("http://localhost:" + port + "/");
        template = new TestRestTemplate();
    }

    @Test
    public void getHello() throws Exception {
        ResponseEntity<String> response = template.getForEntity(base.toString(), String.class);
        assertThat(response.getBody(), is("Greetings from Spring Boot!"));
    }
}

@IntegrationTest("${server.port=0}") で組み込みのサーバがランダムなポート選ぶので、実際のポートは @Value("${local.server.port}") で得られます。

以下でテスト実行。

./gradlew test

Add production-grade services

商用向けに actuator module 追加するので、以下の依存関係追加。

compile("org.springframework.boot:spring-boot-starter-actuator")

実行。

./gradlew build && java -jar build/libs/gs-spring-boot-0.1.0.jar

http://localhost:8080/health でアクセスするとヘルスチェック文字 {"status":"UP"} が得られます。あと、http://localhost:8080/env環境変数取れたり、詳細は Spring Boot Reference Guide 参照。

商用向けなので、エンドポイントからの shutdown も出来なくなってます。

curl -X POST localhost:8080/shutdown

今回追加したのは spring-boot-starter-actuator だけど、いろんな starter があります。 Spring Boot Reference Guide

らに詳しくは Spring Boot Reference Guide