
Thymeleaf에 대해 공부하기전 우선 뷰 템플릿(View Template)의 개념에 대해 알아야 한다. 뷰 템플릿(View Template)이란 웹 애플리케이션에서 사용자 인터페이스를 구성하는 도구라고 할 수 있다.
뷰 템플릿을 사용하면 서버에서 데이터를 동적으로 HTML 페이지에 통합하여 웹 브라우저에서 렌더링할 수 있다. 이것이 HTML, CSS의 주요한 차이점이다. HTML, CSS는 주로 정적인 웹페이지를 만드는데 사용되지만 뷰 템플릿은 기본적으로 HTML 파일인데, 특별한 문법(속성)을 추가해서 서버에서 데이터를 동적으로 채워넣을 수 있다.
Thymeleaf는 현대적인 서버 사이드 자바 템플릿 (뷰 템플릿) 엔진의 일종으로, HTML, XML, JavaScript, CSS등을 생성하는 데 사용된다. 주로 웹 및 독립형 환경에서 사용되며, Spring Framework와의 통합을 위해 특별히 설계된 Spring Edition을 제공한다.
다음은 표현식에 해당하는 Thymeleaf 문법이다.
모델 (Model)에서 데이터를 가져올 때 사용
컨트롤러에서 모델에 담긴 데이터를 템플릿에서 출력하거나 조건문 등에 사용
✍️ Java 작성
model.addAttribute("username", "JohnDoe");
✍️ html 작성
<p th:text="${username}">사용자 이름</p>
위 코드에서 ${username}은 컨트롤러에서 model.addAttribute("username", "JohnDoe")로 추가한 데이터를 가져와서 표시한다.
🖥️ 출력
<p>JohnDoe</p>
th:object로 설정한 객체의 속성을 간결하게 참조할 때 사용
보통 th:object와 함께 사용되며, 해당 객체를 기준으로 값을 가져옴.
✍️ Java 작성
model.addAttribute("user", new User("Alice", "alice@example.com"));
✍️ html 작성
<form th:object="${user}">
<p th:text="*{name}">사용자 이름</p>
<p th:text="*{email}">이메일</p>
</form>
th:object="${user}"가 설정된 상태에서 *{name}은 user.name을 의미함.
🖥️ 출력
<p>Alice</p>
<p>alice@example.com</p>
국제화 (i18n) 메시지를 가져올 때 사용.
※ 국제화 (i18n) 메시지란?
국제화 (i18n) 메시지는 다국어 지원을 위한 기능으로, 예를 들어 웹사이트를 여러 언어로 제공하고 싶을 때, 각 언어별로 메시지 파일 (messages.properties, messages_en.properties, messages_ko.properties 등)을 만들어서 관리한다.
Thymeleaf에서는 메시지 표현식 (#{...})을 이용해서 해당 메시지 파일에 정의된 문자열을 불러와서, 다국어 지원을 쉽게 할 수 있다.
messages.properties 파일에서 정의된 메시지를 불러와서 다국어 지원이 가능하도록 해줌.
✍️ Java 작성
model.addAttribute("username", "Alice");
✍️ messages.properties 파일 내용 작성
✍️ html 작성
<p th:text="#{welcome.message(${username})}">기본 메시지</p>
🖥️ 출력
<p>환영합니다, Alice님!</p>
URL을 동적으로 생성할 때 사용
웹 애플리케이션에서 컨트롤러의 요청 매핑과 연결되는 링크를 만들 수 있음
✍️ html 작성
<a th:href="@{/users/{id}(id=${userId})}">사용자 프로필 보기</a>
userId가 5일 경우, 결과는 다음과 같다.
🖥️ 출력
<a href="/users/5">사용자 프로필 보기</a>
고정된 문자열 값이나 연산할 때 사용
변수 없이 직접 값을 표현하거나 문자열 연결(+), 곱셈(*), 연산을 수행가능
✍️ html 작성
<p th:text="'Hello, ' + ${username}">기본 텍스트</p>
<p th:text="${price} * 2">가격 연산</p>
username이 "Alice"이고, price가 10이면 최종 HTML 결과는 다음과 같다.
🖥️ 출력
<p>Hello, Alice</p>
<p>20</p>
HTML 태그의 내용을 동적으로 설정
모델에서 전달된 데이터나 표현식의 결과를 태그의 텍스트로 대체해줘.
✍️ html 작성
<p th:text="${username}">기본 사용자 이름</p>
만약 모델에 username에 "Alice"가 담겨 있다면, 결과는 다음과 같다.
🖥️ 출력
<p>Alice</p>
반복문을 통해 컬렉션 데이터를 처리
리스트나 배열 등의 요소들을 순회하며, 각 요소마다 템플릿 조각을 반복 생성할 때 사용
✍️ Java 작성
model.addAttribute("items", Arrays.asList("Apple", "Banana", "Orange"));
✍️ html 작성
<ul>
<li th:each="item : ${items}" th:text="${item}">항목</li>
</ul>
🖥️ 출력
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Orange</li>
</ul>
th:each의 기본 개념과 사용법에 대해 이해했다면 상태 변수 (status variable)에 대해서도 추가로 알아보자.
th:each 속성을 사용하면, 컬렉션을 반복하면서 각 요소뿐만 아니라 반복 상태 정보를 함께 얻을 수 있다.
가령 th:each="item, stat : ${items}"와 같이 두 개의 변수를 지정하면,
item : 현재 반복되는 컬렉션의 요소stat : 반복 상태 정보를 담은 변수로, 여러 유용한 속성을 제공과 같이 반복 상태 정보를 stat이라는 변수를 통해 알 수 있다. 여기서 stat은 단순한 변수명으로서, 다른 변수명으로 대체가 가능하다.
상태 변수 (status variable)를 통해 알 수 있는 정보는 다음과 같다.
stat.index : 현재 요소의 인덱스, 0부터 시작
stat.count : 현재 반복의 횟수, 1부터 시작
stat.first : 현재 요소가 첫 번째 요소이면 true, 그렇지 않으면 false.
stat.last : 현재 요소가 마지막 요소이면 true, 그렇지 않으면 false.
stat.even / stat.odd : 짝수, 홀수 여부를 나타내는 boolean 값.
상태 변수 (status variable)를 사용하는 예제는 다음과 같다.
✍️ html 작성
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>List Example</title>
</head>
<body>
<h1>List Example</h1>
<ul>
<!-- th:each의 상태 변수(stat)를 사용하여 반복 상태 제어 -->
<li
th:each="item, stat : ${items}"
th:if="${stat.index >= 2 and stat.index < 7}"
th:text="${item}"
>
<!-- 아이템 출력 -->
</li>
</ul>
</body>
</html>
예제는 리스트 값이 "item1"부터 "item10"까지 있을 때, 인덱스가 2 이상 7 미만인 요소들만 출력하는 예제로 최종적으로 아래와 같이 렌더링이 된다.
🖥️ 출력
<!DOCTYPE html>
<html>
<head>
<title>List Example</title>
</head>
<body>
<h1>List Example</h1>
<ul>
<li>item3</li>
<li>item4</li>
<li>item5</li>
<li>item6</li>
<li>item7</li>
</ul>
</body>
</html>
조건에 따라 태그의 존재 여부를 제어
th:if : 조건이 참 (true)이면 해당 태그를 렌더링하고, 거짓이면 태그를 렌더링하지 않음.th:unless : 조건이 거짓 (false)이면 해당 태그를 렌더링하고, 참이면 렌더링하지 않음✍️ html 작성
<p th:if="${isLoggedIn}">로그인 상태입니다.</p>
<p th:unless="${isLoggedIn}">로그인 해주세요.</p>
만약 isLoggedIn이 true면 "로그인 상태입니다."가 보이고, false면 "로그인 해주세요."가 보이는 식이다.
🖥️ 출력
<p>로그인 상태입니다.</p> // model.addAttribute("isLoggedIn", true);
<p>로그인 해주세요.</p> // model.addAttribute("isLoggedIn", false);
다른 템플릿 파일에 정의된 프래그먼트를 포함하거나 교체
th:include : 지정한 프래그먼트의 내용을 현재 태그 내부에 포함th:replace : 현재 태그 자체를 지정한 프래그먼트로 교체(1) th:include
✍️ html 작성
<footer th:include="common/footer :: footerFragment">푸터 기본 내용</footer>
✍️ common/footer.html 파일 내용
<!-- common/footer.html -->
<div th:fragment="footerFragment">
<p>© 2024 My Website, All rights reserved.</p>
</div>
th:include는 현재 태그 내부에 프래그먼트 내용을 포함시키므로, <footer> 태그 안에 프래그먼트의 내용이 삽입.
🖥️ 출력
<footer>
<div>
<p>© 2024 My Website, All rights reserved.</p>
</div>
</footer>
(2) th:replace
✍️ html 작성
<div th:replace="common/header :: headerFragment">헤더 기본 내용</div>
✍️ common/header.html 파일 내용
<!-- common/header.html -->
<div th:fragment="headerFragment">
<h1>My Website Header</h1>
</div>
th:replace는 현재 태그 자체를 프래그먼트 내용으로 완전히 대체.
🖥️ 출력
<div>
<h1>My Website Header</h1>
</div>
URL 처리에 사용되는 th:href는 동적으로 URL을 생성해주는 속성.
주로 앵커 태그 (<a>)나 링크를 사용하는 곳에서 사용되며, URL에 변수나 파라미터를 쉽게 삽입할 수 있게 해줌.
th:href는 @{...} 표현식을 사용해서 URL 템플릿을 만들고, 변수 값을 삽입할 수 있음. 예를 들어, 컨트롤러에서 userId를 모델에 담았다면, 이를 URL 경로에 동적으로 넣어줄 수 있음.
✍️ html 작성
<a th:href="@{/users/{id}(id=${userId})}">사용자 프로필 보기</a>
@{/users/{id}(id=${userId})}는 URL 템플릿 표현식이다.
/users/{id}에서 {id}는 변수 자리이고, (id=${userId})로 해당 변수에 model의 userId 값을 매핑한다.
만약 userId가 5라면 최종 URL은 /users/5가 된다.
폼이 제출될 URL을 지정하는 속성
가령 th:action="@{/register}"는 폼 데이터를 /register URL로 전송함.
Thymeleaf의 URL 표현식 @{...} 덕분에, 애플리케이션의 컨텍스트 경로를 자동으로 붙여주기 때문에 배포 환경에 따라 URL 관리가 편함
폼 입력 필드를 모델 객체의 속성과 자동으로 연결
th:object로 지정한 모델 (예를 들어, userForm)의 속성에 값을 연결.
예를 들어, <input type="text" th:field="*{username}" />는 userForm 객체의 username 속성과 자동으로 연결
이걸 사용하면 HTML 표준 속성인 name, id, value 같은 속성이 자동으로 생성돼서, 폼 데이터를 주고받을 때 편리.
지정된 폼 필드에 대해 발생한 에러 메시지를 화면에 출력
가령 <div th:errors="*{email}"></div>는 email 필드에 대한 에러 메시지를 BindingResult에서 자동으로 가져와서 보여줌.
만약 이메일 형식이 올바르지 않다면, 관련 에러 메시지가 여기에 출력
thymeleaf가 폼 유효성 검사 (예, 스프링의 BindingResult)에 의해 발생한 에러 정보를 다루기 위해 제공하는 유틸리티 객체
주요 기능
#fields.hasErrors('email') : 특정 필드 (여기선 email)에 유효성 검사 에러가 있는지 확인. 에러가 있으면 true, 없으면 false를 반환#fields.errors('email') : email 필드에 해당하는 에러 메시지들을 리스트 형태로 가져올 수 있음.다음은 지금까지 나온 폼 관련 문법들을 활용한 예제이다.
✍️ HTML 작성
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login Form Example</title>
</head>
<body>
<!-- 컨트롤러에서 loginForm 객체를 모델에 담아 넘긴 상태라고 가정 -->
<form th:action="@{/login}" th:object="${loginForm}" method="post">
<!-- 이메일 입력 필드 -->
<div>
<label for="email">Email:</label>
<!-- th:field는 loginForm.email과 자동으로 바인딩됨 -->
<input type="email" th:field="*{email}" id="email" />
<!-- #fields를 이용해 email 필드에 에러가 있으면 에러 메시지를 출력 -->
<div th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></div>
</div>
<!-- 비밀번호 입력 필드 -->
<div>
<label for="password">Password:</label>
<!-- th:field는 loginForm.password와 자동으로 바인딩됨 -->
<input type="password" th:field="*{password}" id="password" />
<!-- password 필드의 에러 메시지 출력 -->
<div th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></div>
</div>
<button type="submit">Login</button>
</form>
</body>
</html>
Java 코드
✍️ 작성
package com.example.springmvc.controller;
import com.example.springmvc.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.Arrays;
import java.util.List;
@Controller
public class UserController {
@GetMapping("/users")
public String users(Model model) {
// User 객체를 담은 리스트 생성
List<User> users = Arrays.asList(
new User("kang", true),
new User("kim", false),
new User("hong", false)
);
// "users"라는 이름으로 리스트를 모델에 추가
// -> Thymeleaf 템플릿에서 ${users}로 접근 가능
model.addAttribute("users", users);
// "users"라는 뷰(템플릿)를 반환
// -> resources/templates/users.html를 찾아가서 렌더링할 것
return "users";
}
}
HTML 코드
✍️ 작성
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>user List</title>
</head>
<body>
<!-- 제목 -->
<h1>user List</h1>
<table>
<thead>
<tr>
<!-- 표 헤더: 사용자 이름, 관리자 여부 -->
<th>사용자 이름</th>
<th>관리자 여부</th>
</tr>
</thead>
<tbody>
<!--
th:each="user : ${users}"
-> model에 담긴 "users" 리스트를 순회하면서
각각의 원소를 user라는 변수로 사용
-->
<tr th:each="user : ${users}">
<!-- user.name 값을 출력 -->
<td th:text="${user.name}"></td>
<!-- user.admin == true일 때만 이 <td>가 렌더링되고 "Admin" 표시 -->
<td th:if="${user.admin}"
th:text="'Admin'">
</td>
<!-- user.admin == false일 때만 이 <td>가 렌더링되고 "User" 표시 -->
<td th:unless="${user.admin}"
th:text="'User'">
</td>
</tr>
</tbody>
</table>
</body>
</html>
List<User>를 만든 뒤, model.addAttribute("users", users);로 데이터를 넘김.Thymeleaf 템플릿(users.html)에선 th:each로 users 리스트를 반복해서 화면에 표 형태로 보여줌.th:if / th:unless를 사용해 admin 값에 따라 "Admin" 또는 "User" 문구를 다르게 표시함.멋쟁이사자처럼 강의자료