Apache Tiles ์นํธ์ํธ ํต์ฌ ๋ช
๋ น์ด ๋ฐ ์ค์ ๊ฐ์ด๋
Composite View ํจํด, tiles.xml ๋ ์ด์์ ์ ์, ์์ผ๋์นด๋ ๋งคํ(*/*), Base Layout ์์, Spring MVC TilesViewResolver ๋ฐ ์ค๋ฌด ํธ๋ฌ๋ธ์ํ
๋ ํผ๋ฐ์ค
## 1. ๊ธฐ๋ณธ ์ํคํ
์ฒ & Composite View ํจํด (Architecture)
```text
[ํด๋ผ์ด์ธํธ ์์ฒญ] โโ> [Spring Controller] โโ> [TilesViewResolver]
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โผ โผ
[tiles.xml Definition ๋งคํ] [์ต์ข
์กฐ๋ฆฝ ๋ธ๋ผ์ฐ์ ์ถ๋ ฅ]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Definition: "board/list" (extends "baseLayout") โ โ Header (header.jsp) โ
โ - header : /WEB-INF/views/layout/header.jsp โ โโโโโโโ> โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ - sidebar : /WEB-INF/views/layout/sidebar.jsp โ โ Sidebar โ Body (board/list.jsp) โ
โ - body : /WEB-INF/views/board/list.jsp โ โ โ โ
โ - footer : /WEB-INF/views/layout/footer.jsp โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ Footer (footer.jsp) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
## 2. ์์กด์ฑ ์ค์ (Maven & Gradle)
```xml
<!-- Maven (pom.xml) - Tiles 3.x ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ -->
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>3.0.8</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-core</artifactId>
<version>3.0.8</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-servlet</artifactId>
<version>3.0.8</version>
</dependency>
```
```groovy
// Gradle (build.gradle)
implementation 'org.apache.tiles:tiles-jsp:3.0.8'
implementation 'org.apache.tiles:tiles-core:3.0.8'
implementation 'org.apache.tiles:tiles-servlet:3.0.8'
```
## 3. Spring MVC ์ค์ (TilesConfigurer & ViewResolver)
```java
// Spring Java Config ์ค์ ํด๋์ค
@Configuration
public class TilesConfig {
// [1] tiles.xml ํ์ผ ๊ฒฝ๋ก ์ง์ ๋ฐ ์ค์ ๋ก๋
@Bean
public TilesConfigurer tilesConfigurer() {
TilesConfigurer configurer = new TilesConfigurer();
configurer.setDefinitions(new String[] {
"/WEB-INF/tiles/tiles.xml",
"/WEB-INF/tiles/tiles-*.xml" // ๋ชจ๋๋ณ ๋ถํ ์ค์ ์ง์
});
configurer.setCheckRefresh(true); // ๊ฐ๋ฐ ํ๊ฒฝ: XML ์์ ์ ์๋ฒ ์ฌ์์ ์์ด ์ค์๊ฐ ๋ฆฌ๋ก๋
return configurer;
}
// [2] ์ปจํธ๋กค๋ฌ๊ฐ ๋ฐํํ View ์ด๋ฆ์ Tiles Definition๊ณผ ๋งคํ
@Bean
public TilesViewResolver tilesViewResolver() {
TilesViewResolver resolver = new TilesViewResolver();
resolver.setViewClass(TilesView.class);
resolver.setOrder(1); // InternalResourceViewResolver(JSP ์ง์ ํธ์ถ)๋ณด๋ค ์ฐ์ ์์ ๋๊ฒ ์ค์
return resolver;
}
}
```
## 4. tiles.xml ๊ธฐ๋ณธ ๊ณจ๊ฒฉ & ๋ ์ด์์ ์ ์ (tiles.xml)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<!-- [1] ๊ณตํต ๊ธฐ๋ณธ ๋ ์ด์์ ์ ์ (๋ชจ๋ ํ์ด์ง์ ๋ถ๋ชจ ํ
ํ๋ฆฟ) -->
<definition name="baseLayout" template="/WEB-INF/views/layout/baseLayout.jsp">
<put-attribute name="title" value="๊ธฐ๋ณธ ์์คํ
ํ์ดํ" />
<put-attribute name="header" value="/WEB-INF/views/layout/header.jsp" />
<put-attribute name="sidebar" value="/WEB-INF/views/layout/sidebar.jsp" />
<put-attribute name="body" value="" /> <!-- ๊ฐ ์์ ํ์ด์ง์์ ์ค๋ฒ๋ผ์ด๋ -->
<put-attribute name="footer" value="/WEB-INF/views/layout/footer.jsp" />
</definition>
<!-- [2] ๊ฐ๋ณ ์ ์ ํ์ด์ง ์ ์ (baseLayout ์์) -->
<definition name="home" extends="baseLayout">
<put-attribute name="title" value="ํ ํ๋ฉด - LuckyTechWorld" />
<put-attribute name="body" value="/WEB-INF/views/home.jsp" />
</definition>
</tiles-definitions>
```
## 5. ์ค๋ฌด ํ์: ์์ผ๋์นด๋ ์๋ ๋งคํ (Wildcard Patterns)
```xml
<!-- ์์ผ๋์นด๋(*)๋ฅผ ์ฌ์ฉํ์ฌ ํ์ด์ง๊ฐ ์ถ๊ฐ๋ ๋๋ง๋ค ๋งค๋ฒ XML์ ์์ ํ ํ์๊ฐ ์๋๋ก ์๋ํ -->
<tiles-definitions>
<!-- 1๋จ๊ณ ๋๋ ํ ๋ฆฌ ๋งคํ: "user/list" -> body: "/WEB-INF/views/user/list.jsp" -->
<definition name="*/*" extends="baseLayout">
<put-attribute name="title" value="{1} - {2}" />
<put-attribute name="body" value="/WEB-INF/views/{1}/{2}.jsp" />
</definition>
<!-- 2๋จ๊ณ ํ์ ๋๋ ํ ๋ฆฌ ๋งคํ: "admin/member/detail" -> body: "/WEB-INF/views/admin/member/detail.jsp" -->
<definition name="*/*/*" extends="baseLayout">
<put-attribute name="title" value="{1} - {2} - {3}" />
<put-attribute name="body" value="/WEB-INF/views/{1}/{2}/{3}.jsp" />
</definition>
</tiles-definitions>
```
## 6. Base Layout JSP ์์ฑ (baseLayout.jsp)
```html
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!-- Tiles ํ๊ทธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ์ธ ํ์ -->
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<!-- ๋ฌธ์์ด ์์ฑ ๋จ์ ์ถ๋ ฅ (getAsString) -->
<title><tiles:getAsString name="title" /></title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/css/layout.css">
</head>
<body>
<div id="wrapper">
<!-- 1. ํค๋ ์กฐ๊ฐ ์ฝ์
-->
<header id="header">
<tiles:insertAttribute name="header" />
</header>
<div id="container">
<!-- 2. ์ข์ธก ์ฌ์ด๋๋ฐ ์ฝ์
-->
<aside id="sidebar">
<tiles:insertAttribute name="sidebar" />
</aside>
<!-- 3. ๋ณธ๋ฌธ ๋ฉ์ธ ์ฝํ
์ธ ์์ญ ์ฝ์
(๋งค ํ์ด์ง๋ง๋ค ๊ต์ฒด๋จ) -->
<main id="content">
<tiles:insertAttribute name="body" />
</main>
</div>
<!-- 4. ํธํฐ ์กฐ๊ฐ ์ฝ์
-->
<footer id="footer">
<tiles:insertAttribute name="footer" />
</footer>
</div>
</body>
</html>
```
## 7. ์ปจํธ๋กค๋ฌ ์ฐ๋ & View ๋ฐํ (Spring Controller)
```java
@Controller
public class BoardController {
// 1. "board/list" ๋ฌธ์์ด ๋ฐํ
@GetMapping("/board/list")
public String list(Model model) {
model.addAttribute("articles", boardService.getArticles());
// TilesResolver์ ์ํด tiles.xml์ "board/list" ๋๋ "*/*" ์์ผ๋์นด๋ ์ ์์ ๋งคํ๋จ
return "board/list";
}
// 2. ๊ด๋ฆฌ์ ํ์ด์ง ๋ฐํ (adminLayout ์ ์ฉ)
@GetMapping("/admin/users")
public String adminUsers(Model model) {
return "admin/users"; // "admin/*" ์ ์์ ๋งคํ
}
}
```
## 8. ๋ค์ค ๋ ์ด์์ ๋ถ๊ธฐ (Multiple Layouts: User vs Admin)
```xml
<tiles-definitions>
<!-- [์ฌ์ฉ์ ๊ณตํต ๋ ์ด์์] -->
<definition name="userBaseLayout" template="/WEB-INF/views/layout/userLayout.jsp">
<put-attribute name="header" value="/WEB-INF/views/layout/userHeader.jsp" />
<put-attribute name="footer" value="/WEB-INF/views/layout/userFooter.jsp" />
</definition>
<!-- [๊ด๋ฆฌ์ ์ ์ฉ ๋ ์ด์์ (์ด๋์ด ํ
๋ง + ์ ์ฉ ๊ด๋ฆฌ ๋ฉ๋ด)] -->
<definition name="adminBaseLayout" template="/WEB-INF/views/admin/layout/adminLayout.jsp">
<put-attribute name="adminHeader" value="/WEB-INF/views/admin/layout/adminHeader.jsp" />
<put-attribute name="adminSidebar" value="/WEB-INF/views/admin/layout/adminSidebar.jsp" />
<put-attribute name="footer" value="/WEB-INF/views/layout/userFooter.jsp" />
</definition>
<!-- ๊ด๋ฆฌ์ ๊ฒฝ๋ก ๋งคํ ์ ๋์ฌ ๋ถ๊ธฐ -->
<definition name="admin/*/*" extends="adminBaseLayout">
<put-attribute name="body" value="/WEB-INF/views/admin/{1}/{2}.jsp" />
</definition>
</tiles-definitions>
```
## 9. ํ์
/ ๋ ์ด์์ ์๋ ๋จ๋
๋ทฐ (Popup & Clean View)
```xml
<tiles-definitions>
<!-- ํค๋, ํธํฐ ์์ด ๋ณธ๋ฌธ ํ์
๋ด์ฉ๋ง ์ถ๋ ฅํ๋ ๋น ๊ป๋ฐ๊ธฐ ๋ ์ด์์ -->
<definition name="emptyLayout" template="/WEB-INF/views/layout/emptyLayout.jsp">
<put-attribute name="body" value="" />
</definition>
<definition name="popup/*" extends="emptyLayout">
<put-attribute name="body" value="/WEB-INF/views/popup/{1}.jsp" />
</definition>
</tiles-definitions>
```
```html
<!-- emptyLayout.jsp -->
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<!DOCTYPE html>
<html>
<head><title>ํ์
</title></head>
<body>
<tiles:insertAttribute name="body" />
</body>
</html>
```
## 10. ๋์ ์์ฑ ์ฃผ์
(Dynamic Attribute Passing)
```html
<!-- ๋ถ๋ชจ ๋ ์ด์์์์ ์์ ์กฐ๊ฐ์ผ๋ก ๋์ ํ๋ผ๋ฏธํฐ ์ ๋ฌ -->
<tiles:insertAttribute name="header">
<tiles:putAttribute name="currentMenu" value="BOARD" />
<tiles:putAttribute name="showSearch" value="true" />
</tiles:insertAttribute>
<!-- header.jsp ๋ด๋ถ์์ ์ ๋ฌ๋ฐ์ ์์ฑ ์ ๊ทผ -->
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<tiles:importAttribute name="currentMenu" />
<c:if test="${currentMenu eq 'BOARD'}">
<span class="active">๊ฒ์ํ ํ์ฑํ</span>
</c:if>
```
## 11. Spring Boot 3.x / Jakarta ์ฃผ์์ฌํญ (Migration Tips)
```text
# โ ๏ธ Spring Boot 3.x (Jakarta EE 9+) ํธํ์ฑ ์ด์
- ๊ณต์ Apache Tiles๋ 2018๋
(3.0.8) ์ดํ ์๋ก์ด ๋ฒ์ ์ด ๋ฆด๋ฆฌ์ฆ๋์ง ์์ jakarta.servlet.* ๋ค์์คํ์ด์ค๋ฅผ ๊ธฐ๋ณธ ์ง์ํ์ง ์์.
- Spring Boot 3.x์์ Tiles๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ NoSuchMethodError ๋๋ ClassNotFoundException(javax.servlet.*) ๋ฐ์.
# [ํด๊ฒฐ ๋ฐฉ์ 3๊ฐ์ง]
1. ์ต์ ํ๋ก์ ํธ: Thymeleaf, JSP ๋จ๋
ํ
ํ๋ฆฟ(jsp:include), ๋๋ ํ๋ก ํธ SPA(Vue/React) ๋์
๊ถ์ฅ
2. ๊ธฐ์กด ์์คํ
์ ์ง๋ณด์: Spring Framework 5.3.x (Spring Boot 2.7.x) LTS ๋ฒ์ ์ ์ง
3. ๋ณํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ: ์ปค๋ฎค๋ํฐ ์คํ์์ค jakarta-tiles ํฌํฌ ํ๋ก์ ํธ ๋๋ Eclipse Transformer ํ์ฉ
```
## 12. ์ค๋ฌด ํธ๋ฌ๋ธ์ํ
& ์ฒดํฌ๋ฆฌ์คํธ (Troubleshooting)
```text
# [1] NoSuchDefinitionException ๋ฐ์ ์
- ์์ธ 1: ์ปจํธ๋กค๋ฌ์์ ๋ฐํํ ๋ทฐ ์ด๋ฆ๊ณผ tiles.xml์ <definition name="..."> ๋ถ์ผ์น
- ์์ธ 2: tiles.xml์ DTD ์ ์ธ URL ์คํ ๋๋ XML ํ์ฑ ์๋ฌ
- ํ์ธ: TilesConfigurer์ setDefinitions ๊ฒฝ๋ก ๋งคํ์ด ์ฌ๋ฐ๋ฅธ์ง ํ์ธ
# [2] JSP ํ๋ฉด ์์ ๋ด์ฉ์ด ๋ฐ๋ก ๋ฐ์๋์ง ์์ ๋
- ์์ธ: tiles.xml ๋ฐ JSP ํ์ผ ์บ์ฑ
- ํด๊ฒฐ: TilesConfigurer์ configurer.setCheckRefresh(true) ์ค์ ํ์ธ
# [3] ViewResolver ์ฐ์ ์์ ์ถฉ๋
- TilesViewResolver์ order(1)๊ฐ InternalResourceViewResolver์ order(2)๋ณด๋ค ์์์ผ ํจ.
```
์๊ฒฌ ๋ฐ ์ง๋ฌธ
0์์ง ๋ฑ๋ก๋ ์๊ฒฌ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋๊ธ์ ๋จ๊ฒจ๋ณด์ธ์!
๋๊ธ ์์
๋๊ธ ์ญ์