๋ฐ์ํ
ํ์๋ฆฌํ(Thymeleaf)๋?
ํ์๋ฆฌํ(Thymeleaf)๋ Spring MVC + ๋ทฐ ํ ํ๋ฆฟ ์์ง์ผ๋ก ๊ฐ์ฅ ๋ง์ด ์ฐ์ด๋ ๊ธฐ๋ณธ ํ ํ๋ฆฟ์ด๋ค.
๋ณดํต ๊ธฐ๋ณธ ํ์ด๋ผ๊ณ ํ๋ฉด, HTML ์์์ ํ์๋ฆฌํ๋ฅผ ์ ์ฉํ๋ ๊ณต์ ๊ตฌ์กฐ๋ฅผ ์๋ฏธํ๋ค.
1.HTML ๋ฌธ์ ๊ธฐ๋ณธ ๊ตฌ์กฐ
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ํ์๋ฆฌํ ๊ธฐ๋ณธ ํ</title>
</head>
<body>
<h1 th:text="${title}">๊ธฐ๋ณธ ์ ๋ชฉ</h1>
<!-- ๋ณ์ ์ถ๋ ฅ -->
<p th:text="${message}">๊ธฐ๋ณธ ๋ฉ์์ง</p>
<!-- ์กฐ๊ฑด๋ฌธ -->
<div th:if="${isLogin}">
๋ก๊ทธ์ธ ์ํ์
๋๋ค.
</div>
<div th:unless="${isLogin}">
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค.
</div>
<!-- ๋ฐ๋ณต๋ฌธ -->
<ul>
<li th:each="item : ${items}" th:text="${item}">์์ดํ
</li>
</ul>
<!-- ๋งํฌ -->
<a th:href="@{/member/list}">ํ์๋ชฉ๋ก</a>
</body>
</html>
2. ์ปจํธ๋กค๋ฌ ์์ (Spring mvc)
@Controller
public class ThymeleafController {
@RequestMapping("/example")
public String example(Model model) {
model.addAttribute("title", "ํ์๋ฆฌํ ์์ ");
model.addAttribute("message", "Hello Thymeleaf!");
model.addAttribute("isLogin", true);
model.addAttribute("items", Arrays.asList("์ฌ๊ณผ", "๋ฐ๋๋", "ํฌ๋"));
return "example"; // resources/templates/example.html
}
}
3. ๋์ ์๋ฆฌ
- Controller์์ Model์ ๋ฐ์ดํฐ ๋ด์
- return "example" → example.html ์ฐพ์๊ฐ
- ${๋ณ์๋ช } → Model์ ๊ฐ์ผ๋ก ์นํ
๋ ์ด์์ ๊ตฌ์กฐํ
JSP ์์ ์ <%@ include file="header.jsp" %> ์ด๋ฐ ๊ฑฐ ํ๋ ๊ฑธ, ํ์๋ฆฌํ์์๋ fragment๋ก ๊น๋ํ๊ฒ ๊ฐ์ ธ์ฌ ์ ์๋ค.
1.header.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="${title}">๊ณตํต ํ์ดํ</title>
</head>
<body>
<!-- header fragment -->
<div th:fragment="header">
<header>
<h1>๊ณตํต ํค๋</h1>
<nav>
<a th:href="@{/}">ํ</a>
<a th:href="@{/member/list}">ํ์๋ชฉ๋ก</a>
</nav>
</header>
</div>
2. footer.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<!-- footer fragment -->
<div th:fragment="footer">
<footer>
<p>โ 2025 MyCompany</p>
</footer>
</div>
</body>
</html>
๋ณธ๋ฌธ์์ include ํ๊ธฐ
3. example.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>ํ์๋ฆฌํ Fragment ์์ </title>
</head>
<body>
<!-- ํค๋ ์ฝ์
-->
<div th:replace="fragments/header :: header"></div>
<!-- ๋ณธ๋ฌธ -->
<main>
<h2 th:text="${title}">๊ธฐ๋ณธ ์ ๋ชฉ</h2>
<p th:text="${message}">๋ณธ๋ฌธ ๋ด์ฉ</p>
</main>
<!-- ํธํฐ ์ฝ์
-->
<div th:replace="fragments/footer :: footer"></div>
</body>
</html>
๋์ ์๋ฆฌ
- fragments/header :: header → header.html ํ์ผ ์์ th:fragment="header" ์ง์ ๋ ๋ถ๋ถ์ ๊ฐ์ ธ์ด
- fragments/footer :: footer → footer.html ์์ footer fragment ๊ฐ์ ธ์ด
- ์ด๋ ๊ฒ ํ๋ฉด ํค๋/ํธํฐ๋ฅผ ๋ชจ๋ ํ์ด์ง์ ๋ฐ๋ณตํ์ง ์๊ณ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ
replace vs insert vs include ์ฐจ์ด
- th:replace → ๊ฐ์ ธ์จ fragment๊ฐ ์๊ธฐ ์์ ์ ํต์งธ๋ก ๋์ฒด
- th:insert → fragment ๋ด์ฉ์ ํ์ฌ ํ๊ทธ ์์ ์ฝ์
- th:include → ์๋ ๋ฐฉ์ (๊ฑฐ์ ์ ์)
์ค๋ฌด์์๋ ๋ณดํต replace ๋ง์ด ์ด๋ค.
์ฆ, header/footer๋ฅผ fragment๋ก ๋๋ ๋๊ณ
ํ์ํ ๋ th:replace๋ก ๊ฐ์ ธ์ค๋ฉด JSP include์ฒ๋ผ ๊ณตํต ๋ ์ด์์ ๊ด๋ฆฌ๊ฐ ๊น๋ํด์ง ์ ์๋ค.
๋ฐ์ํ