Spring Boot で Ajax を実装する単純なサンプル

# はじめに

初心者用の丁度よい記事が見当たらなかったので、**Spring Boot** で **Ajax** を動かすためのサンプルコードを残しておきます。
文字列を送信すると、２回繰り返して表示されるだけの簡単なものです。
![2021-07-31 205713.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/551412/486b9e57-36e2-2d78-92e5-291d466d664f.png)
Ajax は **jQuery** を使用して書きますが、参考として**ネイティブ Java Script** も併記しておきます。
**CSRF 対策**が有効な場合の対処についても触れておきます。

＜参考サイト＞
・[Ajax：Jsonで受け取り画面遷移なしに更新する](https://arakan-pgm-ai.hatenablog.com/entry/2017/04/13/194032)
・[SpringBoot × Ajax通信による画面遷移](https://learning-collection.com/springboot-x-ajax%E9%80%9A%E4%BF%A1%E3%81%AB%E3%82%88%E3%82%8B%E7%94%BB%E9%9D%A2%E9%81%B7%E7%A7%BB/)
・[どのようにしてajaxでSpring Controllerからデータを取得できますか？](https://www.it-swarm-ja.com/ja/jquery/%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E3%81%97%E3%81%A6ajax%E3%81%A7spring-controller%E3%81%8B%E3%82%89%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E5%8F%96%E5%BE%97%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%81%8B%EF%BC%9F/1048874523/)

# 1. プロジェクトの作成

プロジェクトは、どのように作成しても構いませんが、一応書いておきます。

サンプルでは、ビルドツールは「Maven」、Java バージョンは「11」、パッケージングは「Jar」を選んでいます。とはいえ、何でも構いません。

特別なライブラリも入れる必要はありません。
次のように、「Spring Web」と「Thymeleaf」だけ選んでおきます。
![2021-07-31 193646.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/551412/dcb52575-8be3-4dff-2850-ad51c32bded0.png)
Thymeleaf は直接には使用しませんが、HTML ファイルの読み込みで必要になるので入れておきます（[参考](https://koroworks.jp/spring-boot%EF%BC%8D%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%AF%E5%87%BA%E3%81%AA%E3%81%84%E3%81%8C404-whitelabel-error-page%E3%81%AB%E9%A3%9B%E3%81%B0%E3%81%95%E3%82%8C%E3%82%8B/)）。

# 2. サンプルコード

以下、**POST メソッド**で HTTP リクエストを送信して、サーバからレスポンスを受け取るサンプルコードです。

## 2-1. 作成するファイル

ファイルは、赤枠の３つを作成します。
![2021-07-31 204126.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/551412/29754636-54e2-b988-19c4-1cd2d3d5aec7.png)
js ファイルは、`/src/main/resources/static` に「js」フォルダを作成した上で格納します。

## 2-2. HTML ファイル

HTML は次のように記述しておきます。

```/src/main/resources/templates/index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Ajax Test</title>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
  <script src="/js/note.js"></script>
</head>
<body>
  <form method="post" action="/test" id="note_form">
    <input type="text" id="note">
    <button type="submit">送信</button>
  </form>
  <div class="notes"></div>
</body>
</html>
```

＜補足＞
`src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"` で、jQuery をGoogleのCDN経由で読み込んでいます（[参考](https://macoblog.com/jquery-saishin/)）。
`src="/js/note.js"` で、使用する JavaScript ファイルを指定します。
`<div class="notes">` のところに Ajax で取得した文字列を表示します。

**＜参考：CSRF対策が有効な場合＞**
なお、Spring Security などを導入していて、CSRF 対策が有効になっている場合は、Thymeleaf を使用して、form タグの action 属性を次のようにする必要があります。

```html:Sample
<html xmlns:th="http://www.thymeleaf.org">
<!-- 略 -->
  <form method="post" th:action="@{/test}" id="note_form">
<!-- 略 -->
```
こうすることで、次のように自動的に CSRF トークンが作成されます。
![2021-08-01 162248.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/551412/11bd02d7-31c3-d305-cedc-4bea82a30a3a.png)
この、CSRF トークンがないまま POST メソッド等でリクエストをしても、サーバ側で拒否されてしまいます。

## 2-3. JavaScript ファイル

### 2-3-1. jQuery で実装（CSRF 対策なし）

jQuery を使用して Ajax の処理を書くと次のようになります。

```/src/main/resources/static/js/note.js
$(function() {
  $("#note_form").on("submit", function(e) {
    e.preventDefault();  // デフォルトのイベント(ページの遷移やデータ送信など)を無効にする
    $.ajax({
      url: $(this).attr("action"),  // リクエストを送信するURLを指定（action属性のurlを抽出）
      type: "POST",  // HTTPメソッドを指定（デフォルトはGET）
      data: {
        note: $("#note").val()  // 送信データ
      }
    })
    .done(function(data) {
      $(".notes").append(`<div>${data}</div>`);  // HTMLを追加
      $("#note").val("");  // 入力欄を空にする
    })
    .fail(function() {
      alert("error!");  // 通信に失敗した場合の処理
    })
  });
});
```

**＜補足＞**
`$(this).attr("action")` で、action 属性のURL文字列（"/test"）を取り出しています。
`type: "POST"` で、HTTP メソッドを「POST」と指定しています。
`data: { note: $("#note").val() }` で、リクエストで送るデータを「キー/値のペア」で指定しています。`$("#note")`により、id="note" の要素を探し出しています。
`$(".notes").append([HTML文]);` で、class="notes" のタグの子要素として `[HTML文]` を追加しています。

**＜Ajax実装のひな形＞**
jQuery による Ajax のひな形を簡単に示すと次のとおりです（詳細は[こちら](https://js.studio-kingdom.com/jquery/ajax/ajax)を参照してください）。

```js:jQueryのAjax実装のひな形
    $.ajax({
      url: /* リクエストを送信するURLを指定 */,
      type: /* HTTPメソッドを指定 */,
      data: /* 送信データを指定 */,
      dataType: /* レスポンスデータの形式を指定 */
    })
    .done(function(data) {
      // (1) リクエストが成功した場合に行う処理
    })
    .fail(function() {
      // (2) リクエストが成功しなかった場合に行う処理
    })
    .always(function() {
      // (3) リクエストの成功・失敗に関わらず行う処理
    });
```

**＜参考サイト＞**
・[JavaScript 日本語リファレンス jQuery $.ajax()](https://js.studio-kingdom.com/jquery/ajax/ajax)
・[一般的なAjax通信を実装するには？（$.ajax）](https://www.buildinsider.net/web/jqueryref/059)

### 2-3-2. （参考）jQuery で実装（CSRF 対策あり）

この記事で作成しているサンプルコードでは、CSRF 対策は考慮する必要はありませんが、参考までに CSRF 対策が有効になっている場合のコードも併せて書いておきます。

Spring Security などを導入して CSRF 対策を有効にしている場合は、**CSRF トークン**を併せて送信しないと、認可エラーが生じます。
具体的には、送信するデータの項目に、`_csrf: $("*[name=_csrf]").val()` と追加することで、POST メソッドによるリクエストが可能となります。

```/src/main/resources/static/js/note.js
$(function() {
  $("#note_form").on("submit", function(e) {
    e.preventDefault();
    $.ajax({
      url: $(this).attr("action"),
      type: "POST",
      data: {
        note: $("#note").val(),
        _csrf: $("*[name=_csrf]").val()  // CSRFトークンを送信
      }
    })
    .done(function(data) {
      $(".notes").append(`<div>${data}</div>`);
      $("#note").val("");
    })
    .fail(function() {
      alert("error!");
    })
  });
});
```

### 2-3-3. （参考）ネイティブ JavaScript で実装

こちらも参考ですが、ネイティブ JavaScript で Ajax を書くと次のようになります。

```/src/main/resources/static/js/note.js
window.addEventListener("load", function() {
  document.getElementById("note_form").addEventListener("submit", function(e) {
    e.preventDefault();  // デフォルトのイベントを無効にする
    let inputText = document.getElementById("note").value;  // 入力値を取得
    let url = document.getElementById("note_form").getAttribute("action");  // action属性のurlを抽出
    let data = "note=" + encodeURIComponent(inputText);  // URIエンコード
    let httpRequest = new XMLHttpRequest();  // XMLHttpRequestのインスタンス作成
    httpRequest.open("POST", url, true);  // open(HTTPメソッド, URL, 非同期通信[true:default]か同期通信[false]か）
    httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");  // リクエストヘッダーを追加(URIエンコードで送信)
    httpRequest.send(data);  // sendメソッドでサーバに送信
    httpRequest.onreadystatechange = function() {
      if (httpRequest.readyState === 4) {  // readyStateが4になればデータの読込み完了
        if (httpRequest.status === 200) {  // statusが200の場合はリクエストが成功
          let divElm = document.createElement("div");  // 追加用のdivタグを作成
          divElm.appendChild(document.createTextNode(httpRequest.response));  // レスポンスで得た値をdivタグの子要素に追加
          document.getElementsByClassName("notes")[0].appendChild(divElm);  // class="notes" の子要素として追加
          document.getElementById("note").value = "";  // テキストエリアを空白に戻す
        } else {  // statusが200以外の場合はリクエストが適切でなかったとしてエラー表示
          alert("error");
        }
      }
    };
  }, false);
});
```
補足は、コメント部分を参照してください。

**＜CSRF 対策が有効な場合＞**
Spring Security を導入すると、CSRF 対策への対応も必要となります。
jQuery の場合と同様に、次のように `data` の内容を変更して、CSRF トークンを送信するようにすればリクエストをすることができます。

```js:修正前
    let data = "note=" + encodeURIComponent(inputText);
```

```js:修正後
    let csrfToken = document.getElementsByName("_csrf")[0].value;  // csrfトークンを取得
    let data = "note=" + encodeURIComponent(inputText) + "&_csrf=" + encodeURIComponent(csrfToken);  // リクエストbodyにcsrfトークンを追加
```

JavaScript の Ajax については、次の記事で細かく書いていますのでご参考としていただければ幸いです。
・[RailsにおけるAjaxの実装（JavaScriptとjQueryのコード比較）](https://qiita.com/t-yama-3/items/7148d25b17e70ceb48d6)
・[JavaScriptの多次元ハッシュ（連想配列）を一括でURIエンコードする](https://qiita.com/t-yama-3/items/e4c16bcbd42fd7ec50f8)

## 2-4. Controller

コントローラーは、次のように書きます。

```/src/main/java/com/example/demo/TestController.java
package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping
public class TestController {
	@GetMapping
	public String index() {
		return "index";
	}

	@PostMapping("/test")
	@ResponseBody
	public String note(@RequestParam String note) {
		return note + note;
	}
}
```

**① リクエストデータを受け取る**
クライアントから送信されたリクエストパラメータを受け取るには、`＠RequestParam` アノテーションを使用します。
メソッドの引数に、`＠RequestParam String note` と指定することで、キーが `note` のデータを取得することができます。

**② メソッドの戻り値をレスポンスデータにする**
`＠Controller` で作成したコントローラーのメソッドの戻り値は、基本的に view の遷移先となります。
この場合、メソッドに `＠ResponseBody` アノテーションを付けることで、戻り値を HTTP レスポンスのコンテンツとすることができます。

なお、`＠RestController` を使用してコントローラーを作成すると、メソッドの戻り値がそのままレスポンスのコンテンツとなります（後述「[3-2. ＠RestController を使用する場合](#3-2-restcontroller-を使用する場合)」に記載）。

**サンプルコードは以上**です。
これで、Ajax を使用したデータのやり取りができます。

# 3. 補足

補足として、別のパターンによる実装にも触れておきます。

## 3-1. レスポンスデータを JSON で返す場合

レスポンスデータは JSON で返すことの方が多いと思いますので、そのパターンを書いておきます。

### 3-1-1. JavaScript ファイル

JavaScript ファイルでは、レスポンスデータを JSON 形式で受け取れるように修正します。

#### 3-1-1-1. jQuery で実装

```/src/main/resources/static/js/note.js
$(function() {
  $("#note_form").on("submit", function(e) {
    e.preventDefault();
    $.ajax({
      url: $(this).attr("action"),
      type: "POST",
      data: {
        note: $("#note").val()
      },
      dataType: "json"  // レスポンスデータをjson形式と指定する
    })
    .done(function(data) {
      $(".notes").append(`<div>${data.note}</div>`);  // JSON形式のレスポンスからnoteを取得
      $("#note").val("");
    })
    .fail(function() {
      alert("error!");
    })
  });
});
```
修正したのは、コメントを付した２行です。
`dataType: "json"` で、レスポンスデータをjson形式と指定しています。
`${data.note}` で、JSON 形式で受け取った `data` から、キーが `note` の値を取得しています。

なお、Spring Security を導入していると、**CSRF トークン**の送信も必要となります。
実装方法は、前述の「[2-3-2. （参考）jQuery で実装（CSRF 対策あり）](#2-3-2-参考jquery-で実装csrf-対策あり)」を参照してください。

#### 3-1-1-2. （参考）ネイティブ JavaScript で実装

ネイティブ javaScript で書くと次のとおりです。

```/src/main/resources/static/js/note.js
window.addEventListener("load", function() {
  document.getElementById("note_form").addEventListener("submit", function(e) {
    e.preventDefault();
    let inputText = document.getElementById("note").value;
    let url = document.getElementById("note_form").getAttribute("action");
    let data = "note=" + encodeURIComponent(inputText);
    let httpRequest = new XMLHttpRequest();
    httpRequest.open("POST", url, true);
    httpRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    httpRequest.setRequestHeader("Accept", "application/json");  // リクエストヘッダーを追加（クライアントが理解できるコンテンツタイプをサーバに通知）
    httpRequest.responseType = "json";  // レスポンスデータをjson形式と指定
    httpRequest.send(data);
    httpRequest.onreadystatechange = function() {
      if (httpRequest.readyState === 4) {
        if (httpRequest.status === 200) {
          let divElm = document.createElement("div");
          divElm.appendChild(document.createTextNode(httpRequest.response.note));  // JSON形式のレスポンスからnoteを取得
          document.getElementsByClassName("notes")[0].appendChild(divElm);
          document.getElementById("note").value = "";
        } else {
          alert("error");
        }
      }
    };
  }, false);
});
```

修正したのは、コメントを付した部分です。

**① レスポンスデータをjson形式と指定**
レスポンスデータをjson形式と指定するには、次の２行が必要です.


```js:Sample
    httpRequest.setRequestHeader("Accept", "application/json");
    httpRequest.responseType = "json";
```

最初の行では、クライアントが受け取るデータタイプを `JSON` と指定しています（[参考記事](https://qiita.com/t-yama-3/items/9547445a6c77d1956dde)）。
２行目は、受け取るデータの形式が JSON 形式であること指定しています（指定しなければ`"note":"hogehoge"` という形のままですが、指定すれば`note: "hogehoge"` という形のデータとして受け取れるので、その後の処理が簡単になります）。

**② JSON 形式のデータから値を取得**
次の１行で、受け取ったデータ `httpRequest.response` から、キーが `note` のデータを取り出して、HTML コードを生成しています。

```js:Sample
    divElm.appendChild(document.createTextNode(httpRequest.response.note));
```

なお、Spring Security を導入していると、**CSRF トークン**の送信も必要となります。
実装方法は、前述の「[2-3-3. （参考）ネイティブ JavaScript で実装](#2-3-3-参考ネイティブ-javascript-で実装)」を参照してください。

### 3-1-2. Controller

コントローラーでは、次のようにして、レスポンスデータを単純に JSON 形式にしています。
これにより、戻り値は `"{"note":"テストテスト"}"` というような文字列になります。

```/src/main/java/com/example/demo/TestController.java
// 略
@Controller
@RequestMapping
public class TestController {
    // 略
    @PostMapping("/test")
    @ResponseBody
    public String note(@RequestParam String note) {
        String json = "{\"note\":\"" + note + note + "\"}";
        return json;
    }
}
```

＜参考サイト＞
・[JSONとは？データフォーマット（データ形式）について学ぼう！](https://products.sint.co.jp/topsic/blog/json#toc-2)

## 3-2. ＠RestController を使用する場合

`＠Controller` に代えて **`＠RestController`** を使用すると、メソッドの戻り値がそのままレスポンスのコンテンツになります。
Ajax のみのコントローラーを作成する場合は、こちらを使用した方がシンプルに記述できます。

```/src/main/java/com/example/demo/TestController.java
// 略
@RestController
@RequestMapping
public class TestController {
    @GetMapping
    public ModelAndView index() {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("index");
        return mav;
    }
    
    @PostMapping("/test")
    public String note(@RequestParam String note) {
        return note + note;
    }
}
```

＜参考サイト＞
・[Spring BootのRestControllerからHTMLを生成する方法](https://salumarine.com/how-to-render-html-from-rest-controller-with-spring-boot-app/)

# さいごに

とりあえず、順序通りにコードを書けば動くものを記事として残しておきました。
まだ、色々と試している段階なので、CSRF トークンの送信方法については、もっと簡単な方法があるのかもしれません。より簡単な方法がわかりましたら追記するようにします。