6. 템플릿 엔진 알아보기

DAEILLIM·2023년 12월 2일
0
post-thumbnail

1. 템플릿 엔진

뷰는 사용자에게 보여줄 내용을 처리 결과 데이터를 가지고 HTML을 생성해서 클라이언트에 응답을 알려주는 역할을 합니다. 템플릿 엔진에 대해 간단하게 설명하면 '데이터를 미리 정의된 템플릿에 바인딩해서 뷰의 표시를 도와주는 것'입니다.

바인딩이란 어떤 요소나 데이터, 파일 등을 서로 연결하는 것을 의미합니다.


2. 타임리프

  • HTML 기반의 템플릿 엔진으로, 정해진 문법으로 작성하면 페이지를 동적으로 구현할 수 있습니다.
  • HTML을 기반으로 하기 때문에 최종 출력을 생각하면서 뷰를 생성할 수 있습니다.
    즉, 타임리프를 사용하면 디자이너와 쉽게 분업할 수 있습니다.

타임리프에 대해 학습하기 전에 반드시 기억해야 할 것은 모델입니다.
스프링 MVC가 요청을 받고 응답을 보낼 때까지의 흐름에 대해 다시 한 번 복습하고 진행합니다.


image-20231202224033234
  1. DispatcherServlet이 클라이언트로부터 요청을 받습니다.(프런트 컨트롤러 패턴)
  2. DispatcherServlet이 컨트롤러의 요청 핸들러 메서드를 호출합니다.
  3. 컨트롤러는 비즈니스 로직 처리를 실행하여 처리 결과를 취득합니다.
  4. 처리 결과를 Model로 설정하고 뷰 이름을 반환합니다.
  5. DispatcherServlet은 뷰 이름에 대응하는 뷰에 대해서 화면 표시 처리를 의뢰합니다.
  6. 클라이언트가 응답을 받고 브라우저에 화면이 표시됩니다.

3. Model 인터페이스

Model 인터페이스의 특징에 대해 간단하게 설명하면 다음과 같습니다.

  • 처리한 데이터를 뷰에 표시하고 싶을 경우 데이터를 전달하는 역할을 수행합니다.
  • 스프링 MVC에 의해 관리되며, 수동 혹은 자동으로 객체를 저장하고 관리하는 기능을 제공합니다.
  • Model을 이용하고 싶은 경우 요청 핸들러 메서드의 인수에 Model 타입을 전달합니다.
    그러면 스프링 MVC가 자동으로 Model 타입 인스턴스를 설정합니다.

3.1 addAttribute

Model 객체를 저장하기 위한 메서드는 여러 가지가 존재합니다. 다음의 메서드는 반드시 기억해야 합니다.

Model addAttribute(String name, Ojbect value)
  • name: 이름(별명)
  • value: 값(저장하고 싶은 객체)

addAttribute는 스프링 MVC에서 모델에 데이터를 추가하는 메서드입니다.
모델은 컨트롤러에서 뷰로 데이터를 전달하는 데 사용되는 객체이며, addAttribute를 통해 모델에 속성(attribute)을 추가할 수 있습니다.

일반적으로 컨트롤러에서 처리한 결과나 화면에 표시할 데이터를 모델에 추가한 후, 이 모델은 뷰에 전달되어 클라이언트에게 화면이나 정보를 보여주게 됩니다.


3.2 addAttribute 예제 1

아래는 간단한 예제를 통해 addAttribute의 사용을 설명합니다.
여기서는 스프링 MVC의 컨트롤러 메서드 내에서 사용되는 경우를 살펴봅니다.

@Controller
@RequestMapping("/example")
public class ExampleController {

    @GetMapping("/showData")
    public String showData(Model model) {
        // 모델에 데이터 추가
        model.addAttribute("message", "Hello, World!");
        model.addAttribute("count", 42);

        // 뷰 이름 반환
        return "dataView";
    }
}

위 코드에서 showData라는 메서드는 "/example/showData" 경로에 대한 GET 요청을 처리하는 메서드입니다. Model 객체를 메서드 매개변수로 받아와서, 이 모델에 addAttribute를 사용하여 두 개의 속성("message"와 "count")을 추가하고 있습니다.

이후 메서드는 문자열 "dataView"를 반환합니다. 이 문자열은 실제로 뷰의 이름을 나타냅니다. 스프링 MVC는 이 뷰의 이름을 기반으로 실제 뷰를 찾아 렌더링하고, 그 과정에서 모델에 추가된 데이터를 사용합니다.

