본문 바로가기

Dev Log

Thymeleaf 개념 정리

728x90

백오피스 프로젝트를 진행하면서 **타임리프(Thymeleaf)**를 사용하게 되었다.

템플릿 엔진이란 무엇인지, 그리고 타임리프가 어떤 구조로 동작하는지 정리해보았다

🧩 템플릿 엔진이란?

템플릿 엔진(Template Engine) 은 서버에서 HTML을 동적으로 생성해주는 도구다. 단순히 HTML을 반환하는 것이 아니라, 서버에서 전달한 데이터를 템플릿 문법으로 바인딩하여 조건부 렌더링, 반복문, 데이터 출력 등을 가능하게 한다.

Spring Boot 환경에서는 JSP 대신 Thymeleaf 가 많이 사용된다.

💡 왜 JSP 대신 Thymeleaf를 사용할까?

JSP는 오래된 방식으로, 설정이 복잡하고 Spring Boot의 내장 톰캣 환경에서 불안정하게 동작한다.

 

JSP는 .jsp 파일을

서블릿(.java) 로 변환

컴파일 후 실행하면서 HTML을 생성

 

문제는 JAR 형태로 패키징된 Spring Boot 앱에서는 JSP 파일이 실제 파일 시스템이 아닌 압축된 리소스(JAR 내부) 로 존재한다는 점이다. JSP 엔진(톰캣)은 JSP 파일을 파일 경로 기반으로 접근해야 하는데, 압축된 상태에서는 이를 읽을 수 없게 되어 오류가 자주 발생한다.

즉, JAR 실행 구조와 JSP의 서블릿 변환·컴파일 메커니즘이 맞지 않다. 반면, Thymeleaf는 HTML을 파싱해서 서버 데이터를 바인딩하는 구조라 파일 시스템 접근 없이도 문제없이 작동한다.

 

정리:

  • 타임리프는
    • html 친화적 템플릿으로 레이아웃 프래그먼트 재사용이 가능함
    • Spring과 자연스럽게 연동 (Model, Security, Validation 등)
  • JSP는
    • 복잡한 설정: JSP는 내장 톰캣(Tomcat) 환경에서 동작이 불안정하고, jar 패키징 시 제대로 렌더링되지 않는 경우가 있다고 함

구조와 동작 방식

1. 의존성 설정 (build.gradle)

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
  • spring-boot-starter-thymeleaf: 타임리프 기본 엔진
  • thymeleaf-layout-dialect: 레이아웃 기능 (템플릿 조합)
  • thymeleaf-extras-springsecurity6: Spring Security 통합

2. 타임리프 설정 (application.yml)**application.yml

thymeleaf:
  cache: false
  prefix: classpath:/templates/
  mode: HTML
  encoding: UTF-8
  • cache: false: 개발 시 템플릿 캐시 비활성화
  • prefix: classpath:/templates/: 템플릿 파일 위치
  • mode: HTML: HTML 모드로 동작
  • encoding: UTF-8: UTF-8 인코딩

3. 컨트롤러에서 뷰 반환

@GetMapping (value = "/main/dashboard")
public String main(Model model, HttpServletRequest req, HttpServletResponse res) {
    return "pages/main/dashboard";
}
  • 컨트롤러 메서드가 문자열을 반환하면 타임리프가 해당 템플릿을 찾음
  • "pages/main/dashboard" → classpath:/templates/pages/main/dashboard.html

4.템플릿 구조

html인데 타임리프 문법적인게 포함된다.

<!DOCTYPE html>
<html lang="ko"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
>
  • xmlns:th: 타임리프 네임스페이스
  • xmlns:layout: 레이아웃 다이얼렉트 네임스페이스

페이지 템플릿(login.html)

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/single_layout}">

• layout:decorate="~{layouts/single_layout}": 레이아웃 템플릿 사용

타임리프 주요 기능들

실제 프로젝트에서 사용한 기능들은 다음과 같다.

 

1.Fragment 사용

<th:block th:replace="~{fragments/common_include :: common-css}"></th:block>
<th:block layout:fragment="css"></th:block>
  • th:replace: 다른 템플릿의 fragment를 가져와서 교체
  • layout:fragment: 레이아웃에서 사용할 수 있는 영역 정의

2. URL 생성

<h6><a th:href="@{/admin-view/login/resetPwd}">비밀번호 재발급</a><a th:href="@{/admin-view/login/joinTerms}">회원가입</a></h6>

• th:href="@{...}": URL을 동적으로 생성

 

3. 데이터 바인딩

<form id="loginCertForm" th:object="${loginRequest}" onsubmit="return false;">
<input th:field="*{authMobileNo}" class="txt-type-normal" placeholder="인증번호를 입력해 주세요.">
  • th:object="${loginRequest}": 폼 객체 바인딩
  • ${...}: 서버에서 전달된 데이터 참조

4. 자바스크립트 변수 주입

<script th:inline="javascript">
    state.rootPath = /*[[${@appProps.getWebRootPath()}]]*/ "/admin-view";
    /* 메뉴 권한 관련된 */
    state.menuAuth = {
        gnbSeq: /*[[${gnbSeq}]]*/ "",
        lnbSeq: /*[[${lnbSeq}]]*/ "",
        authMenuCode: /*[[${authMenuCode}]]*/ ""
    };
</script>

• th:inline="javascript": JavaScript에 서버 데이터 주입

동작 흐름 정리

  1. 요청 처리: 사용자가 URL 접근 (/admin-view/login)
  2. 컨트롤러 실행: LoginController.getLogin() 메서드 실행
  3. 모델 데이터 추가: model.addAttribute("newPwdRequest", new NewPwdRequest())
  4. 뷰 이름 반환: "pages/login/login"
  5. 템플릿 해석: 타임리프가 templates/pages/login/login.html 찾음
  6. 레이아웃 적용: layout:decorate="~{layouts/single_layout}"로 레이아웃 적용
  7. Fragment 처리: th:replace로 공통 요소들 삽입
  8. 데이터 바인딩: ${...}, *{...} 표현식으로 데이터 렌더링
  9. HTML 생성: 최종 HTML 생성하여 브라우저에 전송

핵심 개념

  • 템플릿 엔진: 서버에서 HTML을 동적으로 생성
  • Fragment: 재사용 가능한 템플릿 조각
  • Layout: 전체 페이지 구조를 정의하는 템플릿
  • 데이터 바인딩: 서버 데이터를 HTML에 연결
  • 표현식: ${...}, *{...}, @{...} 등으로 데이터 참조
728x90