Spring BootのGetting Startedを軽く読んでみる

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

丹内です。昨日に引き続きSpring Bootネタです。
本日はGetting Startedのうち、webアプリを作る上で基本的なタイトルの内容を読んでみようと思います。

Getting Startedのフォーマット

インデックスページを見るとたくさんあって読み切れないようにも見えるのですが、詳細ページの構造は以下のようになっています。

  • What you’ll build
  • What you’ll need
  • How to complete this guide
  • 本題
  • Summary

このうち本題とSummaryが各ドキュメントによって異なります。なので、アプリケーションを作る上で疑問になった点が出たら、そのドキュメントの本題とSummaryだけを見れば済みそうです。

Spring Boot with Docker

ということでいきなりDockerです。
文字列を返す簡単なアプリをセットアップした後、./gradlew buildでビルドして以下のDockerfileでコンテナ化しています。

FROM java:8
VOLUME /tmp
ADD gs-spring-boot-docker-0.1.0.jar app.jar
RUN bash -c 'touch /app.jar'
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

また、GradleでDockerイメージのビルドを行う設定もしています。

buildscript {
    ...
    dependencies {
        ...
        classpath('se.transmode.gradle:gradle-docker:1.2')
    }
}

group = 'springio'

...
apply plugin: 'docker'

task buildDocker(type: Docker, dependsOn: build) {
  push = true
  applicationName = jar.baseName
  dockerfile = file('src/main/docker/Dockerfile')
  doFirst {
    copy {
      from jar
      into stageDir
    }
  }
}

Spring Cloud + Lattice

またコンテナ絡みです。Latticeという、マイクロサービスで複数コンテナを協調動作させる際のユーティリティとSpring Bootの統合に関するGetting Startedです。

Latticeは以下のことが可能なツールのようです。

  • Cluster scheduling
  • HTTP load balancing
  • Log aggregation
  • Health management

このツール単体でも非常に興味深い!
...ドキュメントを読み進めましょう。https://github.com/cloudfoundry-incubator/lattice.gitをgit cloneしてvagrant upでLattice用の環境を作った後、latticeコマンドをセットアップし、コンテナ化したSpring Bootアプリケーションをデプロイしています。

Consuming a RESTful Web Service

SpringのRestTemplateを使ったGetting Startedです。
最初に@JsonIgnorePropertiesをアノテートしたドメインクラスを作り、Application.javaではRestTemplateを使ってドメインクラスのオブジェクトをJSONに変換してレスポンスにしています。

Building a Hypermedia-Driven RESTful Web Service

HATEOASなサンプルを作成するGetting Startedです。
ResourceSupportをextendして@JsonCreatorをアノテートしたGreetingドメインクラスを作り、ControllerとApplication.javaを作って動かすだけで、_linksプロパティが付くハイパーメディアなレスポンスになります。

Building an Application with Spring Boot

Spring Bootによるアプリ作成の流れを一通り書いています。
途中でControllerのテストについて書かれてあります。
testCompile("org.springframework.boot:spring-boot-starter-test")の依存を追加し、テストを書きます。

@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(equalTo("Greetings from Spring Boot!")));
    }
}

メソッドチェーンでexpectationしているのが便利そうだと思いました。

Building a RESTful Web Service with Spring Boot Actuator

Spring Boot ActuatorはSpring Bootのサブプロジェクトで、production環境でアプリを動かすために必要なフィーチャーを追加してくれるようです。
org.springframework.boot:spring-boot-starter-actuatorを依存関係に追加するだけで、Spring Bootアプリケーションのレスポンスにタイムスタンプが付いたりします。また、監査やメトリクスなどの機能もあるようです。

Accessing Relational Data using JDBC with Spring

JdbcTemplateを使ってRDBにアクセスします。

@SpringBootApplication
public class Application implements CommandLineRunner {

