앞에서 RestAPI를 구현할 때는 뷰 페이지 없이 데이터만 JSON으로 변환해서 응답으로 보냈다. 앞에서 구현한 RestAPI는 JSON 데이터를 응답으로 보내기 때문에 화면 구성이 필요 없었지만 일반적인 웹 애플리케이션은 웹 페이지를 HTML로 만들어 클라이언트에게 응답으로 보낸다. 웹 서버는 일반적으로 요청을 처리한 결과 데이터를 HTML 문서 형태로 만들어 응답하는데 이 때 필요한 데이터를 HTML에 포함시켜서 완성된 HTML 문서로 만들어 주는 도구를 템플릿 엔진이라고 한다.
스프링 부트 레퍼런스를 살펴보면 템플릿 엔진 사용에 대한 안내를 볼 수 있으며 아래 그림과 같이
여러 유형의 템플릿 엔진을 지원하지만 JSP는 임베디드 서블릿 컨테이너에서 사용할 때 몇 가지 제
한 사항이 있어서 권장하지 않는다. 우리는 아래의 템플릿 엔진중에서 많이 사용되는 Thymeleaf에
대해서 알아볼 것이다
Thymeleaf는 자바 웹 애플리케이션의 뷰(화면)를 만들기 위한 템플릿 엔진이다.
Thymeleaf는 HTML, XML, JavaScript, CSS 등의 마크업 언어를 지원하며, 순수한 HTML을 최대한 유지하려는 네츄럴 템플릿(Natural Templates) 기반의 템플릿 엔진이다.
Thymeleaf는 서버 측에서 HTML 문서로 렌더링 되어 전달되므로 클라이언트 측에서 추가적인 처리
없이 HTML 문서를 확인할 수 있다. 무엇보다 스프링과 자연스런 통합을 지원하기 때문에 Spring
MVC 애플리케이션에서 JSP를 대체하는 모든 기능을 제공하며 스프링이 지원하는 다양한 기능을 활
용할 수 있다.
Thymeleaf는 자바 기반 웹 애플리케이션을 개발 할 때 많이 사용하는 템플릿 엔진으로 변수, 링크,이미지와 조건문, 반복문 등을 다루는 다양한 기능을 지원한다. 또한 Thymeleaf Layout과 같은 추가 기능을 통해 레이아웃을 쉽게 관리할 수 있다.




package com.springbootstudy.app.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import com.springbootstudy.app.service.MemoEntityService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* Spring MVC Controller 클래스로, MemoEntityService를 주입받아 사용한다.
*/
@Controller
@RequiredArgsConstructor
@Slf4j
public class MemoViewController {
private final MemoEntityService entityService;
/**
* "/memoListJsp" 경로에 대한 GET 요청을 처리하여 memoList를 JSP로 반환한다.
* @param model 뷰에 전달할 데이터를 담는 Spring의 Model 객체
* @return "main" 문자열을 반환하여 main.jsp를 렌더링한다.
*/
@GetMapping("/memoListJsp")
public String memoListJsp(Model model) {
model.addAttribute("mList", entityService.memoList());
return "main";
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>메모 리스트</title>
<link href="<c:url value='/bootstrap/bootstrap.min.css' />" rel="stylesheet">
<!-- Note: Adjust the path '/bootstrap/bootstrap.min.css' based on your project structure -->
</head>
<body>
<div class="container">
<h1>메모 리스트</h1>
<table class="table">
<thead>
<tr>
<th>NO</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
<c:if test="${not empty mList}">
<c:forEach var="memo" items="${mList}">
<tr>
<td>${memo.no}</td>
<td>${memo.title}</td>
<td>${memo.writer}</td>
<td>${memo.regDate}</td>
</tr>
</c:forEach>
</c:if>
<c:if test="${empty mList}">
<tr>
<td colspan="4">작성된 메모가 없습니다.</td>
</tr>
</c:if>
</tbody>
</table>
</div>
</body>
</html>
MemoViewController 클래스: Spring MVC의 Controller로서 MemoEntityService를 주입받아 사용한다. memoListJsp 메서드는 /memoListJsp 경로로 GET 요청이 들어오면 main.jsp를 렌더링하기 위해 Model에 mList라는 이름으로 memo 리스트를 추가한다.
main.jsp: JSP 파일로, memo 리스트를 테이블 형식으로 표시한다. <c:if>와 <c:forEach> 태그를 사용하여 mList가 비어있지 않으면 각 memo 항목을 표시하고, 비어있으면 "작성된 메모가 없습니다." 메시지를 표시한다. 또한 Bootstrap을 사용하여 간단한 스타일링을 적용했다.
주의 사항: JSP 파일은 반드시 src/main/webapp/WEB-INF/views/ 경로에 위치해야 정상적으로 동작한다. 이 경로는 Spring Boot의 기본 설정에 따라 설정되어 있다.

다음은 Thymeleaf에서 많이 사용하는 표현식과 문법을 정리한 표이다. 아래 표의 표현식과 같이
Thymeleaf는 주로 HTML 태그에 th:* 속성을 지정하는 방식으로 동작한다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Thymeleaf 사용하기1</h1>
<p th:text="${text1}">text1의 내용이 태그를 포함해서 p태그의 내용으로 출력됨</p>
<p th:utext="${text1}">text1의 내용이 태그가 적용되서 p태그의 내용으로 출력됨</p>
<p class="test1" th:attrappend="class='temp1'">내용1</p>
<p class="test1" th:classappend="temp2">내용1</p>
<p th:text="${#temporals.format(today, 'yyyy-MM-dd')}"></p>
<p th:text="${#dates.format(memo.regDate, 'yyyy-MM-dd')}"></p>
<ul th:object="${memo}">
<li th:text="|no : *{no}|"></li>
<li th:text="|제목 : *{title}|"></li>
<li th:text="'작성자 : ' + *{writer}"></li>
<li th:text="|내용 : *{content}|"></li>
<li th:text="|작성일 : ${#dates.format(memo.regDate, 'yyyy-MM-dd')}|"></li>
</ul>
<a th:href="@{thymeleaf2/{no}(no=${memo.no})}">다음 보기</a>
<a th:href="@{thymeleaf3(no=${memo.no})}">마지막 보기</a>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link href="/bootstrap/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div th:utext="${title}"></div>
<table class="table">
<thead>
<tr>
<th>NO</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
</tr>
</thead>
<tbody>
<th:block th:if="${not #lists.isEmpty(mList)}">
<th:block th:each="memo, status: ${mList}">
<tr>
<td th:text="${memo.no}"></td>
<td>
<a th:href="@{/thymeleaf3(no=${memo.no})}" th:text="${memo.title}"></a>
</td>
<td th:text="${memo.writer}"></td>
<td th:text="${#dates.format(memo.regDate, 'yyyy-MM-dd')}"></td>
</tr>
</th:block>
</th:block>
<th:block th:if="${#lists.isEmpty(mList)}">
<tr>
<td colspan="4">작성된 메모가 없습니다.</td>
</tr>
</th:block>
</tbody>
</table>
<a th:href="@{/thymeleaf1}">이전 보기</a>
</div>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div class="container">
<div th:utext="${title}"></div>
<th:block th:if="${memo != null}">
<ul th:object="${memo}">
<li th:text="|no : *{no}|"></li>
<li th:text="|제목 : *{title}|"></li>
<li th:text="|작성자 : *{writer}|"></li>
<li th:text="|내용 : *{content}|"></li>
<li th:text="|작성일 : ${#dates.format(memo.regDate, 'yyyy-MM-dd')}|"></li>
</ul>
</th:block>
</div>
</body>
</html>