【Spring Boot】作成したREST APIを使いたい【RestTemplate】

## はじめに
APIについて学習中です。
前回REST API作成について記事を書きました。
[【Spring Boot + MyBatis】はじめてREST APIを作成してみる](https://qiita.com/1noseA/items/333bd451d928d5bed911)

そのAPIプロジェクトを画面側プロジェクトで使いたいのですが、使うところまで書いている記事は少ないと思ったので今回書きます。
※検索の仕方が悪かっただけでRestTemplateについての記事はたくさんあります
（参考記事は最後に記載）

## getForObjectで一覧取得してみる
RestTemplateにはいろいろなメソッドがありますが、getForObjectがお手軽そうだったので、はじめに使用してみました。
（RequestEntityとexchangeメソッドを使う方がよいとのことなので、この後書き換えます）

```RestConfig.java
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestConfig {

    @Bean
    public RestTemplate restTemplate() {
        RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
        return restTemplateBuilder.build();
    }
}
```
レスポンスボディの型を定義
```Manuals.java
import lombok.Data;
import java.time.LocalDate;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Manuals {

    // マニュアルID
    public int manualId;

    // 社員ID
    public int userId;

    // 表示順
    public int displayOrder;

    // タイトル
    public String title;

    // 掲載開始日
    public LocalDate startDate;

    // 掲載終了日
    private LocalDate endDate;

    // 内容
    public String content;

    // リンク
    public String link;

    @Override
    public String toString() {
        return "Manuals{" +
                "manualId='" + manualId +
                ", userId=" + userId +
                ", displayOrder=" + displayOrder +
                ", title=" + title +
                ", startDate=" + startDate +
                ", endDate=" + endDate +
                ", content=" + content +
                ", link=" + link +
                '}';
    }
}
```
```ManualsController.java
import java.util.Arrays;
import java.util.List;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.client.RestTemplate;

@Controller
public class ManualsController {

    @Autowired
    RestTemplate restTemplate;

    // マニュアルAPI 一覧取得URL
    private static final String API_URL = "http://localhost:8081/manual-api";

    @GetMapping("/manual/list")
    public String index(Model model) {
        // ★一覧取得
        Manuals[] manualArray = restTemplate.getForObject(API_URL, Manuals[].class);
        List<Manuals> manualList = Arrays.asList(manualArray);
        model.addAttribute("manualList", manualList);
        
        return "manual/list";
    }
}
```

苦戦したのはListで取得する部分です。
Listで取得しようとしていたのですが、`「JSON parse error: Cannot deserialize value of type 'com.sample.dto.ManualList' from Array value (token 'JsonToken.START_ARRAY')」`といったエラーが出て、解決に時間がかかりました。
（＠JsonTypeInfoとか＠JsonSubTypesは必要ありませんでした）
結果として以下でできました。
```
Manuals[] manualArray = restTemplate.getForObject(GET_LIST_URL, Manuals[].class);
List<Manuals> manualList = Arrays.asList(manualArray); 
```

Controllerの中に以下を入れ、コンソール上にレスポンスが表示されるかどうかも確認しました。
```
System.out.println(manualList.toString());
```
以下のように出力されます。
`
[Manuals{manualId='2, userId=1, displayOrder=2, title=タイトル２, startDate=2024-02-04, endDate=2024-02-04, content=内容2, link=リンク2}, Manuals{manualId='3, userId=1, displayOrder=3, title=タイトル３, startDate=2024-02-04, endDate=2024-02-04, content=内容３, link=リンク３}]
`

```list.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="common :: meta_header('マニュアル一覧',~{::link},~{::script})">
<link rel="stylesheet" th:href="@{/css/manual/list.css}" />
</head>
<body>
    <div th:replace="common :: header"></div>

    <div class="contents">
        <div class="container">
            <div class="listArea mx-auto">
                <th:block th:if="${manualList.size() > 0}">
                    <div class="table-responsive">
                        <table class="table table-striped table-hover">
                            <thead>
                                <tr class="border-bottom border-dark">
                                    <th scope="col">タイトル</th>
                                    <th scope="col">掲載開始日</th>
                                    <th scope="col">内容</th>
                                </tr>
                            </thead>
                            <tbody>
                                <th:block th:each="manualList : ${manualList}">
                                    <tr>
                                        <td>
                                            <span th:text="${manualList.title}"></span>
                                        </td>
                                        <td>
                                            <span th:text="${#strings.replace(manualList.startDate, '-', '/')}"></span>
                                        </td>
                                        <td>
                                            <span th:text="${manualList.content}"></span>
                                        </td>
                                    </tr>
                                </th:block>
                            </tbody>
                        </table>
                    </div>
                </th:block>
                <div class="my-5">
                    <button type="button" class="btn btnBack" th:onclick="location.href='/top'">戻る</button>
                </div>
            </div>
        </div>
    </div>

    <div th:replace="common :: footer"></div>
</body>
</html>
```

## 動作確認方法
REST API側プロジェクトのapplication.propertiesに以下記載があることを確認してください。
（画面側プロジェクトが8080を使用するため）
```application.properties
server.port=8081
```

REST API側プロジェクトをデバッグ起動し、画面側プロジェクトもデバッグ起動します。
localhost:8080/~でマニュアル一覧画面に遷移すると、APIの処理を通って画面表示されることを確認しました。

## RequestEntityとexchangeメソッド
ResponseEntityはレスポンスボディとステータスコードを返します。

### exchangeメソッド
- 第一引数：送信先のURL
- 第二引数：リクエストメソッド
- 第三引数：リクエスト情報（ヘッダーやボディ等）
- 第四引数：レスポンスボディの型

### 一覧取得
```ManualsController.java
@Controller
public class ManualsController {

    @Autowired
    RestTemplate restTemplate;

    // マニュアルAPI URL
private static final String API_URL = "http://localhost:8081/manual-api";

    @GetMapping("/manual/list")
    public String index(Model model) {
        // ★一覧取得
        ResponseEntity<Manuals[]> response = restTemplate.exchange(API_URL, HttpMethod.GET, null, Manuals[].class);
        List<Manuals> manualList = Arrays.asList(response.getBody());   
        model.addAttribute("manualList", manualList);
        
        return "manual/list";
    }
}
```

### 詳細取得
URLにパラメータがある場合、URLに「{id}」のようにし、exchangeメソッドの最後の引数に入れるとバインドされます（複数指定も可能です）。
```ManualsController.java
// マニュアルAPI URL（パラメータあり）
private static final String API_URL_PARAM = "http://localhost:8081/manual-api/{id}";

@PostMapping("/manual/detail")
public String detail(int manualId, Model model) {
    // ★詳細取得
    ResponseEntity<Manuals> response = restTemplate.exchange(API_URL_PARAM, HttpMethod.GET, null, Manuals.class, manualId);
    Manuals manualInfo = response.getBody();
    model.addAttribute("manualInfo", manualInfo);
    return "manual/detail";
}
```

### 登録
登録の場合、リクエストヘッダーが必要です。
```ManualsController.java
// 初期表示処理は省略

// 登録処理
@Transactional
@PostMapping("/manual/create")
public String create(@ModelAttribute @Validated ManualsForm manualsForm,
                     BindingResult result,
                     Model model) {
    // 入力エラーチェック
    if (result.hasErrors()) {
        return create(manualsForm, model);
    }

    // FormをEntityにセットする処理は省略
    Manuals manual = setEntity(manualsForm);
    
    // リクエスト情報を作成
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);   
    HttpEntity<Manuals> entity = new HttpEntity<>(manual, headers);

    // ★登録
    ResponseEntity<Manuals> response = restTemplate.exchange(API_URL, HttpMethod.POST, entity, Manuals.class);

    return "redirect:/manual/list";
}
```
```ManualsForm.java
@Data
public class ManualsForm {

    // 社員ID
    private int userId;

    // 社員番号
    private String userNumber;

    // 表示順
    @NotNull(message = "入力してください")
    private Integer displayOrder;

    // タイトル
    @NotBlank(message = "入力してください")
    private String title;

    // 掲載開始日_年
    private String startYear;

    // 掲載開始日_月
    private String startMonth;

    // 掲載開始日_日
    private String startDay;

    // 掲載終了日_年
    private String endYear;

    // 掲載終了日_月
    private String endMonth;

    // 掲載終了日_日
    private String endDay;

    // 内容
    @NotBlank(message = "入力してください")
    private String content;

    // リンク
    private String link;
}
```

### 更新
putメソッドを使います。
同様に最後の引数はURLのパラメータです。
```ManualsController.java
// 初期表示処理は省略

// 更新処理
@Transactional
@PostMapping("/manual/update")
public String update(int manualId,
                     @ModelAttribute @Validated ManualsForm manualsForm,
                     BindingResult result,
                     Model model) {
    // 入力エラーチェック
    if (result.hasErrors()) {
        return edit(manualId, manualsForm, model);
    }

    // FormをEntityにセットする処理は省略
    Manuals manual = setEntity(manualsForm);
    // マニュアルID
    manual.setManualId(manualId);

    // ★更新
    restTemplate.put(API_URL_PARAM, manual, manualId);

    return "redirect:/manual/list";
}
```

今回PUTで実装しましたが、PATCHを使用する際は以下をご参照ください。
[【Spring Boot】REST APIでpatchForObject（PATCH）使用時の注意点【RestTemplate】](https://qiita.com/1noseA/items/f735aa5c6044df1636f2)

### 削除
deleteメソッドを使います。
同様に最後の引数はURLのパラメータです。
```ManualsController.java
@Transactional
@RequestMapping("/manual/delete")
public String delete(int manualId, Model model) {
// 権限チェック（省略）
if (XXX == YYY) {
    // ★削除
    restTemplate.delete(API_URL_PARAM, manualId);
    return "redirect:/manual/list";
} else {
    // エラーメッセージを表示
    model.addAttribute("message", "権限がありません。");
    return index(model);
}
```

## 参考
★[【Spring Boot】RestTemplateによるAPI呼び出し](https://b1san-blog.com/post/spring/spring-rest-template/)
- [Spring BootでRestTemplateを使ってAPIを呼び出す方法](https://reasonable-code.com/resttemplate/)
- [【Spring】RestTemplate オススメの使い方【GET/POST】](https://4engineer.net/spring/rest-template-usage/)
- [RestTemplate (Spring Framework API) - Javadoc](https://spring.pleiades.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html)