    private static final Logger log = LoggerFactory.getLogger(Application.class);

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

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Override
    public void run(String... strings) throws Exception {

        log.info("Creating tables");

        jdbcTemplate.execute("DROP TABLE customers IF EXISTS");
        jdbcTemplate.execute("CREATE TABLE customers(" +
                "id SERIAL, first_name VARCHAR(255), last_name VARCHAR(255))");

        // Split up the array of whole names into an array of first/last names
        List<Object[]> splitUpNames = Arrays.asList("John Woo", "Jeff Dean", "Josh Bloch", "Josh Long").stream()
                .map(name -> name.split(" "))
                .collect(Collectors.toList());

        // Use a Java 8 stream to print out each tuple of the list
        splitUpNames.forEach(name -> log.info(String.format("Inserting customer record for %s %s", name[0], name[1])));

        // Uses JdbcTemplate's batchUpdate operation to bulk load data
        jdbcTemplate.batchUpdate("INSERT INTO customers(first_name, last_name) VALUES (?,?)", splitUpNames);

        log.info("Querying for customer records where first_name = 'Josh':");
        jdbcTemplate.query(
                "SELECT id, first_name, last_name FROM customers WHERE first_name = ?", new Object[] { "Josh" },
                (rs, rowNum) -> new Customer(rs.getLong("id"), rs.getString("first_name"), rs.getString("last_name"))
        ).forEach(customer -> log.info(customer.toString()));
    }
}

さくっとLambdaが使われていて最高だと思います。

Serving Web Content with Spring MVC

今まではJSONばかり返していましたが、このGetting StartedではHTMLを返します。
src/main/resources/templates/greeting.htmlとして、以下のようにテンプレートを書きます。

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Getting Started: Serving Web Content</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <p th:text="'Hello, ' + ${name} + '!'" />
</body>
</html>

Creating an Asynchronous, Event-Driven Application with Reactor

AWS Lambdaのような非同期イベント駆動型アプリケーションを作成するGetting Startedです。
まずReceiverを作ります。

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import reactor.bus.Event;
import reactor.fn.Consumer;

import java.util.concurrent.CountDownLatch;

@Service
class Receiver implements Consumer<Event<Integer>> {

    @Autowired
    CountDownLatch latch;

    RestTemplate restTemplate = new RestTemplate();

    public void accept(Event<Integer> ev) {
        QuoteResource quoteResource =
                restTemplate.getForObject("http://gturnquist-quoters.cfapps.io/api/random", QuoteResource.class);
        System.out.println("Quote " + ev.getData() + ": " + quoteResource.getValue().getQuote());
        latch.countDown();
    }

}

次にPublisherを作ります。

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.bus.Event;
import reactor.bus.EventBus;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

@Service
public class Publisher {

    @Autowired
    EventBus eventBus;

    @Autowired
    CountDownLatch latch;

    public void publishQuotes(int numberOfQuotes) throws InterruptedException {
        long start = System.currentTimeMillis();

        AtomicInteger counter = new AtomicInteger(1);

        for (int i = 0; i < numberOfQuotes; i++) {
            eventBus.notify("quotes", Event.wrap(counter.getAndIncrement()));
        }

        latch.await();

        long elapsed = System.currentTimeMillis() - start;

        System.out.println("Elapsed time: " + elapsed + "ms");
        System.out.println("Average time per quote: " + elapsed / numberOfQuotes + "ms");
    }

}

あとはドメインクラスとApplication.javaですが、Application.javaでは作成したコンポーネントを登録しています。

AWS Lambdaに処理を切り出す前の緩衝材として使えそうです!!

Securing a Web Application

Spring Securityを使ってアプリケーションに認証機能を追加するGetting Startedです。
org.springframework.boot:spring-boot-starter-securityの依存関係を追加して、src/main/java/hello/WebSecurityConfig.javaを作成します。

package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER");
    }
}

これでOKです。

まとめ

Spring Bootは膨大なSpringプロジェクトの成果を簡単に使えるようにする上、ドキュメントも豊富で良いと思いました。
マイクロサービスのこともよく考えられていて、このエコシステムに乗ると効率よく学習できそうです。