타임리프

예지성준·2024년 7월 18일

스프링프레임워크

목록 보기
14/14
post-thumbnail

타임리프(Thymeleaf)

Expression Basic Objects

Expression Utility Object

  • 뷰 템플릿이라고 부름
    • 컨트롤러가 전달하는 데이터를 이용하여 동적으로 화면을 구성할 수 있게 해줌

🎈JSP와 Thymeleaf의 차이점

기존 MVC 패턴 방식에는 JSP를 사용하였다.

  • JSP 의 경우에는 서블릿 이라는 형태로 변환되어 실행이 된다.

서블릿이 자바 소스이다 보니 HTML 코드에서 JAVA 코드를 넣어 동적 웹페이지를 구성한다.

  • Thymeleaf는 HTML, JS, CSS 등을 처리할 수 있는 웹 및 독립형 환경에서 사용이 가능한 java 템플릿 엔진이다.
    -> 서블릿으로 변환 X

1. 설정

의존성 추가
🔖thymeleaf-spring6

🔖thymeleaf - java8time // JDK8 Date & TIME API
-> #temporals : 형식화

타임리프는 레이아웃 기능이 없다.
🔖thymeleaf layout :레이아웃 기능 추가

implementation 'org.thymeleaf:thymeleaf-spring6:3.1.2.RELEASE'
implementation 'org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.4.RELEASE'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.3.0'

2. 특징

  • Natural Template

    • 타임리프로 작성한 파일은 HTML을 유지하기 때문에 웹 브라우저에서 파일을 직접 열어도 내용을 확인할 수 있고, 서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과를 확인할 수 있다.

    참고로 jsp를 포함한 뷰 템플릿에서는 jsp코드와 html코드가 섞여있다.

  • 원래 HTML과 서버사이드 렌더링 결과 거의 동일하게 보이는 효과

  • 번역 기술

  • 캐시 기능 제공

캐시 비활성화 (setCacheable(false))

  • 목적: 개발 중에는 템플릿 파일을 자주 수정하고, 그 변경 사항을 즉시 확인하고자 합니다. 이 설정은 서버를 재시작하지 않고도 HTML 파일의 변경 사항이 즉시 반영되도록 합니다.

  • 동작: 이 설정을 적용하면, 각 요청 시마다 템플릿 파일이 다시 로드됩니다. 즉, 변경된 템플릿 파일을 서버가 매번 읽어와서 최신 상태를 반영합니다.

캐시 활성화 (setCacheable(true)) //배포시

  • 목적: 배포 환경에서는 템플릿 파일이 자주 변경되지 않으며, 성능 최적화가 중요합니다. 이 설정은 템플릿 파일을 캐시에 저장하여, 서버 성능을 최적화합니다.

  • 동작: 이 설정을 적용하면, 템플릿 파일이 최초 요청 시에만 로드되고, 이후 요청부터는 캐시된 버전을 사용합니다. 따라서 변경된 템플릿 파일을 반영하려면 서버를 재시작해야 합니다.

    • 템플릿 파일이 변경되더라도 서버를 재시작하지 않는 한 변경 사항이 반영되지 않는다.

개발 중에는 templateResolver.setCacheable(false)로 설정하여, 템플릿 파일이 변경될 때마다 다시 로드되도록 한다. 배포 중에는 templateResolver.setCacheable(true)로 설정

한글 안깨진다!

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>회원가입</title>
    </head>
    <body>
        <h1>회원가입</h1>
        <form method="post" autocomplete="off">
            <dl>
                <dt>이메일</dt>
                <dd>
                    <input type="text" name="email" value="user01@test.org">
                </dd>
            </dl>
            <dl>
                <dt>비밀번호</dt>
                <dd>
                    <input type="password" name="password">
                </dd>
            </dl>
            <dl>
                <dt>비밀번호 확인</dt>
                <dd>
                    <input type="password" name="confirmPassword">
                </dd>
            </dl>
            <dl>
                <dt>회원명</dt>
                <dd>
                    <input type="text" name="userName" value="사용자01">
                </dd>
            </dl>
            <div>
                <input type="checkbox" name="agree" value="true" id="agree">
                <label for="agree">회원가입 약관에 동의합니다.</label>
            </div>
            <button type="submit">가입하기</button>
        </form>
    </body>
</html>

문서에서 파일 그대로 넣어도 똑같이 동작한다
디자이너가 건내준 파일 그 자체를 로드할 수 있다!

타임리프 기본문법


👩‍🏫태그 알고가기
dl, dt, dd
dl - Definition List : 정의 목록 (용어 - 정의 쌍)
dt - Definition Term : 용어
dd - Definition Description : 용어에 대한 정의


