Framework & Library/Spring

[Java / Spring] JSP -> Thymeleaf(타임리프) 변경

마볼링 2023. 5. 8. 21:48

📍 서론

JSP는 예전 키보드 쇼핑몰 프로젝트에서 썻던 템플릿 엔진이고,
mustache는 null값 처리와 프론트 단에서 불편한 점이 조금 있어서,
타임리프를 사용해보았다.

 

📍 Thymeleaf (타임리프) 사용하기

Gradle - build.gradle

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

Maven - pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

위 설정을 추가 후 빌드하면 application.properties에 아래 코드가 자동으로 추가된다.

spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

 

📍 Thymeleaf 문법

대부분의 html 속성을  th:xxx 로 변경할 수 있다.

ex: th:text="${변수명}"

표현 설명 예제
@{ ... } URL 링크 표현식 th:href="@{/css/bootstrap.min.css}"
th:href="@{/{itemId}/edit(itemId=${item.id})}"
| ... | 리터럴 대체 th:text="|Hi ${user.name}!|"
(= th:text="'Hi '+${user.name}+'!'"
${ ... } 변수 th:text=${user.name}
th:each 반복 출력 <tr th:each="item: ${items}">
  <td th:text="${item.price}">100</td>
</tr>
*{ ... } 선택 변수 <tr th:object="${items}">
  <td th:text="*{price}">100</td>
</tr>
#{ ... } 메시지. properties 같은 외부 자원에서 코드에 해당하는 문자열 get. th:text="#{member.register}"

 

📍 Thymeleaf Layout

Header, Footer와 같이 공통적으로 반복되는 코드를 화면마다 작성하지 않고 레이아웃 처리를 통해 일괄 적용한다

 

Gradle - build.gradle

implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'

 

fragments/header.html - 상단

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <th:block th:fragment="headerFragment">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport"
            content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <!-- 제이쿼리 -->
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
      <!-- CSS -->
      <link rel="stylesheet" href="../css/index.css">
      <link rel="stylesheet" href="../css/lostark.css">
      <!-- Boxicons CSS -->
      <link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
      <title>Minhyeok Personal Web</title>
    </head>
  </th:block>
</html>

 

fragments/footer.html - 하단

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <th:block th:fragment="footerFragment">
    <footer>
    </footer>
  </th:block>
</html>

 

layouts/default_layout.html - 기본 레이아웃 (메뉴바포함)

<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<th:block th:replace="fragments/header :: headerFragment"></th:block>
<body>
<nav class="sidebar">
    <header>
        <div class="image-text">
            <span class="image">
                <img src="../resources/logo.png" alt="logo">
            </span>
            <div class="text header-text">
                <span class="name">Minhyeok</span>
                <span class="profession">Personal Web</span>
            </div>
        </div>
        <i class="bx bx-chevron-right toggle"></i>
    </header>
    <div class="menu-bar">
        <div class="menu">
            <li class="search-box">
                <i class="bx bx-search icon"></i>
                <input id="igdb_id" type="text" placeholder="IGDB ID Search..." onkeyup="enterKey();"/>
            </li>
            <ul class="menu-links">
                <li class="nav-link">
                    <a href="/lostark" class="nav-link-item">
                        <img class="img-icon" src="../resources/lostark_icon.jpeg" alt="lostark_icon">
                        <span class="text nav-text">로스트아크</span>
                    </a>
                </li>
                <div class="nav-dropdown">
                    <li>
                        <a href="/lostark/character" class="nav-link-item">
                            <i class='bx bx-right-arrow icon'></i>
                            <span class="text nav-text">내 캐릭터</span>
                        </a>
                    </li>
                </div>

                <li class="nav-link">
                    <a href="/restaurant">
                        <i class='bx bx-food-menu icon'></i>
                        <span class="text nav-text">점심식사</span>
                    </a>
                </li>
                <div class="nav-dropdown">
                    <li>
                        <a href="/restaurant/seohyunData" class="nav-link-item">
                            <i class='bx bx-data icon'></i>
                            <span class="text nav-text">서현 데이터</span>
                        </a>
                    </li>
                </div>
                <li class="nav-link">
                    <a href="/IGDB">
                        <i class="bx bx-bar-chart-alt-2 icon"></i>
                        <span class="text nav-text">IGDB</span>
                    </a>
                </li>
                <li class="nav-link">
                    <a href="">
                        <i class="bx bx-bell icon"></i>
                        <span class="text nav-text">Notifications</span>
                    </a>
                </li>
                <li class="nav-link">
                    <a href="">
                        <i class="bx bx-pie-chart-alt icon"></i>
                        <span class="text nav-text">Analytics</span>
                    </a>
                </li>
                <li class="nav-link">
                    <a href="">
                        <i class="bx bx-heart icon"></i>
                        <span class="text nav-text">Likes</span>
                    </a>
                </li>
                <li class="nav-link">
                    <a href="">
                        <i class='bx bx-data icon'></i>
                        <span class="text nav-text">Datas</span>
                    </a>
                </li>
            </ul>
        </div>
        <div class="bottom-content">
            <!--
            <li class="">
                <a href="">
                    <i class="bx bx-log-out icon"></i>
                    <span class="text nav-text">Logout</span>
                </a>
            </li>
            -->
            <li class="mode">
                <div class="moon-sun">
                    <i class="bx bx-moon icon moon"></i>
                    <i class="bx bx-sun icon sun"></i>
                </div>
                <span class="mode-text text">Dark Mode</span>
                <div class="toggle-switch">
                    <span class="switch"></span>
                </div>
            </li>
        </div>
    </div>
</nav>
<section class="home">
    <th:block layout:fragment="content"></th:block>
</section>
</body>
<th:block th:replace="fragments/footer :: footerFragment"></th:block>
<script src="../js/index.js"></script>
<script src="../js/lostark.js"></script>
</html>

 

본문 적용

lostark/lostarkCharacter.html - 로스트아크 오픈 Api를 이용하여 대표캐릭터를 가져와서 출력하는 영역

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="layouts/default_layout">
<th:block layout:fragment="content">
    <div class="text">로스트아크 > 내 캐릭터(${mainServer} - ${mainCharacter})</div>
    <div class="lostark wrap">
        <div class="lostark-character">
            <div th:each="character : ${characters}" class="lostark-character-wrap">
                <div class="lostark-character-image">
                    <img th:src="${character.characterImage}">
                </div>
                <div class="lostark-character-text">
                    <div class="lostark-character-textbox" style="background: #695CFE;">
                        <p th:text="${character.characterName}"></p>
                        <p>캐릭터 클래스 : <span th:text="${character.characterClassName}"></span></p>
                        <p>아이템 레벨 : <span th:text="${character.itemMaxLevel}"></span></p>
                    </div>
                    <div class="lostark-character-textbox" style="background: #FA8072;">
                        <p>카오스 던전 : <span th:text="${character.chaosName}"></span></p>
                        <p>가디언 토벌 : <span th:text="${character.guardianName}"></span></p>
                        <p>카오스 던전 휴게 : 개발중</p>
                        <p>가디언 토벌 휴게 : 개발중</p>
                    </div>
                </div>
            </div>
        </div>
    </div>
</th:block>
</html>

 

기존 jsp

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@include file="../common/header.jsp" %>
<div class="text">로스트아크 > 내 캐릭터(${mainServer} - ${mainCharacter})</div>
<div class="lostark wrap">
    <div class="lostark-character">
        <c:forEach var="character" items="${characters}">
        <div class="lostark-character-wrap">
            <div class="lostark-character-image">
                <img src="${character.characterImage}">
            </div>
            <div class="lostark-character-text">
                <div class="lostark-character-textbox" style="background: #695CFE;">
                    <p>${character.characterName}</p>
                    <p>캐릭터 클래스 : ${character.characterClassName}</p>
                    <p>아이템 레벨 : ${character.itemMaxLevel}</p>
                </div>
                <div class="lostark-character-textbox" style="background: #FA8072;">
                    <p>카오스 던전 : ${character.chaosName}</p>
                    <p>가디언 토벌 : ${character.guardianName}</p>
                    <p>카오스 던전 휴게 : 개발중</p>
                    <p>가디언 토벌 휴게 : 개발중</p>
                </div>
            </div>
        </div>
        </c:forEach>
    </div>
</div>

<%@include file="../common/footer.jsp" %>

 

결과는 똑같이 출력된다

 

이제... 다 바꾸면된다...

 

 

 

 

 


참고

 

[Thymeleaf] 타임리프란? (+기본적인 사용법)

간단한 CRUD를 구현하다가 org.thymeleaf.exceptions.TemplateProcessingException 에러를 너무 많이 겪어서 타임리프에 대해 잠깐 공부하기로 했다. (위 에러는 타임리프 문법을 잘못 쓰는 등의 이유로 특정 url

yeonyeon.tistory.com

 

 

[Spring Boot] Thymeleaf 반복되는 헤더, 푸터 레이아웃 적용하기

[Spring Boot/게시판 만들기] - 스프링 부트 프로젝트 생성하기 위의 과정을 통해 진행되는 프로젝트입니다. 📌 개발환경 IntelliJ Community, SpringBoot, Java 1.8, Gradle, Jar, Thymeleaf, JPA, MariaDB Thymeleaf Layout Hea

hnev.tistory.com