Spring Boot + Docker の Java 開発環境構築

# 概要
Spring Boot での Java 開発環境を Docker で構築します。
Java 11 と gradle を使います。

Hello World! を表示させるところまで。
（更新 : リモートデバッグの方法を追記）

# 完成品
https://github.com/tomoyuki-mikami/spring-boot-docker

実行方法

```
$ cd docker
$ docker-compose up -d
```

# 環境
MacOS Catalina 10.15.2
Docker for Mac 2.1.0.5
IntelliJ IDEA Ultimate 2019.3.1
OpenJDK 11（homebrew でインストール済み）

# ローカルでの手順
### プロジェクトの作成

##### IntelliJ がない場合
https://start.spring.io/
こちらで同様の選択をして Generate すると zip でダウンロードされるので、それを使います。

##### IntelliJ がある場合
※ 最近の IntelliJ から spring initializer 使う場合は src 以下ディレクトリが自動作成されないので
IntelliJ を持っていても、上記 zip ダウンロードして読み込むほうがいいのかも…
何か問題があるらしく、オプションを消したらしい。

1. IntelliJ で Create New Project  
<img width="248" alt="スクリーンショット 2019-12-29 10.44.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/7924390a-7b0c-33c8-e359-fc051a940e59.png">  
1. Java 11 の指定、Spring Initializer はデフォルトで
<img width="700" alt="スクリーンショット 2019-12-29 10.45.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/2739bbe3-8d69-4930-5402-145888995ba3.png">  
1. Gradle Config, Java, Jar, 11 を選択。それ以外はお好きに  
<img width="755" alt="スクリーンショット 2019-12-29 10.45.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/b4c8d044-12a5-958c-0188-a2e34a23e6a8.png">  
1. Dependencies は以下を選択したが、Hello World 表示させるだけなら不要  
後々 MySQL に接続したり、ログイン機能を作ったり、View を作成するためにいろいろ入れてある。  
むしろ最初に Hello World 表示させるだけでは、起動しないので build.gradle でコメントアウトしている。
<img width="315" alt="スクリーンショット 2019-12-29 10.46.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/a5107b39-794a-a350-fe10-64fa1b57e990.png">  
1. プロジェクト名とプロジェクトを作成する場所を指定  
<img width="888" alt="スクリーンショット 2019-12-29 10.47.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/6fd21127-a2a1-9f44-a8f2-bc2d85eca261.png">  
これで FINISH を押すと、自動的にプロジェクトが作成され、初回は gradle が実行されます。  
最近の IntelliJ を利用している場合、自動的に src 以下のディレクトリが作成されないので手動で作る。  

### Hello World の作成
src/main/java/com.mika.app 配下にある Application.java を修正
※ initializer で指定したプロジェクト名によってファイル名は異なります。

```java:Application.java
package com.mika.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class Application {

    @RequestMapping("/")
    String Index() {
        return "Hello World!";
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
```
この段階で IntelliJ で実行してみる → たぶん失敗します。
public class Application { の横にある実行ボタンを押す。
<img width="847" alt="スクリーンショット 2019-12-29 11.07.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/36d312ce-67b2-4ca6-3cd7-e0643ed8c160.png">

dependencies に色々入れすぎて無駄に動いているので、コメントアウトが必要です。

### build.gradle の一部をコメントアウト

```build.gradle
dependencies {
//    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
//    implementation 'org.springframework.boot:spring-boot-starter-security'
//    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
//    runtimeOnly 'mysql:mysql-connector-java'
//    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
    testImplementation 'org.springframework.security:spring-security-test'
}
```

これで再度実行
http://localhost:8080 を表示して Hello World が表示されているのを確認。
確認したら停止ボタンで止める。

# Dockerfile, docker-compose.yml の作成
docker ディレクトリを作成し、その配下に Dockerfile, docker-compose.yml を作成
docker-compose を使う理由は、今後ここに MySQL や nginx なども建てる予定のため。

```dockerfile:Dockerfile
FROM openjdk:11

ENV APP_ROOT /app

COPY . $APP_ROOT
WORKDIR $APP_ROOT

ENTRYPOINT ["sh", "./gradlew", "bootRun"]
```

```yml:docker-compose.yml
version: '3'

services:
  web:
    build:
      # Dockerfile の app のコピーのために必要なパス指定
      context: ../
      # context のパスから見た Dockerfile の場所
      dockerfile: docker/Dockerfile
    volumes:
      # :より左の部分は自分の環境に合わせる
      - ~/Dev/java/spring-boot:/app
    ports:
      - "8080:8080"
```

# Docker 環境の起動
```
$ cd docker
$ docker-compose up
```
起動されることを確認し、再度 http://localhost:8080 が表示されることを確認。

# HotDeploy
spring-boot-devtools を利用しているので、HotDeploy が適用されています。
Application.java の Hello World の文字を変えてみると
数秒後に Java が再起動されるのが確認できます（docker-compose up のログから）
ブラウザをリロードすると変更した文字になっているのを確認できます。

（更新）IntelliJ で HotDeploy が効いてなかったので設定を追加
Build, Execution, Deployment の Compiler で Build project automatically にチェック
Advanced Settings の Compiler で Allow auto-make to start even if developed application is currently running にチェック
![スクリーンショット 2022-01-22 16.17.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/62f303a6-ae5f-29c0-b42f-91e34d098298.png)
![スクリーンショット 2022-01-22 16.19.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/57cc006c-f7fb-f995-2026-67ede548620e.png)



# IntelliJ でリモートデバッグ
##### IntelliJ の設定
開発する上でブレークポイントとか置いて止めたいので、リモートデバッグの設定をします。
まずは IntelliJ のメニューから Run → Edit Configurations... を開き
左上の + ボタンから Remote を選択します。
以下のような設定のまま OK を押して閉じます。
※ ここの Command line arguments for remote JVM にかかれているコマンドを
build.gradle に追記するのでコピーしておきます。
<img width="869" alt="スクリーンショット 2019-12-29 17.22.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/7264ca2f-1e8f-5453-563c-8daa59c7e349.png">

##### build.gradle に以下を追記
```build.gradle
bootRun {
    // for remote debug
    jvmArgs "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
}
```

##### docker-compose.yml の修正
リモートデバッグで 5005 ポートを利用するので、それを記載。

```yml:docker-compose.yml
ports:
   - "8080:8080"
   # for remote debug
   - "5005:5005"
```

##### docker 起動
再度 docker-comose up して、待ち受ける状態になっていることを確認する。

```
web_1  | > Task :bootRun
web_1  | Listening for transport dt_socket at address: 5005
```

IntelliJ でさきほど設定した `remote-debug` でデバッグを起動しておく

Application.java の Hello World にブレークポイントを置いて
http://localhost:8080 にアクセスすると、その位置で止まることが確認できる。
<img width="1090" alt="スクリーンショット 2019-12-29 17.28.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265518/1f1d1ffc-b5fe-709e-7d1f-5b2535623861.png">

# おわりに
とりあえずかんたんに構築する方法をまとめました。