Thymeleaf의 개념과 문법 살펴보기

CJI0524·2025년 1월 23일

Spring Boot

목록 보기
16/21

1. 뷰 템플릿 (View Template)이란?

Thymeleaf에 대해 공부하기전 우선 뷰 템플릿(View Template)의 개념에 대해 알아야 한다. 뷰 템플릿(View Template)이란 웹 애플리케이션에서 사용자 인터페이스를 구성하는 도구라고 할 수 있다.

뷰 템플릿을 사용하면 서버에서 데이터를 동적으로 HTML 페이지에 통합하여 웹 브라우저에서 렌더링할 수 있다. 이것이 HTML, CSS의 주요한 차이점이다. HTML, CSS는 주로 정적인 웹페이지를 만드는데 사용되지만 뷰 템플릿은 기본적으로 HTML 파일인데, 특별한 문법(속성)을 추가해서 서버에서 데이터를 동적으로 채워넣을 수 있다.

2. Thymeleaf 소개와 문법

Thymeleaf는 현대적인 서버 사이드 자바 템플릿 (뷰 템플릿) 엔진의 일종으로, HTML, XML, JavaScript, CSS등을 생성하는 데 사용된다. 주로 웹 및 독립형 환경에서 사용되며, Spring Framework와의 통합을 위해 특별히 설계된 Spring Edition을 제공한다.

2.1. 표현식 (Expressions)

다음은 표현식에 해당하는 Thymeleaf 문법이다.

[1] 변수 표현식 ( ${...} )

모델 (Model)에서 데이터를 가져올 때 사용
컨트롤러에서 모델에 담긴 데이터를 템플릿에서 출력하거나 조건문 등에 사용

✍️ Java 작성

model.addAttribute("username", "JohnDoe");

✍️ html 작성

<p th:text="${username}">사용자 이름</p>

위 코드에서 ${username}은 컨트롤러에서 model.addAttribute("username", "JohnDoe")로 추가한 데이터를 가져와서 표시한다.


🖥️ 출력

<p>JohnDoe</p>

[2] 선택 변수 표현식 ( *{...} )

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>

