개요
여기에서는 Spring Boot + RestTemplate + Thymeleaf를 사용하여, 이전 글 [Spring Boot] REST API 개발 (MyBatis,HSQLDB) 에서 예제로 만든 CRUD REST API를 RestTemplate를 사용하여 호출하는 방법을 보여준다.
개발환경은 다음과 같다:
● Spring Tool Suite 3 (Version: 3.9.11)
● Spring Boot Version : 2.7.11
● Java : JDK 8
※ 관련글 목록: http://yellow.kr/lifeView.jsp?s=spring
예제 애플리케이션 설명
만들려는 예제 Books 관리 시스템은 REST API를 호출하여 다음과 같은 기능을 구현한다:
● 책 정보 리스트
● 책 정보 등록
● 책 정보 조회
● 책 정보 수정
● 책 정보 삭제
예제 Application의 Flow는 다음과 같다. 여기에서는 Client 부분에 해당한다(8080 port 사용).
‘Spring Boot REST 애플리케이션(8081 port 사용)’은 [Spring Boot] REST API 개발 (MyBatis,HSQLDB) 글을 참조
그리고 CORS(cross-origin 리소스 공유) 오류가 나타나는 것을 해결하기 위해 ‘Spring Boot REST 애플리케이션’의 Book Controller에 @CrossOrigin 어노테이션을 사용하였다.
RestTemplate
RestTemplate은 Spring Framework에서 제공하는 HTTP 클라이언트 라이브러리이다. RestTemplate을 사용하면 다른 서버에 HTTP 요청을 보내고, HTTP 응답을 받아들이고, 이를 처리할 수 있다.
RestTemplate은 간단한 API를 제공하며, 다양한 HTTP 메서드(GET, POST, PUT, DELETE 등)를 지원한다. 또한 요청 헤더 및 쿼리 매개 변수를 설정하고, 요청 본문과 응답 본문을 처리할 수 있다.
RestTemplate은 기본적으로 Java.net 패키지를 사용하여 HTTP 요청을 처리하지만, Spring에서는 Apache HttpClient 또는 OkHttp와 같은 대체 HTTP 클라이언트 라이브러리도 지원한다.
RestTemplate은 RESTful 웹 서비스에서 데이터를 송수신할 때 자주 사용되며, Spring Boot에서는 RestTemplate을 자동으로 구성할 수 있도록 다양한 Bean Factory 메서드를 제공한다. 따라서 RestTemplate을 사용하여 웹 애플리케이션을 개발하는 것이 간단하고 편리하다.
RestTemplate 대신 WebClient를 사용하여 REST API를 호출하는 것도 가능하다. WebClient는 비동기(non-blocking) HTTP 클라이언트 라이브러리이며, Reactor라는 리액티브 라이브러리를 기반으로 하여 작동한다.
※ [Spring Boot] REST API 호출 (WebClient) : http://yellow.kr/blog/?p=5841
Spring Boot project 생성
● eclips에서 Spring Boot project를 생성한다.
File > New > Project… > Spring Boot > Spring Starter Project
Name, Group, Package에 적당한 내용을 입력하고 [Next] 클릭
● 필요한 Dependency들을 선택한다.
Spring Web, Thymeleaf를 선택한다.
[Finish] 클릭
● Project 생성 됨
yellow-resttemplate 라는 이름으로 Project가 생성되었고, YellowResttemplateApplication.java 가 생성되었다.
package com.yellow.resttemplate;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
public class YellowResttemplateApplication {
public static void main(String[] args) {
SpringApplication.run(YellowResttemplateApplication.class, args);
}
@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.build();
}
}
여기 예제에서 RestTemplate은 생성자 주입을 사용하여 주입된다. 따라서 RestTemplate 인스턴스를 생성하려면 위와 같이 @Bean 어노테이션을 사용하여 RestTemplate 빈을 정의하면 된다. RestTemplate을 빈으로 등록하려면 @Configuration 어노테이션이 붙은 클래스에서 등록하는 것이 좋다. 만약 AppConfig.java가 없다면, @SpringBootApplication 어노테이션이 붙은 클래스(YellowResttemplateApplication.java)에서도 RestTemplate을 Bean으로 등록할 수 있다.이렇게 정의된 RestTemplate 빈은 BookController.java에서 생성자 주입을 통해 주입된다.
예제 애플리케이션이 완성되면 Project Structure은 다음과 같다.
Maven Dependencies
pom.xml을 보면 다음과 같은 dependency를 확인할 수 있다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Model
● Book.java
package com.yellow.resttemplate;
public class Book {
private String bookId;
private String title;
private String author;
private String publisher;
private String releaseDate;
private String isbn;
public Book() {}
public Book(String bookId, String title, String author, String publisher, String releaseDate, String isbn) {
this.setBookId(bookId);
this.setTitle(title);
this.setAuthor(author);
this.setPublisher(publisher);
this.setReleaseDate(releaseDate);
this.setIsbn(isbn);
}
public String getBookId() {
return bookId;
}
public void setBookId(String bookId) {
this.bookId = bookId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getPublisher() {
return publisher;
}
public void setPublisher(String publisher) {
this.publisher = publisher;
}
public String getReleaseDate() {
return releaseDate;
}
public void setReleaseDate(String releaseDate) {
this.releaseDate = releaseDate;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
@Override
public String toString() {
return "bookId:" + bookId + ",title:" + title + ",author:" + author
+ ",publisher:" + publisher + ",releaseDate:" + releaseDate + ",isbn:" + isbn;
}
}
Contoller, View(Thymeleaf)
● BookController.java
package com.yellow.resttemplate;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.client.RestTemplate;
@Controller
public class BookController {
@Autowired
private RestTemplate restTemplate;
private final String BASE_URL = "http://localhost:8081/api/books";
@GetMapping("/")
public String index() {
return "index";
}
@GetMapping("/listBook")
public String viewBookList(Model model) {
ResponseEntity<List<Book>> responseEntity = restTemplate.exchange(
BASE_URL,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Book>>() {}
);
List<Book> books = responseEntity.getBody();
model.addAttribute("allBooks", books);
return "listBook";
}
@GetMapping("/viewBook/{id}")
public String viewBook(@PathVariable("id") String id, Model model) {
ResponseEntity<Book> responseEntity = restTemplate.getForEntity(
BASE_URL + "/" + id,
Book.class
);
Book book = responseEntity.getBody();
model.addAttribute("book", book);
return "viewBook";
}
@GetMapping("/addViewBook")
public String addViewBook() {
return "addViewBook";
}
@PostMapping("/addBook")
public String addBook(@ModelAttribute Book book) {
restTemplate.postForEntity(
BASE_URL,
book,
Book.class
);
return "redirect:/listBook";
}
@GetMapping("/updateViewBook/{id}")
public String updateViewBook(@PathVariable("id") String id, Model model) {
ResponseEntity<Book> responseEntity = restTemplate.getForEntity(
BASE_URL + "/" + id,
Book.class
);
Book book = responseEntity.getBody();
model.addAttribute("book", book);
return "updateViewBook";
}
@PostMapping("/updateBook/{id}")
public String updateBook(@PathVariable("id") String id, @ModelAttribute Book book) {
restTemplate.put(
BASE_URL + "/" + id,
book
);
return "redirect:/listBook";
}
@GetMapping("/deleteBook/{id}")
public String deleteBook(@PathVariable("id") String id) {
restTemplate.delete(
BASE_URL + "/" + id
);
return "redirect:/listBook";
}
}
● index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Main Page</title>
</head>
<body>
<h1>Main Page</h1>
<p><a href="/listBook">List Book</a></p>
<p><a href="/addViewBook">Register Book</a></p>
</body>
</html>
● listBook.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Book List</title>
</head>
<body>
<h1>Book List</h1>
<br/>
<div>
<table border="1">
<tr>
<th>ID</th>
<th>제목</th>
<th>저자</th>
<th>Action</th>
</tr>
<tr th:each ="book : ${allBooks}">
<td><a th:href="@{/viewBook/{id}(id=${book.bookId})}" th:text="${book.bookId}"></a></td>
<td th:text="${book.title}"></td>
<td th:text="${book.author}"></td>
<td>
<a th:href="@{/updateViewBook/{id}(id=${book.bookId})}" >수정</a>,
<a th:href="@{/deleteBook/{id}(id=${book.bookId})}" >삭제</a>
</td>
</tr>
</table>
</div>
<br/><br/>
<a href="/">Main Page</a>
</body>
</html>
● viewBook.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>View Book</title>
</head>
<body>
<h1>Book Detail View</h1>
<div>
<span>Book ID : </span>
<span th:text="${book.bookId}"></span>
</div>
<div>
<span>제 목 : </span>
<span th:text="${book.title}"></span>
</div>
<div>
<span>저 자 : </span>
<span th:text="${book.author}"></span>
</div>
<div>
<span>출판사 : </span>
<span th:text="${book.publisher}"></span>
</div>
<div>
<span>출판일 : </span>
<span th:text="${book.releaseDate}"></span>
</div>
<div>
<span>ISBN : </span>
<span th:text="${book.isbn}"></span>
</div>
<br>
<br>
<div>
<a th:href="@{/updateViewBook/{id}(id=${book.bookId})}" >수정</a> /
<a th:href="@{/deleteBook/{id}(id=${book.bookId})}" >삭제</a>
</div>
<p><br></p>
<p><a href="/listBook">List Book</a></p>
</body>
</html>
● addViewBook.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Add a New Book</title>
</head>
<body>
<h1>Add a New Book</h1>
<form method="POST" action="/addBook">
<div>
<label>Book ID</label>
<input type="text" id="bookId" name="bookId">
</div>
<div>
<label>제 목 </label>
<input type="text" id="title" name="title">
</div>
<div>
<label>저 자 </label>
<input type="text" id="author" name="author">
</div>
<div>
<label>출판사</label>
<input type="text" id="publisher" name="publisher">
</div>
<div>
<label>출판일</label>
<input type="text" id="releaseDate" name="releaseDate">
</div>
<div>
<label>ISBN </label>
<input type="text" id="isbn" name="isbn">
</div>
<div>
<p><br></p>
<input type="submit" id="addNew" value="Submit">
</div>
</form>
<p><br></p>
<p><a href="/">Main</a></p>
</body>
</html>
● updateViewBook.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Update a Book</title>
</head>
<body>
<h1>Update a Book</h1>
<form th:action="@{/updateBook/{id}(id=${book.bookId})}" method="POST" >
<div>
<label>Book ID :</label>
<span th:text="${book.bookId}"></span>
<input type="hidden" th:field="*{book.bookId}" >
</div>
<div>
<label>제 목 </label>
<input type="text" th:field="*{book.title}" >
</div>
<div>
<label>저 자 </label>
<input type="text" th:field="*{book.author}" >
</div>
<div>
<label>출판사</label>
<input type="text" th:field="*{book.publisher}" >
</div>
<div>
<label>출판일</label>
<input type="text" th:field="*{book.releaseDate}" >
</div>
<div>
<label>ISBN </label>
<input type="text" th:field="*{book.isbn}" >
</div>
<div>
<p><br></p>
<input type="submit" id="addNew" value="Submit">
</div>
</form>
<p><br></p>
<p><a href="/">Main</a></p>
</body>
</html>
Application 실행
Project를 선택한 후, Run AS > Spring Boot App 를 하면 내장 Tomcat 서버에 배포된다. 브라우저에서 다음의 URL을 실행한다.
● http://localhost:8080/
● http://localhost:8080/listBook
● http://localhost:8080/addViewBook
● http://localhost:8080/listBook
● http://localhost:8080/viewBook/1004
● http://localhost:8080/updateViewBook/1004
● http://localhost:8080/listBook
등등…