타임 리프의 기본 기능
th:text , th:utext
<span th:text="${data}">
<span th:utext="${data}">
서버에서 전달된 데이터를 출력하고 싶을 때 타임리프는 th:text , th:utext를 쓴다
하지만 HTML 은 이스케이프가 존재한다
개발자는 th:text를 통해 <b>hellp</b> 를 출력하고 싶지만 실제 html이 < ,>를 만나면 특수문자로 인식해 다음과 같이 바꾼다
- < → $<
- > → >
이렇게 HTML에서 사용하는 특수문자를 Entity로 변환하는 것을 Escape라고 한다
그래서 utext를 사용하면 이런 Escape에 빠지지 않고 있는 문자 그대로 출력할 수 있다
SpringEL
타임리프에서 변수를 사용할 때는 변수 표현식을 사용한다
${...}
해당 변수 표현식을 통해서 Object(보통 Model) , List , Map , RequestParam에 접근할 수 있다
- Object
- ${user.username}
- ${user ['username']}
- ${user.getUsername()}
- List
- ${users [0]. username}
- ${users [0]['username']}
- ${users [0]. getUsername()}
- Map
- ${userMap ['userA']. username}
- ${userMap ['userA']['username']}
- ${userMap ['userA']. getUsername()}
참고
타임 리프에서 단순하게 ${data}를 쓰면 SpringEL에 우선순위에 따라 해당 변수를 탐색하기 시작한다
탐색의 우선순위는 다음과 같다
1. Request Attribute
2. Session Attribute
3. Application Attribute
4. Request Parameter
&{data}만 해도 우선순위를 따라서 알아서 찾게 할 수 있지만 우선순위를 타기 싫고 탐색범위를 지정할 수 있게 할 수 있다
${username} 모든 scope 뒤짐 ${param.username} request parameter만 봄 ${session.username} session만 봄
기본 객체들
원래는 parameter를 View template에 넘기려면 Model에 param을 넘기고 템플릿에서 랜더링 해야 하는데
타임 리프는 이러한 점을 해결하기 위해 편의 객체를 제공한다
- HTTP 요청 파라미터 접근 : param
- ${param.paramData}
- Http 세션 접근 session
- ${session.sessionData}
- 스프링 빈 접근 : @
- ${@빈이름. 빈메서드}
URL
1. 보통의 URL
타임리프에선 URL 링크를 생성할 때 @{...} 문법을 사용하면 된다
단순하게 URL 링크를 걸어주고 싶으면 다음과 같이 사용하면 된다
@{/hello/hi/my/name/}
2. 쿼리 파라미터의 URL
쿼리 파라미터를 추가하고 싶으면 다음과 같은 문법을 제공한다
@{/hello (param1=${param1}, param2=${param2}) }
추가하고 싶은 쿼리 파라미터가 있으면 URL 뒤에 (...) 안에 선언하면 된다
위에 예시의 최종 모습 다음과 같다
/hello/data1/data2
3. PathVariable의 URL
@{/hello/{param1}/{param2} (param1=${param1}, param2=${param2}) }
PathVariable은 Controller에서 사용하는 문법처럼 {}을 사용하여 URL을 구성하고
변수에 대한 정보는 (...) 안에 정의하면 된다
최종 모습은 다음과 같다
/hello/data1/data2
PathVariable + 쿼리파리미터의 URL
마지막으로 PathVariable과 쿼리파라미터를 같이 쓴 경우 다음과 같이 선언하면 된다
@{/hello/{param1}(param1=${param1}, param2=${param2})}
리터럴
타임 리프에서 문자열은 ''로 감싸야한다 예시를 보자면
<span th:text="'hello world'"></span>
이렇게 감싸야하는데 만약 문자와 변수를 같이 쓰고 싶다면 Java에 + 연산과 같이 다음과 같이 써야 한다
<span th:text="'hello ' + ${user.username}"></span>
이렇게 하면 불편하다.. 그래서 나온 게 Literal Substitution 문법이다 |.... |
<span th:text="|hello ${user.username}|"></span>
반복
th:each`
<tr th:each="user : ${users}">`
반복 문법 사용 시 오른쪽 컬렉션 (${users})의 값을 하나씩 꺼내서 왼쪽 변수 (user)에 담아서 태그를 반복 실행 한다
여기에 다양한 기능을 제공하는데 참고만 하고 나중에 사용할 일 있으면 매뉴얼을 보고 사용해 보자
참고
- index : 0부터 시작하는 값
- count : 1부터 시작하는 값
- size : 전체 사이즈
- even, odd : 홀수, 짝수 여부 (boolean)
- first , last : 처음 마지막 여부(boolean)
- current : 현재 객체
IF
th:if
if 안에 조건을 만족하면 해당 th:if 가 선언된 태그가 보이고
if 안에 조건을 만족하지 못하면 해당 태그 자체가 랜더링이 안된다 (사라진다)
블록
반복문을 할 때 태그마다 th:each를 걸어주면 너무 불편하다 그래서 반복할 대상을 하나의 Block으로 잡아주는 태그가 존재한다
<th:block th:each="user : ${users}">
<div>
사용자 이름1 <span th:text="${user.username}"></span>
사용자 나이1 <span th:text="${user.age}"></span>
</div>
<div>
요약 <span th:text="${user.username} + ' / ' + ${user.age}"></span>
</div>
</th:block>
th:block 은 유일하게 HTML 태그가 아닌 타임리프가 제공하는 유일한 자체 태그다
템플릿 조각(fragment)
웹 페이지를 개발할 때는 공통 영역이 많이 있다.
예를 들어서 상단 영역이나 하단 영역, 좌측 카테고리 등등 여러 페이지에서 함께 사용하는 영역들이 있다.
이런 부분을 코드를 복사해서 사용한다면 변경 시 여러 페이지를 다 수정해야 하므로 상당히 비효율 적이다.
타임리프는 이런 문제를 해결하기 위해 템플릿 조각과 레이아웃 기능을 지원한다
템플릿 조각 fragment를 가져오는 방법은 2가지가 있다
1. insert
<div th:insert="~{template/fragment/footer :: copy}"></div>
이렇게 가져오면 <div> </div> 태그 안쪽에 가져온 <footer></footer> 태그가 들어가게 된다
최종 모습은 다음과 같다
<div><footer>푸터 자리 입니다.</footer></div>
2. repalce
반면 Replace는 태그 안쪽에 껴 넣는 게 아니라 가져온 자리를 그냥 대체해 보인다
fragment를 가져올 때
<div th:replace="~{template/fragment/footer :: copy}"></div>
이렇게 가져오면 최종적인 모습은 다음과 같이
<div></div> 태그 자체를 교체해 버린다
템플릿 레이아웃
이전에 Fragment는 전체 모양(구조)은 내가 가지고 있고 Fragment를 통해 코드 조각을 가져다 사용하기 위함이었다면
템플릿 레이아웃은 반대로 전체 모양(구조)은 외부에 정해져 있고 그 모양에 맞춰서 내 코드를 껴 넣는 것!
타임 리프-스프링 통합
th:object
<form action="item.html" th:action th:object="${item}" method="post">
해당 Form 태그에서 사용할 바인딩 객체를 지정한다
해당 form에서 th:object를 item으로 잡았으므로 이렇게 선언한 form 안에서 다음과 같은 의미가 생긴다
*{itemName} = ${item.itemName}
th:field
th:field는 정말 많은 일은 한다 여기서는 그것의 일부만을 설명하겠지만 이후에 등장하는 th:field는 정말 많은 일을 해준다
여기서는 간단하게 다음과 같은 일을 한다고만 알고 있고 추후 알아보자
th:field="*{price}"
- name 자동생성
- id 자동생성
- value 자동출력
- 검증 오류시 사용자 입력값 복원(reject value)
- 타입별로 다르게 동작
- text → value 세팅
- checkbox → checked 자동처리, 히든 필드 자동 생성
- radio → 선택 상태 자동 처리
- select → 선택값 자동 selected 처리
체크 박스
HTML Checkbox의 문제점
- 체크 시 → open=on 전송됨
- 미체크 시 → 값 자체가 전송 안됨 (null)
HTML의 false 가 아니라 필드 자체가 그냥 사라진다
이를 위해 모든 요청을 항상 보내는 Hidden Field를 이용한다
<input type="hidden" name="_open" value="on">
만약 체크 박스에서 체크를 안 하면? open : "on"과 _open : "on" 둘 다 보내짐
체크 박스에서 체크를 하면?항상 보내지는 숨어 있는 태그 _open : "on"만 보내짐
_open:on 만 보내지면? Spring이 해당 값을 false로 바꾸고
open:on,_open:on 둘 다 보내지면? on을 true로 바꾸면 된다
하지만 이 모든 일을 타임 리프의 th:field 가 해결해 준다
<input type="checkbox" th:field="*{open}">
체크박스 - 멀티
멀티 박스에서 th:field를 사용하면 checked와 히든 필드를 자동 생성 해주는 기능을 가진다
<div>등록 지역</div>
<div th:each="region : ${regions}" class="form-check form-check-inline">
<input type="checkbox" th:field="${item.regions}" th:value="$
{region.key}" class="form-check-input" disabled>
<label th:for="${#ids.prev('regions')}"
th:text="${region.value}" class="form-check-label">서울</label>
</div>
th:field가 th:value 값과 th:field 값을 하나하나 비교해서 checked 상태를 설정해 준다
라디오 버튼
<div th:each="type : ${itemTypes}">
<input type="radio"
th:field="*{itemType}"
th:value="${type.name()}">
<label th:for="${#ids.prev('itemType')}"
th:text="${type.description}">
여기도 th:value 값과 th:field 값을 비교해서 선택 여부를 자동으로 처리해 준다
'🌿 스프링 > 스프링 MVC 2편' 카테고리의 다른 글
| 서블릿 예외 처리와 오류 페이지 (0) | 2026.02.15 |
|---|---|
| 서블릿 필터와 스블릿 인터셉터 (0) | 2026.02.15 |
| 로그인 처리- 쿠키,세션 (0) | 2026.02.14 |
| Validation (0) | 2026.02.14 |
| 메시지 , 국제화 기능 (0) | 2026.02.14 |