반응형
1장. Thymeleaf 소개
1.1 Thymeleaf란 무엇인가?
Thymeleaf는 웹 및 독립형 환경에서 사용할 수 있는 현대적인 서버사이드 Java 템플릿 엔진입니다. 2011년에 Daniel Fernández에 의해 개발되었으며, HTML, XML, JavaScript, CSS, 그리고 일반 텍스트를 처리할 수 있습니다.
핵심 개념
Thymeleaf의 핵심은 "Natural Templates" 개념입니다. 이는 템플릿 파일이 웹 브라우저에서 직접 열어도 올바르게 표시되는 유효한 HTML 문서라는 의미입니다. 이러한 특성은 디자이너와 개발자 간의 협업을 크게 향상시킵니다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Welcome Page</title>
</head>
<body>
<h1 th:text="${welcomeMessage}">Welcome to our website!</h1>
<p th:text="${description}">This is a sample description that shows in browser.</p>
<div th:if="${user}">
<p>Hello, <span th:text="${user.name}">Guest User</span>!</p>
<p>Your role: <span th:text="${user.role}">User</span></p>
</div>
<div th:unless="${user}">
<p><a href="/login">Please log in</a></p>
</div>
</body>
</html>
예제 설명:
xmlns:th="http://www.thymeleaf.org"
: Thymeleaf 네임스페이스 선언th:text="${welcomeMessage}"
: 서버에서 전달된welcomeMessage
변수의 값으로 텍스트 교체- 브라우저에서 직접 열면 "Welcome to our website!"가 표시되지만, 서버에서 렌더링되면 실제 변수 값으로 교체됩니다
th:if
와th:unless
를 통한 조건부 렌더링
1.2 주요 특징과 장점
Natural Templates (자연스러운 템플릿)
가장 큰 특징으로, HTML 파일 자체가 완전히 유효한 HTML 문서입니다.
<!-- 일반적인 템플릿 엔진의 경우 -->
<p>Hello <%= user.getName() %>!</p> <!-- 브라우저에서 깨짐 -->
<!-- Thymeleaf의 경우 -->
<p th:text="|Hello ${user.name}!|">Hello Guest!</p> <!-- 브라우저에서 정상 표시 -->
설명:
- 첫 번째 예제는 JSP 스타일로, 브라우저에서 직접 열면 스크립틀릿이 그대로 표시됩니다
- 두 번째 예제는 Thymeleaf 방식으로, 브라우저에서는 "Hello Guest!"가 표시되고, 서버에서 렌더링되면 실제 사용자 이름이 표시됩니다
강력한 표현식 언어
Thymeleaf는 Spring Expression Language(SpEL)를 기반으로 한 풍부한 표현식을 제공합니다.
<!-- 기본 변수 표현식 -->
<p th:text="${user.name}">사용자명</p>
<!-- 선택 변수 표현식 (객체 선택) -->
<div th:object="${user}">
<p>Name: <span th:text="*{name}">John</span></p>
<p>Email: <span th:text="*{email}">john@example.com</span></p>
<p>Age: <span th:text="*{age}">25</span></p>
</div>
<!-- 링크 URL 표현식 -->
<a th:href="@{/users/{id}(id=${user.id})}">View Profile</a>
<!-- 메시지 표현식 (국제화) -->
<p th:text="#{welcome.message}">Welcome message</p>
<!-- 복합 표현식 -->
<p th:text="|Hello ${user.name}, you have ${user.messageCount} messages|">
Hello John, you have 5 messages
</p>
각 표현식 설명:
${...}
: 컨텍스트 변수에 접근*{...}
: 선택된 객체의 속성에 접근 (th:object
와 함께 사용)@{...}
: URL 생성 (컨텍스트 경로 자동 추가, 파라미터 처리)#{...}
: 국제화 메시지|...|
: 문자열 리터럴 템플릿 (문자열 연결 간소화)
조건부 처리와 반복
<!-- 조건부 렌더링 -->
<div th:if="${user.isActive()}">
<p th:text="|Welcome back, ${user.name}!|">Welcome back, John!</p>
</div>
<div th:unless="${user.isActive()}">
<p class="warning">Your account is inactive.</p>
</div>
<!-- Switch 문 -->
<div th:switch="${user.role}">
<p th:case="'admin'" class="admin">Administrator Access</p>
<p th:case="'moderator'" class="mod">Moderator Access</p>
<p th:case="*" class="user">Regular User Access</p>
</div>
<!-- 반복 처리 -->
<table>
<tr th:each="product : ${products}">
<td th:text="${product.name}">Product Name</td>
<td th:text="${product.price}">$99.99</td>
<td>
<span th:if="${product.inStock}" class="available">Available</span>
<span th:unless="${product.inStock}" class="soldout">Sold Out</span>
</td>
</tr>
</table>
<!-- 반복 상태 변수 활용 -->
<ul>
<li th:each="item, stat : ${items}"
th:class="${stat.odd} ? 'odd-row' : 'even-row'"
th:text="|${stat.index + 1}. ${item.title}|">
1. Sample Item
</li>
</ul>
설명:
th:if
/th:unless
: 조건에 따른 요소 표시/숨김th:switch
/th:case
: 다중 조건 분기 (Java의 switch문과 유사)th:each
: 컬렉션 반복, 상태 변수(stat
)로 인덱스, 카운트, 홀짝 여부 등 확인 가능
1.3 다른 템플릿 엔진과의 비교
Thymeleaf vs JSP
특징 | Thymeleaf | JSP |
---|---|---|
Natural Templates | ✅ 완전한 HTML | ❌ 스크립틀릿으로 인한 HTML 파괴 |
브라우저 미리보기 | ✅ 가능 | ❌ 불가능 |
디자이너 친화성 | ✅ 매우 좋음 | ❌ 어려움 |
성능 | ⚠️ 상대적으로 느림 | ✅ 빠름 |
학습 곡선 | ⚠️ 중간 | ✅ Java 개발자에게 친숙 |
<!-- JSP 예제 -->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<% if (user != null) { %>
<p>Hello <%= user.getName() %>!</p>
<% } else { %>
<p>Please log in</p>
<% } %>
</body>
</html>
<!-- 동일한 기능의 Thymeleaf 예제 -->
<html xmlns:th="http://www.thymeleaf.org">
<body>
<p th:if="${user}" th:text="|Hello ${user.name}!|">Hello Guest!</p>
<p th:unless="${user}">Please log in</p>
</body>
</html>
Thymeleaf vs Mustache
<!-- Mustache 예제 -->
<html>
<body>
{{#user}}
<p>Hello {{name}}!</p>
{{/user}}
{{^user}}
<p>Please log in</p>
{{/user}}
</body>
</html>
<!-- Thymeleaf 예제 (더 풍부한 표현식) -->
<html xmlns:th="http://www.thymeleaf.org">
<body>
<p th:if="${user}"
th:text="|Hello ${user.name?.toUpperCase() ?: 'Guest'}!|">
Hello Guest!
</p>
<p th:unless="${user}">Please log in</p>
</body>
</html>
설명:
- Mustache는 로직리스 템플릿으로 단순하지만 표현력이 제한적
- Thymeleaf는 메소드 호출, Elvis 연산자(
?:
) 등 풍부한 표현식 지원
1.4 Natural Templates의 개념
Natural Templates는 Thymeleaf의 핵심 철학입니다. 이는 템플릿 파일이 템플릿 엔진 없이도 웹 브라우저에서 의미 있게 표시될 수 있다는 개념입니다.
전통적인 방식의 문제점
<!-- 전통적인 템플릿 (JSP, PHP 등) -->
<html>
<body>
<h1><?= $title ?></h1>
<% for(User user : users) { %>
<p><%= user.getName() %> - <%= user.getEmail() %></p>
<% } %>
</body>
</html>
이 파일을 브라우저에서 직접 열면 스크립트 코드가 그대로 표시되어 디자인 확인이 불가능합니다.
Thymeleaf의 Natural Templates 접근법
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="${pageTitle}">Default Page Title</title>
</head>
<body>
<h1 th:text="${title}">Sample Page Title</h1>
<!-- 사용자 목록 -->
<div class="user-list">
<div class="user-card" th:each="user : ${users}" th:remove="tag">
<div class="user-info">
<h3 th:text="${user.name}">John Doe</h3>
<p th:text="${user.email}">john.doe@example.com</p>
<span class="role" th:text="${user.role}">admin</span>
</div>
</div>
<!-- 샘플 데이터 (실제 렌더링 시 제거됨) -->
<div class="user-card">
<div class="user-info">
<h3>Jane Smith</h3>
<p>jane.smith@example.com</p>
<span class="role">user</span>
</div>
</div>
<div class="user-card">
<div class="user-info">
<h3>Bob Johnson</h3>
<p>bob.johnson@example.com</p>
<span class="role">moderator</span>
</div>
</div>
</div>
</body>
</html>
Natural Templates의 장점:
- 디자이너 친화적: HTML/CSS 디자이너가 실제 데이터 없이도 디자인을 확인할 수 있음
- 프로토타이핑: 백엔드 개발 전에 프론트엔드 작업이 가능
- 협업 향상: 디자이너와 개발자가 같은 파일로 작업 가능
- 유지보수성: HTML 구조가 명확하게 보임
1.5 사용 사례와 적용 분야
웹 애플리케이션
<!-- 전자상거래 상품 목록 -->
<div class="product-grid">
<div class="product-card" th:each="product : ${products}">
<img th:src="@{/images/products/{id}.jpg(id=${product.id})}"
th:alt="${product.name}"
src="/images/placeholder.jpg" alt="Product Image">
<h3 th:text="${product.name}">Sample Product</h3>
<div class="price">
<span th:if="${product.discount > 0}" class="original-price"
th:text="${#numbers.formatCurrency(product.originalPrice)}">$99.99</span>
<span class="current-price"
th:text="${#numbers.formatCurrency(product.currentPrice)}">$79.99</span>
<span th:if="${product.discount > 0}" class="discount"
th:text="|${product.discount}% OFF|">20% OFF</span>
</div>
<button th:onclick="|addToCart(${product.id})|"
th:disabled="${!product.inStock}">
<span th:text="${product.inStock} ? 'Add to Cart' : 'Out of Stock'">Add to Cart</span>
</button>
</div>
</div>
이메일 템플릿
<!-- 이메일 템플릿 -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="#{email.welcome.title}">Welcome Email</title>
</head>
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<div class="header" style="background-color: #f8f9fa; padding: 20px;">
<h1 th:text="#{email.welcome.header(${user.name})}">Welcome John!</h1>
</div>
<div class="content" style="padding: 20px;">
<p th:text="#{email.welcome.message}">Thank you for joining our service.</p>
<div class="account-info">
<h3 th:text="#{email.account.details}">Account Details</h3>
<ul>
<li><strong th:text="#{email.username}">Username</strong>:
<span th:text="${user.username}">john_doe</span></li>
<li><strong th:text="#{email.email}">Email</strong>:
<span th:text="${user.email}">john@example.com</span></li>
<li><strong th:text="#{email.registered}">Registered</strong>:
<span th:text="${#temporals.format(user.createdAt, 'MMM dd, yyyy')}">Jan 15, 2024</span></li>
</ul>
</div>
<div class="action" style="text-align: center; margin: 30px 0;">
<a th:href="@{https://oursite.com/activate/{token}(token=${activationToken})}"
style="background-color: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px;"
th:text="#{email.activate.button}">Activate Account</a>
</div>
</div>
<div class="footer" style="background-color: #f8f9fa; padding: 20px; text-align: center; font-size: 12px;">
<p th:text="#{email.footer.thanks}">Thank you for choosing our service!</p>
<p>
<a th:href="@{https://oursite.com}" th:text="#{email.footer.website}">Visit Website</a> |
<a th:href="@{mailto:support@oursite.com}" th:text="#{email.footer.support}">Contact Support</a>
</p>
</div>
</body>
</html>
관리자 대시보드
<!-- 관리자 대시보드 통계 -->
<div class="dashboard-stats">
<div class="stat-card" th:each="stat : ${dashboardStats}">
<div class="stat-icon" th:class="|icon-${stat.type}|">
<i th:class="${stat.iconClass}"></i>
</div>
<div class="stat-content">
<h3 th:text="${stat.title}">Total Users</h3>
<div class="stat-number" th:text="${#numbers.formatInteger(stat.value, 0, 'COMMA')}">1,234</div>
<div class="stat-change" th:classappend="${stat.changePercent >= 0} ? 'positive' : 'negative'">
<span th:text="|${stat.changePercent >= 0 ? '+' : ''}${stat.changePercent}%|">+5.2%</span>
<span th:text="#{dashboard.from.lastmonth}">from last month</span>
</div>
</div>
</div>
</div>
<!-- 최근 활동 로그 -->
<div class="recent-activity">
<h2 th:text="#{dashboard.recent.activity}">Recent Activity</h2>
<div class="activity-list">
<div class="activity-item" th:each="activity : ${recentActivities}">
<div class="activity-time" th:text="${#temporals.format(activity.timestamp, 'HH:mm')}">14:30</div>
<div class="activity-content">
<span th:text="${activity.description}">User logged in</span>
<small th:text="${activity.userEmail}">user@example.com</small>
</div>
<div class="activity-status" th:class="|status-${activity.type}|">
<span th:text="${activity.status}">Success</span>
</div>
</div>
</div>
</div>
설명:
#numbers.formatInteger()
: 숫자 포맷팅 (천 단위 콤마)th:classappend
: 기존 클래스에 조건부 클래스 추가#temporals.format()
: 날짜/시간 포맷팅- 복합적인 조건식과 표현식 활용으로 복잡한 UI 로직 처리
이러한 다양한 사례들을 통해 Thymeleaf가 단순한 웹 페이지부터 복잡한 대시보드까지 다양한 용도로 활용될 수 있음을 알 수 있습니다.
반응형