따라서 "dataView"라는 뷰에서는 "message"와 "count"라는 속성을 사용하여 화면에 정보를 표시할 수 있습니다. 예를 들어, Thymeleaf와 같은 템플릿 엔진을 사용한다면 해당 속성을 템플릿에서 가져와 동적으로 화면을 구성할 수 있습니다.


3.3 addAttribute 예제 2

@controller
@RequestMapping("hello")
public class HelloModelController {
    @GetMapping("model")
    public String helloView(Model model) {
        // Model에 데이터를 저장
        model.addAttribute("msg", "타임리프!!!");
        
        // 반환값으로 뷰 이름을 반환
        return "helloThymeleaf";
    }
}

타임 리프를 사용하는 경우, 컨트롤러는 뷰에서 표시할 데이터를 준비해야 합니다. 그때 Model 인터페이스를 통해 뷰에 표시할 데이터를 준비할 수 있습니다. Model 인터페이스를 사용하려면 요청 핸들러 메서드의 인수에 Model 타입을 전달합니다. 전달되면 스프링 MVC가자동으로 Model 타입의 인스턴스를 설정하므로 Model의 addAttribute 메서드를 사용할 수 있습니다.

HelloModelController 클래스에서는 클래스에 @RequestMapping("hello") 어노테이션을 부여하고, 요청 핸들러 메서드에 @GetMapping("model") 어노테이션을 부여합니다. 클라이언트로부터 URL(http://localhost:8080/hello/model)이 GET 메서드로 송신되면 HelloModelController 클래스의 helloView 메서드가 호출되어 반환값으로 뷰 이름을 돌려주는 것으로 그에 대응되는 뷰가 표시됩니다.


4. 뷰 생성

helloView 메서드의 반환값(뷰 이름:helloThymeleaf)에 대응하는 helloThymeleaf.html을 생성합니다.

<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Hello Thymeleaf</title>
    </head>
    <body>
        <h1 th:text="${msg}">표시되는 부분</h1>
    </body>
</html>

타임리프의 기능은 th:xxx속성명 형식으로 입력합니다.

속성기능 개요
th:text속성값에 지정된 값을 새니타이즈(Sanitize)하여 출력합니다.
th:utext속성값에 지정된 값을 출력합니다.(새니타이즈 안 함)

새니타이즈(Sanitize)는 위험한 코드나 변환 또는 제거하여 무력화하는 것입니다. 간단히 설명하면 '특별한 의미를 가진 문자의 특별함을 무효화하고 의도하지 않은 움직임을 봉쇄'하는 것입니다. 새니타이즈는 웹사이트의 입력 폼을 통해 악의적인 코드가 입력되었을 때 등에 유용하게 사용됩니다.


4.1 처리 흐름

image-20231202231126012

요청 핸들러 메서드에서 뷰로 표시하고 싶은 데이터를 Model의 addAttribute(이름, 값) 메서드를 사용해서 저장하고, 타임리프에서 데이터를 표시할 위치를 ${이름} 형식으로 설정합니다. 주의할 점은 뷰에서 사용할 수 있는 것은 addAttribute(이름, 값)이름 부분이라는 것입니다.


5. 타임리프 사용법

타임리프의 특징을 간단하게 설명하면 다음과 같습니다.

  • 스프링 부트에서 추천하는 템플릿 엔진입니다.
  • HTML로 템플릿을 작성하기 때문에 웹 브라우저로 파일의 내용을 표시하고 확인하면서 뷰를 생성할 수 있습니다.
  • 스프링 부트의 기본 설정으로 templates 폴더 아래에 '요청 핸들러 메서드의 반환값 + .html' 파일이 참조됩니다.

5.1 직접 문자를 삽입

직접 설정한 문자를 출력할 때는 th:text="출력 문자"로 문자를 출력할 수 있습니다
속성값의 값 설정에 ""를 사용하므로 문자를 설정할 때는 ''로 둘러쌉니다.

<!-- 01: 직접 문자를 삽입 -->
<h1 th:text="hello world">표시하는 부분</h1>

5.2 인라인 처리

[[${}]] 를 사용하면 태그를 속성에 추가하는 대신 본문에 변수를 포함할 수 있습니다.
고정값과 변수를 조합하고 싶은 경우에는 이 방법이 편리합니다.

<!-- 02: 인라인 처리 -->
<h1>안녕하세요! [[$(name)]] 씨 </h1>

5.3 값 결합

+를 이용해서 값을 결합할 수 있습니다.

<!-- 03: 값 결합 -->
<h1 th:text="'오늘의 날씨는 ' + '맑음 ' + '입니다'">표시하는 부분</h1>

5.4 값 결합(리터럴 치환)

값 결합은 리터럴 치환을 사용하는 것으로 | 문자 |로 기술할 수 있습니다. 문자 안에서 ${} 표현식도 함께 사용이 가능합니다.

<!-- 04: 값 결합(리터럴 치환)-->
<h1 th:text="|안녕하세요 ${name}씨|">표시하는 부분</h1>

5.5 지역 변수

th:with = "변수 이름 = 값"으로 변수에 값을 할당할 수 있습니다. 변수의 범위는 정의된 태그 내부에서만 사용할 수 있습니다.
또한 산술 연산자인 +, -, /, *, %를 사용할 수 있습니다.

<!-- 05: 지역 변수 -->
<div th:with="a=1, b=2">
    <span th:text="|${a} + ${b} = ${a+b}|"></span>
</div>

5.6 비교와 등가

비교 등가 연산자인 <, >, <=, >=, ==, !=를 사용할 수 있습니다. 문자열 비교도 가능합니다.

<!-- 06: 비교와 등가 -->
<span th:text="1 > 10"></span>
<span th:text="1 < 10"></span>
<span th:text="1 >= 10"></span>
<span th:text="1 <= 10"></span>
<span th:text="1 == 10"></span>
<span th:text="1 != 10"></span>
<span th:text="타임 == 타임"></span>
<span th:text="타임 != 리프"></span>

5.7 조건 연산자

삼항 연산자를 이용할 수 있습니다.

<!-- 07: 조건 연산자 -->
<p th:text="${name} == '길동' ? '길동입니다!' : '길동이가 아닙니다'"></p>

5.8 조건 분기(true)

th:if="조건"에서 참(ture)인 경우 th:if에서 작성한 태그와 자식 요소를 표시합니다.

<!-- 08: 조건 분기(true) -->
<div th:if="${name} == '길동'">
    <p>길동 씨입니다!</p>
</div>

5.9 조건 분기(false)

th:unless="조건"에서 거짓(false)인 경우 th:unless에서 작성한 태그와 자식 요소를 표시합니다.

<!-- 09: 조건 분기(false) -->
<div th:unless="${name} == '영희'">
    <p>영희 씨가 아닙니다</p>
</div>

5.10 switch

부모 요소의 th:switch 값과 자식 요소에 작성하는 th:case의 값이 동일한 경우, HTML 요소를 출력합니다.
어떤 값에도 일치하지 않는 값을 출력하는 경우는 th:case="*"를 지정합니다.

<!-- 10: switch -->
<div th:switch="${name}">
    <p th:case="길동" th:text="|${name}입니다!|"></p>
    <p th:case="영희" th:text="|${name}입니다!|"></p>
    <p th:case="철수" th:text="|${name}입니다!|"></p>
    <p th:case="*">명부에 없습니다</p>
</div>

5.11 참조(데이터를 결합한 객체)

캡슐화된 필드를 참조하는 경우 public 접근 제한자의 getXXX()라는 게터 메서드를 작성하여 객체명.필드 형식으로 값을 참조할 수 있씁니다. 또 객체명['필드'] 같이 대괄호로도 참조할 수 있습니다.

<!-- 11. 참조(데이터를 결합한 객체) -->
<p th:text="${mb.id}">ID</p>
<p th:text="${mb.name}">이름</p>
<p th:text="${mb['id']}">ID: []로 접근</p>
<p th:text="${mb['name']}">이름: []로 접근</p>

5.12 참조(th:object)

데이터를 저장한 객체를 th:object에 설정하고, 자식 요소에서 *{필드명}으로 사용합니다.

<!-- 12. 참조(th:object) -->
<div th:object="${mb}">
    <p th:text="*{id}">id</p>
    <p th:text="*{name}">이름</p>
    <p th:text="*{['id']}">ID: []로 접근</p>
    <p th:text="*{['name']}">이름: []로 접근</p>
</div>

5.13 참조(List)

List나 배열의 요소를 참조할 떄는 인덱스 번호를 입력해서 참조합니다.

<!-- 13: 참조(List) -->
<p th:text="${list[0]}">방위</p>
<p th:text="${list[1]}">방위</p>
<p th:text="${list[2]}">방위</p>
<p th:text="${list[3]}">방위</p>

5.14 참조(Map)

Map 요소를 참조하려면 '키'를 이용하여 값을 참조합니다.
map.key 형식으로도 참조할 수 있습니다. 또는 map['키']와 같이 대괄호를 사용하여 참조할 수도 있습니다.

<!-- 14: 참조(map) -->
<p th:text="${map.kim.name}">이름</p>
<p th:text="${map.lee.name}">이름</p>
<p th:text="${map['kim']['name']}">이름 1 : []로 접근</p>
<p th:text="${map['lee'][name]}">이름 2 : []로 접근</p>

5.15 반복

th:each=변수 : 객체로 반복 처리할 수 있습니다. 변수는 반복 처리 중에만 유효합니다. Iterable 인터페이스를 구현한 클래스라면 th:each로 반복 처리할 수 있습니다. 자바의 확장 for문과 동일한 방식입니다.

<!-- 15: 반복 -->
<div th:each="member : ${members}">
    <p>[[${member.id}]] : [[${member.name}]]</p>
</div>

5.16 반복 상태

th:each=변수, 상태 변수 : 객체와 같이 변수의 선언 다음에 상태 변수를 선언하여 반복 상태를 유지한 상태 변수를 사용할 수 있습니다. 다음의 예제에서는 상태 변수s를 선언하고 있습니다. 반복 상태 변수의 목록은 다음 표를 참고하세요.

<!-- 16: 반복 상태 -->
<div th:each="member, s : ${members}" th:object="${obejct}">
    <p>
        index -> [[${s.index}]], count -> [[${s.count}]],
        size -> [[${s.size}]], count -> [[${s.current}]],
        even -> [[${s.even}]], count -> [[${s.odd}]],
        first -> [[${s.first}]], count -> [[${s.last}]],
        [[*{id}]] : [[*{name}]]
    </p>
</div>

반복 상태 변수

상태 변수기능 개요
index0부터 시작하는 인덱스. 현재 인덱스를 표시
count1부터 시작하는 인덱스. 현재 인덱스를 표시
size반복 처리하는 객체 사이즈를 표시
current현재 반복 요소의 객체를 표시
even현재 요소가 짝수 번째인지 여부를 결정
짝수이면 true, 아니면 false를 표시
odd현재 요소가 홀수 번째인지 여부를 결정
홀수이면 true, 아니면 false를 표시
first현재 요소가 첫 번째 요소인지 여부를 결정
첫 번쨰이면 true, 아니면 false를 표시
last현재 요소가 마지막 요소인지 여부를 결정
마지막이면 true, 아니면 false를 표시

5.17 유틸리티 객체

타임리프에서는 자주 사용되는 클래스를 #name이라는 상수로 정의하고 있어서 그대로 변수 표현식에서 사용할 수 있습니다. 데이터를 출력할 때 자주 사용되는 것이 '수치, 일시, 문자열'의 포맷 변환입니다.

유틸리티 객체 목록

유틸리티 객체기능 개요
#strings문자 관련 편의 기능
#numbers숫자 서식 지원
#bools불리언(boolean) 관련 기능
#datesjava.util.Date 서식 지원
#objects객체 관련 기능
arrays배열 관련 기능
listsList 관련 기능
setsSet 관련 기능
mapsMap 관련 기능

유틸리티 객체(숫자)

  • 정수값 형식 변환: #numbers.formatInteger
  • 부동 소수점 형식 변환: #numbers.formatDecimal
  • 쉼표를 사용하는 경우: COMMA
  • 소수점을 사용하는 경우: POINT
<!-- 17:유틸리티 객체(숫자) -->
<div th:with="x=100000, y=123456.789">
    정수 형식:
    <span th:text="${#numbers.formatInteger(x, 3, 'COMMA')}"></span>
    <br/>
    부동 소수점 형식:
    <span th:text="${#numbers.formatDecimal(y, 3, 'COMMA', 2, 'POINT')}"></span>
</div>

유틸리티 객체(날짜 및 시간)

  • 현재 날짜와 시간을 반환: createNow()
  • 날짜가 담긴 변수와 포맷 문자열 지정: format()
  • 년, 월 일을 반환: year(), month(), day()
  • 요일을 반환: dayOfWeek()
<!-- 17: 유틸리티 객체(날짜 및 시간) -->
<div th:with="today=${#dates.createNow()}">
    yyyy/mm/dd 형식: <span th:text="${#dates.format(today, 'yyyy/MM/dd')}"></span> <br/>
    yyyy년 mm월 dd일 형식: <span th:text="${#dates.format(today, 'yyyy년 MM월 dd일')}"></span> <br/>
    yyyy년: <span th:text="${#dates.year(today)}"></span><br/>
    MM월: <span th:text="${#dates.month(today)}"></span><br/>
    dd일: <span th:text="${#dates.day(today)}"></span><br/>
    요일: <span th:text="${#dates.dayOfWeek(today)}"></span><br/>
</div>

유틸리티 객체(문자열)

<!-- 17. 유틸리티 객체(문자열)-->
<div th:with="str1='abcdef'">
    대문자 변환: <span th:text="${#strings.toUpperCase(str1)}"></span><br/>
    빈 문자열 판정: <span th:text="${#strings.isEmpty(str1)}"></span><br/>
    길이: <span th:text="${#strings.length(str1)}"></span><br/>
</div>

5.18 다른 템플릿 삽입하기

th:fragment는 Thymeleaf 템플릿 엔진에서 사용되는 속성으로, 템플릿의 일부분을 정의하여 재사용할 수 있게 해줍니다. 특히, 여러 페이지나 레이아웃에서 공통적으로 사용되는 부분을 분리하고, 이를 템플릿으로 정의하여 th:fragment를 이용해 재사용할 수 있습니다.


5.19 th:fragment 예제 1

th:fragment를 사용하는 일반적인 예제를 살펴보겠습니다. 아래는 헤더를 나타내는 header.html 템플릿 파일이라고 가정합니다:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>My Website</title>
    <link rel="stylesheet" th:href="@{/css/styles.css}" />
</head>
<body>
    <div th:fragment="header">
        <header>
            <h1>Welcome to My Website</h1>
            <nav>
                <ul>
                    <li><a th:href="@{/home}">Home</a></li>
                    <li><a th:href="@{/about}">About</a></li>
                    <li><a th:href="@{/contact}">Contact</a></li>
                </ul>
            </nav>
        </header>
    </div>
</body>
</html>

위 코드에서 <div th:fragment="header">는 헤더 부분을 나타내는 템플릿입니다. 이를 다른 페이지에서 재사용하려면 다음과 같이 다른 페이지에서 th:includeth:replace 등을 사용하여 템플릿을 가져올 수 있습니다:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>My Home Page</title>
    <link rel="stylesheet" th:href="@{/css/styles.css}" />
</head>
<body>
    <div th:replace="header :: header"></div>

    <div>
        <!-- 여기에 페이지의 본문 내용이 들어갑니다 -->
    </div>
</body>
</html>

위 코드에서 <div th:replace="header :: header"></div>header.html 템플릿의 header fragment를 현재 페이지에 삽입합니다.

이렇게 th:fragment를 사용하면 코드의 재사용성이 높아지고, 유지보수가 용이해집니다. 페이지의 일부분을 템플릿으로 정의하고, 이를 th:includeth:replace 등을 통해 다른 페이지에서 가져와 사용할 수 있습니다.


5. 20 th:fragment 예제 2

프래그먼트를 사용하려면 th:fragment 속성을 이용합니다.
th:fragment 속성을 지정한 요소 내의 자식 요소가 프래그먼트 대상이 됩니다. 속성에는 프래그먼트를 식별할 이름을 지정합니다.

<!-- 18: 프래그먼트를 정의 -->
<span th:fragment="one">하나</span>
<span th:fragment="two"></span>
<span th:fragment="three"></span>

프래그먼트를 삽입할 때는 th:insert 속성을 이용합니다.
파일명::이름의 왼쪽에 프래그먼트의 파일명을, 오른쪽에 th:fragment 속성에 앞에서 정의한 프래그먼트 이름을 지정합니다.
전체 내용을 프래그먼트로 완전히 변경하려면 th:replace 속성을 이용합니다.

<!-- 18: 프래그먼트 사용하기 -->
<h1>Fragment를 아래에 삽입하기</h1>
<div id="one" th:insert="fragment::one"></div>
<div id="two" th:insert="fragment::two"></div>

th:insert의 경우는 th:fragment를 부여한 태그의 자식 요소가 삽입됩니다.
th:replace의 경우에는 th:fragment를 부여한 태그 전체로 대체됩니다.


5.21 레이아웃

레이아웃화란 여러 템플릿에서 같은 디자인 레이아웃을 사용하는 경우 공통 레이아웃을 만들고 공유하는 것을 의미합니다. 레이아웃을 사용하려면 전용 라이브러리인 thymeleaf-layout-dialect가 필요합니다.

Thymeleaf Layout Dialect에서는 공통 레이아웃을 Decorator라 하고 공통 레이아웃을 사용하는 측을 Fragment라고 정의합니다. Thymeleaf Layout Dialect를 이용할 때의 단점으로는 전체 화면을 타임리프의 변환 처리 후에 확인할 수 있다는 것입니다.

타임리프의 템플릿 레이아수 기능을 사용하면ㄴ 코드의 중복도 줄어들고 공통 부분에 대한 변경도 간단하게 할 수 있습니다.

profile
필기하고, 타이핑하고, 말하면서 읽고, 코딩하고, 눈으로 읽고 오감으로 공부하기

0개의 댓글