타임리프는 기존 디자인 파일 그대로 데이터와 틀을 유지 하면서 번역기술을 사용할 수 있다.

  • 타임리프는 번역하기 위한 네임스페이스가 필요하다.

기존값 그대로 유지하면서 태그에 속성 추가한다. 서버가 번역할때 이 값(th:text)으로 교체함

th가 붙은 속성 -> 번역이 되는 속성

원본 그대로 유지!
  • 서버측에서는 th 태그를 사용해서 동적으로 내용을 변경할 수 있다. th 속성을 기반으로 데이터를 주입하거나 조건에 따라 html 내용을 동적으로 생성함

  • 클라이언트 측에서는 th속성이 포함된 원본 html을 그대로 볼 수 있다. 그렇기 때문에 디자이너와 개발자가 같은 파일을 사용하더라도 문제없이 작업 할 수 있다.

참고) jsp파일은 서버에서 최초 요청 시 서블릿으로 컴파일됩니다. 이후에는 이 컴파일된 서블릿이 클라이언트의 요청을 처리합니다. JSP 파일이 수정되면, 서버는 이를 감지하여 다시 컴파일한다.
-> 서버 측에서 자바 코드로 변환되어 실행되기 때문에, 별도의 캐시 설정 없이도 변경 사항이 즉시 반영


1. 타임리프의 주요 식(expression)

1) 변수 식

${식...}

2) 메세지 식

#{메세지 코드}

👩‍💻참고)
fmt:setBundle
<fmt:message key="메세지 코드">

3) 링크 식

@{링크}

  • 자동으로 앞에 컨텍스트 경로 추가
  • URL 변수 식, 요청 파라미터 쉽게 추가

👩‍💻참고)<c:url value="...." />

4) 선택 변수식

th:object="${객체}"

*{속성명}

th:text="*{속성명}" //${객체.속성명}

  • th:object 속성은 특정 객체를 선택하는데 선택 변수식은 th:object로 선택한 객체를 기준으로 나머지 경로를 값으로 사용한다.
<div th:object="${member}">
	<span th:text="*{name}">name</span>
</div>
  • *{name}은 <div> 태그의 th:object에서 선택한 member 객체를 기준으로 name경로를 선택한다. 따라서 *{name}은 ${member.name}과 같은 경로가 된다.

th:block 태그
-> 번역되면 삭제
-> 기능만 필요한 경우
-> 출력되지 않고 값만 유지

값만 유지하고 번역되고 나면 제거됨

🔸 회원정보 출력 예시

MemberController

예시데이터 작성

🔹변수식

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org"> 
<!--번역하기 위한 시그니처 네임스페이스 입력해야함-->
    <body>
        <h1>회원정보</h1>
        <dl>
            <dt>이메일</dt>
            <dd th:text="${member.email}"></dd>
            <!--타임리프는 태그 내부 작성이 아닌 속성으로 입력해야함 내용이 있어도 번역될때 th에 지정한 값으로 대체가 된다.-->
        </dl>
        <dl>
            <dt>회원명</dt>
            <dd th:text="${member.userName}"></dd>
        </dl>
        <dl>
            <dt>가입일시</dt>
            <dd th:text="${member.regDt}"></dd>
        </dl>
    </body>
</html>

el식과 동일하게 간단한 연산과 간단한 메서드 호출 가능

🔹선택변수식 - 더 짧게 쓸 수 있는 문법

<body>
        <h1>회원정보</h1>
        <div th:object="${member}">
            <dl>
                <dt>이메일</dt>
                <!--<dd th:text="${member.email}"></dd>-->
                <dd th:text="*{email}"></dd>
            </dl>
            <dl>
                <dt>회원명</dt>
<!--                <dd th:text="${member.userName}"></dd>-->
                <dd th:text="*{userName}"></dd>
            </dl>
            <dl>
                <dt>가입일시</dt>
<!--                <dd th:text="${member.regDt}"></dd>-->
                <dd th:text="*{regDt}"></dd>
            </dl>
        </div>
    </body>

🔹 메시지식

<body>
        <h1>회원정보</h1>
        <div th:object="${member}">
            <dl>
                <dt th:text="#{이메일}">이메일</dt>
                <dd th:text="*{email}"></dd>
            </dl>
            <dl>
                <dt th:text="#{회원명}">회원명</dt>

                <dd th:text="*{userName}"></dd>
            </dl>
            <dl>
                <dt th:text="#{가입일시}">가입일시</dt>

                <dd th:text="*{regDt}"></dd>
            </dl>
        </div>
    </body>