[3] 메시지 표현식 ( #{...} )

국제화 (i18n) 메시지를 가져올 때 사용.

※ 국제화 (i18n) 메시지란?

국제화 (i18n) 메시지는 다국어 지원을 위한 기능으로, 예를 들어 웹사이트를 여러 언어로 제공하고 싶을 때, 각 언어별로 메시지 파일 (messages.properties, messages_en.properties, messages_ko.properties 등)을 만들어서 관리한다.

Thymeleaf에서는 메시지 표현식 (#{...})을 이용해서 해당 메시지 파일에 정의된 문자열을 불러와서, 다국어 지원을 쉽게 할 수 있다.

messages.properties 파일에서 정의된 메시지를 불러와서 다국어 지원이 가능하도록 해줌.

✍️ Java 작성

model.addAttribute("username", "Alice");

✍️ messages.properties 파일 내용 작성

welcome.message=환영합니다, {0}님!

✍️ html 작성


<p th:text="#{welcome.message(${username})}">기본 메시지</p>

🖥️ 출력

<p>환영합니다, Alice님!</p>

[4] 링크 URL 표현식 ( @{...} )

URL을 동적으로 생성할 때 사용
웹 애플리케이션에서 컨트롤러의 요청 매핑과 연결되는 링크를 만들 수 있음

✍️ html 작성

<a th:href="@{/users/{id}(id=${userId})}">사용자 프로필 보기</a>

userId가 5일 경우, 결과는 다음과 같다.

🖥️ 출력

<a href="/users/5">사용자 프로필 보기</a>

[5] 리터럴 값 ( 'one text' , + ,* , etc.)

고정된 문자열 값이나 연산할 때 사용
변수 없이 직접 값을 표현하거나 문자열 연결(+), 곱셈(*), 연산을 수행가능

✍️ 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>

2.2. 표준 속성 설정자

[1] th:text

HTML 태그의 내용을 동적으로 설정
모델에서 전달된 데이터나 표현식의 결과를 태그의 텍스트로 대체해줘.

✍️ html 작성

<p th:text="${username}">기본 사용자 이름</p>

만약 모델에 username"Alice"가 담겨 있다면, 결과는 다음과 같다.

🖥️ 출력

<p>Alice</p>

[2] th:each

반복문을 통해 컬렉션 데이터를 처리
리스트나 배열 등의 요소들을 순회하며, 각 요소마다 템플릿 조각을 반복 생성할 때 사용

✍️ 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>

[3] th:if, th:unless

조건에 따라 태그의 존재 여부를 제어

  • th:if : 조건이 참 (true)이면 해당 태그를 렌더링하고, 거짓이면 태그를 렌더링하지 않음.
  • th:unless : 조건이 거짓 (false)이면 해당 태그를 렌더링하고, 참이면 렌더링하지 않음

✍️ html 작성

<p th:if="${isLoggedIn}">로그인 상태입니다.</p>
<p th:unless="${isLoggedIn}">로그인 해주세요.</p>

만약 isLoggedIntrue"로그인 상태입니다."가 보이고, false"로그인 해주세요."가 보이는 식이다.


🖥️ 출력

<p>로그인 상태입니다.</p> // model.addAttribute("isLoggedIn", true);
<p>로그인 해주세요.</p> // model.addAttribute("isLoggedIn", false);

[4] th:include, th:replace

다른 템플릿 파일에 정의된 프래그먼트를 포함하거나 교체

  • 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>&copy; 2024 My Website, All rights reserved.</p>
</div>

th:include현재 태그 내부에 프래그먼트 내용을 포함시키므로, <footer> 태그 안에 프래그먼트의 내용이 삽입.


🖥️ 출력

<footer>
  <div>
    <p>&copy; 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>

2.3. URL 처리

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})로 해당 변수에 modeluserId 값을 매핑한다.

만약 userId가 5라면 최종 URL/users/5가 된다.


2.4. 폼 데이터 관련 문법

[1] th:action

폼이 제출될 URL을 지정하는 속성

가령 th:action="@{/register}"폼 데이터를 /register URL로 전송함.
ThymeleafURL 표현식 @{...} 덕분에, 애플리케이션의 컨텍스트 경로를 자동으로 붙여주기 때문배포 환경에 따라 URL 관리가 편함


[2] th:field

폼 입력 필드를 모델 객체의 속성과 자동으로 연결

th:object로 지정한 모델 (예를 들어, userForm)의 속성에 값을 연결.

예를 들어, <input type="text" th:field="*{username}" />userForm 객체의 username 속성과 자동으로 연결

이걸 사용하면 HTML 표준 속성인 name, id, value 같은 속성이 자동으로 생성돼서, 폼 데이터를 주고받을 때 편리.


[3] th:errors

지정된 폼 필드에 대해 발생한 에러 메시지를 화면에 출력

가령 <div th:errors="*{email}"></div>email 필드에 대한 에러 메시지를 BindingResult에서 자동으로 가져와서 보여줌.

만약 이메일 형식이 올바르지 않다면, 관련 에러 메시지가 여기에 출력


[4] #fields

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>

3. 예제 코드

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>
  1. 컨트롤러에서 List<User>를 만든 뒤, model.addAttribute("users", users);로 데이터를 넘김.
  2. Thymeleaf 템플릿(users.html)에선 th:eachusers 리스트를 반복해서 화면에 표 형태로 보여줌.
  3. th:if / th:unless를 사용해 admin 값에 따라 "Admin" 또는 "User" 문구를 다르게 표시함.

4. 해당 게시글 작성에 참고한 글 목록

멋쟁이사자처럼 강의자료

profile
개발돌이

0개의 댓글