on
타임리프 - 기본 기능
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술을 공부하고 정리하는 포스트입니다.
타임리프
특징
- 서버 사이드 HTML 렌더링 (SSR)
- 내츄럴 템플릿
- 스프링 통합 지원
서버 사이드 HTML 렌더링 (SSR)
- 타임리프는 백엔드 서버에서 HTML을 동적으로 렌더링 하는 용도로 사용됨.
내츄럴 템플릿
- 순수 HTML을 최대한 유지
- HTML 유지 -> 웹 브라우저에서 파일을 직접 열어서 확인 가능, 서버를 통해 뷰 템플릿을 거쳐 동적으로 변경된 결과를 확인 가능
- JSP를 포함한 다른 뷰 템플릿은 해당 파일을 열면 웹 브라우저에서 정상적인 결과 확인 불가능 -> JSP 코드와 HTML이 섞이기 때문
- 이처럼 순수 HTML을 유지하면서 뷰 템플릿도 사용할 수 있는 특징을 내츄럴 템플릿이라고 함
스프링 통합 지원
- 스프링과 자연스럽게 통합, 다양한 기능을 편리하게 사용할 수 있게 지원함.
기본 기능
- 타임리프 사용 선언 :
<html xmlns:th="http://www.thymeleaf.org">
기본 표현식
- 간단한 표현
- 변수 표현식 :
${...}
- 선택 변수 표현식 :
*{...}
- 메시지 표현식 :
#{...}
- 링크 URL 표현식 :
@{...}
- 조각 표현식 :
~{...}
- 변수 표현식 :
- 리터럴
- 텍스트 : ‘one text’, ‘Another one!’, …
- 숫자 : 0, 34, 3.0, 12.3, …
- 불린 : true, false
- 널 : null
- 리터럴 토큰 : one, sometext, main, …
- 문자 연산
- 문자 합치기 :
+
- 리터럴 대체 :
|The name is ${name}|
- 문자 합치기 :
- 산술 연산
- Binary operators :
+, -, *, /, %
- Minus sign(unary operator) :
-
- Binary operators :
- 불린 연산
- Binary operators : and, or
- Boolean negation(unary operator) : !, not
- 비교와 동등
- 비교 :
>, <, >=, <=
(gt, lt, ge, le) - 동등 연산 :
==, !=
(eq, ne)
- 비교 :
- 조건 연산
- If-then : (if) ? (then)
- If-then-else : (if) ? (then) : (else)
- Default : (value) ? : (default value)
- 특별한 토큰
- No-Operation:
_
- No-Operation:
텍스트 - text, utext
- 타임리프는 기본적으로 HTML 태그의 속성에 기능을 정의해서 동작함
- HTML의 콘텐츠에 데이터를 출력할 때 :
<span th:text="${data}">
- 콘텐츠 영역 안에 직접 출력할 때 :
[[${data}]]
- HTML의 콘텐츠에 데이터를 출력할 때 :
Escape
- HTML은
<
,>
같은 특수 문자를 기반으로 정의됨 -> 뷰 템플릿으로 HTML 화면을 생성할 때 특수 문자에 주의
HTML 엔티티
- 웹 브라우저는
<
를 HTML 태그의 시작으로 인식함 -><
를 문자로 표현하는 방법이 HTML 엔티티 -
특수문자를 HTML 엔티티로 변경하는 것을 이스케이프(escape)라고 함.
- 타임리프가 제공하는
th:text
,[[...]]
는 기본적으로 이스케이프를 제공함.
Unescape
- 이스케이프를 사용하지 않고 싶을 때 타임리프는 다음 두 기능을 제공함
th:text
->th:utext
[[...]]
->[(...)]
변수 - SpringEL
- 변수를 사용할 때는 변수 표현식을 사용 :
${...}
- SpringEL이라는 스프링이 제공하는 표현식을 사용할 수 있음
SpringEL 다양한 표현식 사용
- Object
user.username
: user의 username을 프로퍼티 접근 -> user.getUsername()user['username']
: 위와 같음 -> user.getUsername()user.getUsername()
: user의 getUsername() 을 직접 호출
- List
users[0].username
: List에서 첫 번째 회원을 찾고 username 프로퍼티 접근 -> list.get(0).getUsername()users[0]['username']
: 위와 같음users[0].getUsername()
: List에서 첫 번째 회원을 찾고 메서드 직접 호출
- Map
userMap['userA'].username
: Map에서 userA를 찾고, username 프로퍼티 접근 -> map.get(“userA”).getUsername()userMap['userA']['username']
: 위와 같음userMap['userA'].getUsername()
: Map에서 userA를 찾고 메서드 직접 호출
지역 변수 선언
th:with
를 사용하면 지역 변수를 선언해서 사용 가능- 지역 변수는 선언한 태그 안에서만 사용할 수 있음
기본 객체들
- 다음 기본 객체들을 제공함
${#request}
${#response}
${#session}
${#servletContext}
${#locale}
${#request}
는HttpServletRequest
객체가 그대로 제공되기 때문에 데이터를 조회하려면request.getParameter("data")
와 같이 불편하게 접근해야 함 -> 이를 해결하는 것이 편의 객체- HTTP 요청 파라미터 접근 :
Param
-> ex)${param.paramData}
- HTTP 세션 접근 :
session
-> ex)${session.sessionData}
- 스프링 빈 접근 :
@
-> ex)${@helloBean.hello('Spring!')}
- HTTP 요청 파라미터 접근 :
유틸리티 객체와 날짜
- 타임리프 유틸리티 객체
#message
: 메시지, 국제화 처리#uris
: URI 이스케이프 지원#dates
: java.util.Date 서식 지원#calendars
: java.util.Calendar 서식 지원#temporals
: 자바8 날짜 서식 지원#numbers
: 숫자 서식 지원#strings
: 문자 관련 편의 기능#objects
: 객체 관련 기능 제공#bools
: boolean 관련 기능 제공#arrays
: 배열 관련 기능 제공#lists
,#sets
,#maps
: 컬렉션 관련 기능 제공#ids
: 아이디 처리 관련 기능 제공, 뒤에서 설명
- 타임리프 유틸리티 객체
- https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#expression-utilityobjects
- 유틸리티 객체 예시
- https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#appendix-b-expressionutility-objects
자바8 날짜
- 타임리프에서 자바8 날짜인
LocalDate
,LocalDateTime
,Instant
를 사용하려면 추가 라이브러리 필요- 타임리프 자바8 날짜 지원 라이브러리 :
thymeleaf-extras-java8time
- 타임리프 자바8 날짜 지원 라이브러리 :
- 자바8 날짜용 유틸리티 객체 :
#temporals
- 사용 예시
<span th:text="${#temporals.format(localDateTime, 'yyyy-MM-dd HH:mm:ss')}"></span>
URL 링크
- 단순한 URL :
@{/hello} /hello
- 쿼리 파라미터 :
@{/hello(param1=${param1}, param2=${param2})}
->/hello?param1=data1¶m2=data2
- ()에 있는 부분은 쿼리 파라미터로 처리.
- 경로 변수 :
@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}
-> /hello/data1/data2- URL 경로상에 변수가 있으면 ()부분은 경로 변수로 처리.
- 경로 변수 + 쿼리 파라미터 :
@{/hello/{param1}(param1=${param1}, param2=${param2})}
-> /hello/data1?param2=data2- 경로 변수와 쿼리 파라미터를 함께 사용.
- 상대경로, 절대경로, 프로토콜 기준을 표현.
/hello
: 절대 경로hello
: 상대 경로
리터럴
- 리터럴 : 소스 코드 상에 고정된 값
- 문자 :
'hello'
- 숫자 :
10
- 불린 :
true
,false
- null :
null
- 문자 :
- 타임리프에서 문자 리터럴은 항상
'
(작은 따옴표)로 감싸야 함 -><span th:text="'hello'">
- 문자를 항상
'
로 감싸는 것은 귀찮음 -> 공백 없이 쭉 이어진다면 하나의 의미있는 토큰으로 인지해서 작은 따옴표를 생략할 수 있음 - 룰 :
A-Z
,a-z
,0-9
,[]
,.
,-
,_
-><span th:text="hello">
- 문자를 항상
- 오류
<span th:text="hello world!"></span>
- 문자 리터럴은 원칙 상
'
로 감싸야 함. 중간에 공백이 있어서 하나의 의미있는 토큰으로도 인식되지 않음.
- 문자 리터럴은 원칙 상
- 수정
<span th:text="'hello world!'"></span>
'
로 감싸면 정상 동작.
리터럴 대체(Literal substitutions)
<span th:text="|hello ${data}|">
- 리터럴 대체 문법을 사용하면 마치 템플릿을 사용하는 것 처럼 편리.
연산
- 비교연산 : HTML 엔티티를 사용해야 하는 부분을 주의
> (gt)
,< (lt)
,>= (ge)
,<= (le)
,! (not)
,== (eq)
,!= (neq, ne)
- 조건식 : 자바의 조건식과 유사.
- Elvis 연산자 : 조건식의 편의 버전
- No-Operation :
_
인 경우 마치 타임리프가 실행되지 않는 것 처럼 동작.- 이것을 잘 사용하면 HTML의 내용 그대로 활용할 수 있음.
속성 값 설정
속성 설정
th:*
속성을 지정하면 타임리프는 기존 속성을th:*
로 지정한 속성으로 대체, 기존 속성이 없으면 새로 생성<input type="text" name="mock" th:name="userA" />
-> 타임리프 렌더링 후<input type="text" name="userA" />
속성 추가
th:attrappend
: 속성 값의 뒤에 값을 추가th:attrprepend
: 속성 값의 앞에 값을 추가th:classappend
: class 속성에 자연스럽게 추가
checked 처리
-
HTML에서는
<input type="checkbox" name="active" checked="false" />
-> 이 경우에도 checked 속성이 있기 때문에 checked 처리 -> HTML에서checked
속성은checked
속성의 값과 상관없이checked
라는 속성만 있어도 체크. 이런 부분이true
,false
값을 주로 사용하는 개발자 입장에서는 불편. -
타임리프의
th:checked
는 값이 false 인 경우 checked 속성 자체를 제거<input type="checkbox" name="active" th:checked="false" />
-> 타임리프 렌더링 후 :<input type="checkbox" name="active" />
반복
- 반복 기능 :
<tr th:each="user : ${users}">
- 반복 시 오른쪽 컬렉션(
${users}
)의 값을 하나씩 꺼내서 왼쪽 변수(user
)에 담아서 태그를 반복 실행 th:each
는 List 뿐만 아니라 ‘배열’, ‘java.util.Iterable’, ‘java.util.Enumeration’을 구현한 모든 객체를 반복에 사용할 수 있음.- ‘Map’도 사용 할 수 있는데 이 경우 변수에 담기는 값은
Map.Entry
- 반복 시 오른쪽 컬렉션(
- 반복 상태 유지 :
<tr th:each="user, userStat : ${users}">
- 반복의 두번째 파라미터를 설정해서 반복의 상태를 확인
- 두번째 파라미터는 생략 가능, 생략하면 지정한 ‘변수명(user) + Stat’가 됨. 여기서는 ‘user + Stat = userStat’이므로 생략 가능
- 반복 상태 유지 기능
index
: 0부터 시작하는 값count
: 1부터 시작하는 값size
: 전체 사이즈even
,odd
: 홀수, 짝수 여부( boolean )first
,last
:처음, 마지막 여부( boolean )current
: 현재 객체
조건부 평가
- if, unless
- 타임리프는 해당 조건이 맞지 않으면 태그 자체를 렌더링하지 않음
- 만약 다음 조건이
false
인 경우<span>...<span>
부분 자체가 렌더링 되지 않고 사라짐 -><span th:text="'미성년자'" th:if="${user.age lt 20}"></span>
- switch
*
은 만족하는 조건이 없을 때 사용하는 디폴트
주석
- 표준 HTML 주석 :
<!-- -->
- 자바스크립트의 표준 HTML 주석은 타임리프가 렌더링 하지 않고, 그대로 남겨둠
- 타임리프 파서 주석 :
<!--/* */-->
- 타임리프 파서 주석은 타임리프의 진짜 주석. 렌더링에서 주석 부분을 제거.
- 타임리프 프로토타입 주석 :
<!--/*/ /*/-->
- 타임리프 프로토타입은 약간 특이한데, HTML 주석에 약간의 구문을 더함.
- HTML 파일을 웹 브라우저에서 그대로 열어보면 HTML 주석이기 때문에 이 부분이 웹 브라우저가 렌더링하지 않음.
- 타임리프 렌더링을 거치면 이 부분이 정상 렌더링 됨
- 쉽게 이야기해서 HTML 파일을 그대로 열어보면 주석처리가 되지만, 타임리프를 렌더링 한 경우에는 보이는 기능.
블록
-
<th:block>
은 HTML 태그가 아닌 유일한 타임리프 자체 태그 -
타임리프의 특성상 HTML 태그 안에 속성으로 기능을 정의해서 사용 -> 따라서, 애매한 경우에 사용하면 됨.
<th:block>
은 렌더링 시 제거
자바스크립트 인라인
- 자바스크립트에서 타임리프를 편리하게 사용할 수 있는 기능
<script th:inline="javascript">
텍스트 렌더링
var username = [[${user.username}]];
- 인라인 사용 전 ->
var username = userA;
- 인라인 사용 후 ->
var username = "userA";
- 인라인 사용 전 ->
- 사용 전에는 변수 이름이 그대로 출력. 자바스크립트 오류 발생
-> 사용 후 문자 타입은
"
로 포함. 문제가 될 수 있는 문자는 이스케이프 처리.
자바스크립트 내추럴 템플릿
-
HTML 파일을 직접 열어도 동작하는 내추럴 템플릿 기능을 제공
var username2 = /*[[${user.username}]]*/ "test username";
- 인라인 사용 전 ->
var username2 = /*userA*/ "test username";
- 인라인 사용 후 ->
var username2 = "userA";
- 인라인 사용 전 ->
- 사용 전에는 순수하게 해석. 기능이 동작하지 않고, 렌더링 내용이 주석처리 됨 -> 사용 후 주석 부분은 제거. 기대 값이 출력됨.
객체
-
객체를 JSON으로 자동으로 변환
var user = [[${user}]];
- 인라인 사용 전 ->
var user = BasicController.User(username=userA, age=10);
- 인라인 사용 후 ->
var user = {"username":"userA","age":10};
- 인라인 사용 전 ->
- 사용 전은 객체의 toString()이 호출된 값 -> 사용 후는 객체를 JSON으로 변환
자바스크립트 each
- 인라인에서 each를 지원
[# th:each="user, stat : ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]
템플릿 조각
-
웹 페이지에는 공통 영역이 많음. 이러한 부분을 변경 시 다 수정하는 것은 비효율적 -> 템플릿 조각과 레이아웃 기능 지원
-
th:fragment
가 있는 태그는 다른 곳에 포함되는 코드 조각 -
template/fragment/footer :: copy
- ‘template/fragment/footer.html’ 템플릿에 있는
th:fragment="copy"
라는 부분을 템플릿 조각으로 가져와서 사용한다는 의미
- ‘template/fragment/footer.html’ 템플릿에 있는
부분 포함 insert
<div th:insert="~{template/fragment/footer :: copy}"></div>
th:insert
를 사용하면 현재 태그(div
) 내부에 추가
부분 포함 replace
<div th:replace="~{template/fragment/footer :: copy}"></div>
th:replace
를 사용하면 현재 태그(div
)를 대체
부분 포함 단순 표현식
<div th:replace="template/fragment/footer :: copy"></div>
~{...}
를 사용하는 것이 원칙. 단, 템플릿 조각을 사용하는 코드가 단순하면 생략 가능
파라미터 사용
- 파라미터를 전달하여 동적으로 조각을 렌더링
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터2')}"></div>
템플릿 레이아웃
-
<head>
에 공통으로 사용하는 css, javascript 같은 정보들이 있음. 이러한 공통 정보들을 한 곳에 모아두고, 공통으로 사용. -
common_header(~{::title},~{::link})
::title
은 현재 페이지의 title 태그들을 전달::link
는 현재 페이지의 link 태그들을 전달
템플릿 레이아웃 확장
<head>
에만 적용하는게 아니라<html>
전체에 적용 가능
Comments
SPRING 의 다른 글
-
스프링 타입 컨터버 24 Jun 2022
-
API 예외 처리 17 Jun 2022
-
예외 처리와 오류 페이지 12 Jun 2022
-
로그인 처리 - 인터셉터 08 Jun 2022
-
로그인 처리 - 필터 06 Jun 2022
-
로그인 처리 - 쿠키, 세션 31 May 2022
-
Bean Validation 22 May 2022
-
검증 22 May 2022
-
메시지, 국제화 21 May 2022
-
타임리프 - 스프링 통합과 폼 19 May 2022
-
타임리프 - 기본 기능 10 May 2022
-
스프링 MVC 기본 기능 - 웹 페이지 만들기 02 May 2022
-
스프링 MVC 기본 기능 - HTTP 응답 30 Apr 2022
-
스프링 MVC 기본 기능 - HTTP 요청 24 Apr 2022
-
스프링 MVC 기본 기능 - 요청 매핑 19 Apr 2022
-
스프링 MVC 기본 기능 19 Apr 2022
-
스프링 MVC 구조 이해 14 Apr 2022
-
MVC 프레임워크 만들기 - V4, V5 12 Apr 2022
-
MVC 프레임워크 만들기 - V1, V2, V3 09 Apr 2022
-
서블릿, JSP, MVC 패턴 05 Apr 2022
-
서블릿 29 Mar 2022
-
웹 애플리케이션 이해 24 Mar 2022
-
스프링 웹 계층이란? 05 Nov 2021
-
스프링 시큐리티 공식문서 번역 27 Sep 2021
-
스프링 AOP 총정리 : 개념, 프록시 기반 AOP, @AOP 27 Apr 2021
-
SpEL (스프링 Expression Language) 25 Apr 2021
-
데이터 바인딩 추상화 : Converter와 Formatter 21 Apr 2021
-
데이터 바인딩 추상화 : PropertyEditor 12 Apr 2021
-
Validation 추상화 10 Apr 2021
-
Resource 추상화 08 Apr 2021
-
IoC 컨테이너 9부 07 Apr 2021
-
IoC 컨테이너 8부 06 Apr 2021
-
IoC 컨테이너 7부 02 Apr 2021
-
IoC 컨테이너 6부 29 Mar 2021
-
IoC 컨테이너 5부 27 Mar 2021
-
IoC 컨테이너 4부 23 Mar 2021
-
IoC 컨테이너 3부 20 Mar 2021
-
IoC 컨테이너 2부 18 Mar 2021
-
IoC 컨테이너 1부 12 Mar 2021
-
스프링 PSA 07 Jan 2021
-
스프링 @AOP 실습 07 Jan 2021
-
프록시 패턴 06 Jan 2021
-
스프링 AOP 04 Jan 2021
-
의존성 주입(Dependency Injection) 04 Jan 2021
-
스프링 빈(Bean) 02 Jan 2021
-
스프링 IoC 컨테이너 01 Jan 2021
-
스프링 IoC 01 Jan 2021