🔹 링크식

같은 속성이 있으면 th 값을 # 대신 교체해줌

href="#" 기존속성 제거해줘도 문제 없다.

th block 안에 있지 않아도 동작할 수 있다 대신 ${}로 member.email 이 형식으로 해줘야함

🔽

앞에 이름으로 위치 찾고 뒤에 *{}로 값 교체

두개 교체도 가능함


주의📌 th가 없으면 번역해주지 않는다!!!!!!


2. 타임리프 식 객체

  • 기본 객체
    #ctx
    .response
    .request
    .session
    .servletContext

#locale

  • java.util.Locale

context

  • 요청 파라미터
    • param
      map 형태 속성 데이터
    • session, application...

#session - jakarta.servlet.http.HttpSession
#request - jakarta.servlet.http.HttpServletRequest
#response - jakarta.servlet.http.HttpServletResponse

  • 편의 객체
    #temporals: java.time패키지 관련 형식화 및 편의 메서드가 정의된 식 객체

${@빈이름.메서드명()}
*{@빈이름.메서드명()}

1) #strings

🔼문자열 결합 Strings.concat

2) #numbers

숫자에 대한 형식화

순서대로 범위 내에서 만들때 사용

증감단위는 세번째 있는 매개변수의 단위

3) #dates, #calendars, #temporals : (java 8, java.time 패키지)

🔼제일 많이 쓰는 형태 식객체로 날짜 형식화!

4) #lists, #sets, #maps

-> 내장 식객체에 없는 기능? 스프링 빈으로 생성

${@빈이름.메서드명(...)}


3. th:text

  • 문자열 출력 (문자열만!! - HTML 태그는 해석 X)
  • th:utext - HTML 태그도 해석 O

HTML 태그는 해석되지 않아서 문자 그대로 출력한다.
🐣 th:utext 로 해야함!!

🔽

  • 기본 : 속성을 통해서 번역
  • [[${.. }]] : 태그 안쪽에서도 출력 / 문자열만 인식, HTML 태그는 해석 X

4. th:each

👩‍🏫태그 알고가기
<table> : 테이블 생성을 위한 컨테이너 태그
<thead> : 테이블 머리글부분 정의( 테이블 헤더 행)
<tbody> : 테이블 본문 정의 ( 실제 데이터 행 포함)

  • 반복문

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <body>
        <table border="1">
            <thead>
                <tr>
                    <th>이메일</th>
                    <th>회원명</th>
                    <th>가입일시</th>
                </tr>
            </thead>
            <tbody>
                <tr th:each="item : ${items}">
                    <td th:text="${item.email}"></td>
                    <td th:text="${item.userName}"></td>
                    <td th:text="${item.regDt}"></td>
                </tr>
            </tbody>
        </table>
    </body>
</html>

<c:forEach ... varStatus="status">

📌순번에 관련된 객체

  • status
    • index : 0부터 시작하는 순서 번호
    • count : 1부터 시작하는 순서 번호
    • first : 첫번째 행 여부
    • last : 마지막 행 여부
    • even : 짝수 행 여부
    • odd : 홀수 행 여부
 <tbody>
     <tr th:each="item, status : ${items}" th:object="${item}">
     <td th:text="*{email}"></td>
     <td th:text="*{userName}"></td>
     <td th:text="*{regDt}"></td>
     </tr>
</tbody>

🔽 상태값 활용 🔽

status는 단지 변수명이기 때문에 굳이 status로 안쓰고 s로 바꿔서 짧게 써도 된다.
status는 *{..} 불가..

5. th:if, th:unless

th:if : 조건식
th:if="${....}"

th:unless="${...}" : 조건식이 false -> 노출, true -> 노출 X

조건식이 참일때 노출
<th:block th:if="${s.even}">짝수</th:block>
조건식이 거짓일때 노출
<th:block th:unless="${s.even}">홀수</th:block>

true, false -> 상수로 바로 인식

6. th:switch, th:case

노출 여부 결정 가능

7. th:href, src, action

th:href

th:src

th:action

8. th:object

스프링 MVC 폼과 에러 메시지 연동

1. #fields.errors(..)

  • 특정 필드에 한정한 오류 출력(email, password ...)
    • 커맨드 객체 있는 애노테이션 검증(@NotBlank)
    • Errors::rejectValue("필드명","에러코드")
  • 다만 global 필드이면 global 에러를 출력한다.

2. #fields.globalErrors(..)

  • Global Error 출력 용도

  • Errors::reject("에러코드")

👩‍🏫 참고) <form:errors path=".."/> 와 동일한 역할
#이 붙어있는 경우 -> 식 객체(내장객체)

9. th:classappend

  • 클래스를 조건에 따라 추가 제거하는 문법

  • th:classappend="${조건식 ? '참일때 추가될 클래스명':'거짓일때 추가될 클래스명'}"

  • th:classappend= ${조건식} ? "참일때":"거짓일때"

10. th:field

th:field="*{속성명}

join.html

...

requestJoin은 선택 변수 형태로..

value 속성 쓰지말고 th:field 사용하자!

🔸 회원가입 form 에러 내용 추가

에러메시지가 여러개일 수 있기 때문에 each 사용

🔸로그인 폼

<html xmlns:th="http://www.thymeleaf.org">
    <body>
        <form method="post" th:action="@{/member/login}" autocomplete="off"
            th:object="${requestLogin}">
            <dl>
                <dt th:text="#{이메일}"></dt>
                <dd>
                    <input type="text" name="email" th:field="*{email}">
                    <div th:each="err : ${#fields.errors('email')}" th:text="${err}"></div>
                </dd>
            </dl>
            <dl>
                <dt th:text="#{비밀번호}"></dt>
                <dd>
                    <input type="password" name="password" th:field="*{password}">
                    <div th:each="err : ${#fields.errors('password')}"
                         th:text="${err}"></div>
                </dd>
            </dl>
            <div>
                <input type="checkbox" name="saveEmail" value="true" id="saveEmail"
                th:field="*{saveEmail}">
                <label for="saveEmail" th:text="#{이메일_기억하기}"></label>
            </div>
            
<!--            <div th:each="err : ${#fields.errors('global')}" th:text="${err}"></div>-->
            <div th:each="err : ${#fields.globalErrors()}" th:text="${err}"></div>
            <button type="submit" th:text="#{로그인}"></button>
        </form>
    </body>
</html>

타임리프 페이지 레이아웃

th:replace : 템플릿 파일 치환

th:fragment

templates2 - main - index.html

templates2가 기준 경로임

header의 common쪽으로 내용 치환

footer의 common쪽으로 내용 치환

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
        <header th:replace="~{outlines/header::common}"></header>
        <main>
            <h1>
                내용..!!
            </h1>
        </main>
        <footer th:replace="~{outlines/footer::common}"></footer>
    </body>
</html>

📌공통기능 빼고 출력되는 내용만 치환될 수 있게끔 레이아웃 기능 구성해주자

🔼공통 레이아웃 구성할 파일

공통 레이아웃

바뀌는 내용 부분은 layout이 치환해준다

  • 레이아웃 적용하기

index.html

동일한 이름으로 하게되면 해당 영역을 찾아서 치환하게 된다.

index.html의 main layout 부분 내용이 main.html의 main layout 태그 안에 내용으로 치환됨


공통 css적용

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
        xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<!--네임스페이스를 통해서 별도로 번역-->
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" type="text/css" th:href="@{/css/style.css}">
        <!--th가 있어야 링크식 형태로 번역됨-->

        <th:block th:if="addCss != null"}>
            <link rel="stylesheet" type="text/css" th:each="cssFile: ${addCss}"
                  th:href="@{/css/{file}.css(file=${cssFile})}">
        </th:block>

        <script th:src="@{/js/common.js}"></script>
        <th:block th:if="${addScript != null}">
            <script th:each="jsFile : ${addScript}"
                    th:src="@{/js/{file}.js(file=${jsFile})}"></script>
        </th:block>
    </head>
    <body><!--헤더 푸터 고정-->
        <header th:replace="~{outlines/header::common}"></header>
             <!--내용 교체는 xmlns:layout이 해줌-->
            <main layout:fragment="content"></main>
        <footer th:replace="~{outlines/footer::common}"></footer>
    </body>
</html>

MemberController

레이아웃 추가

body쪽 태그 지우고 main 태그로 교체


📢 메인페이지는 Model로 속성을 추가할 addCss, addScript를 사용할 수 없다. 모델 주입하려면 컨트롤러 메서드에 주입해야해야하는데(model.addAttrubute) 메인페이지는 컨트롤러 없이 만든거라 사용할 수 없음! (주소에 바로 템플릿 뷰 연동함)

addCss, addScript를 정의하지 못하는 상황을 대비해서 코드 추가

명칭을 가지고 내부 내용 치환

컨트롤러 없이 사용되는 뷰의 경우에는 내용 치환영역을 더 잡아주면 된다.


iframe -> 페이지 이동 없이 한 페이지 내에서 처리할때 사용

레이아웃쪽에 추가하기
main.html

profile
꽁꽁 얼어붙은 한강 위로 😺

0개의 댓글