Thymeleaf 기본기능 정복하기[1편]

gwjeon·2021년 11월 1일
1
post-custom-banner



학습참조
인프런-스프링 MVC 2편 백엔드 웹 개발 활용 기술(김영한님)


이 글을 작성하고 난 뒤에는 내가 나중에 타임리프를 사용할 때에, 문법이 잘 기억이 나지 않을 때 이 글만 보아도 기본적인 타임리프 기능들을 사용할 수 있었으면 한다.


✅ 다루는 기능들

🚩 Thymeleaf(타임리프)란?
🚩 텍스트 출력하기 - text / [[...]] , utext / [(...)]
🚩 변수 - SpringEL
🚩 타임리프 기본 객체
🚩 유틸리티 객체
🚩 Url
🚩 리터럴(Literals)

✅ Thymeleaf(타임리프)란?

✔ 서버사이드 렌더링(Server Side Rendering, SSR)

클라이언트 사이드 렌더링(Client Side Rendering, CSR)이 아닌 서버에서 동적으로 HTML을 만들어 렌더링하는 할수있는 템플릿

✔ 네츄럴 템플릿(Natural templates)

  • 순수 HTML을 유지하는 템플릿.
  • 타임리프로 작성된 파일은 기본 HTML 구조를 유지하기 때문에 파일을 직접 열어도, 서버를 통해 렌더링 하더라도 일관된 HTML 구조를 유지한다.
  • JSP를 포함한 다른 템플릿들은 동적으로 렌더링 하기위하여 여러 기능들을 사용하는데 이 과정에서 HTML 구조가 뒤죽박죽 섞이기 때문에 정상적인 HTML 구조를 유지하기가 어렵다.
  • 이렇게 순수 HTML을 그대로 유지하며 템플릿을 사용할 수 있는 타임리프의 특징을 네츄럴 팀플릿 이라한다.

✔ 스프링 통합 지원

스프링에서는 공식으로 타임리프를 사용할 것을 권장하며 스프링의 다양한 기능을 편리하게 사용할 수 있게끔 제공한다.

✔ Thymeleaf(타임리프) 사용선언

최상단 HTML 코드에 다음과 같이 선언한다.

<html xmlns:th="http://www.thymeleaf.org">

✅ 텍스트 출력하기 - text / [[...]] , utext / [(...)]

⭐ controller

@GetMapping("...")
public String ...(Model model) {
	model.addAttribute("data", "Hello Spring!");
    return "...";
}

✔ th:text / [[...]]

<ul>
    <li>th:text 사용 : <span th:text="${data}"></span></li>
    <li>컨텐츠 안에서 직접 출력하기 : [[${data}]]</li>
</ul>
출력
  • th:text 사용 : Hello Spring!
  • 컨텐츠 안에서 직접 출력하기 : Hello Spring!

⭐ HTML 엔티티

스프링에서 Model에 담아 넘겨줄때부터 직접 <b></b> 코드를 작성하여 넘겨주어 보자.

@GetMapping("...")
public String ...(Model model) {
	model.addAttribute("data", "Hello <b>Spring!</b>");
    return "...";
}

이렇게 하면 개발자가 의도한 바로는 "Spring!"이 강조 처리된 "Hello Spring!"이 출력되어야 하겠지만 사실은 Hello <b>Spring!<b>가 출력된다. 웹브라우저는 '<' 를 HTML 태그의 시작으로 인식한다. 따라서 '<'를 HTML 태그가 아닌 단순히 문자로 표현할 수 있는 방법이 필요한데 이것을 HTML 엔티티라 한다. 그리고 이렇게 HTML에서 사용하는 특수문자를 HTML 엔티티로 변경하는 것을 이스케이프(escape)라 하는데 타임리프는 기본적으로 th:text, [[...]]을 통해 이스케이프(escape)를 제공한다.

HTML 엔티티는 다음과 같으며 이외에도 수많은 엔티티가 있다.
  • 공백 : &nbsp;
  • "<" : &lt;
  • ">" : &gt;

그렇다면 이스케이프(escape)를 지원하지 않으려면 어떻게 해야할까? 이 역시 타임리프에서 th:utext, [(...)]를 통해 지원한다.


✔ th:utext / [(...)]

th:utext와 [(...)]을 통해 이스케이프 되지 않은 기능을 제공한다.

<ul>
    <li>th:text 사용 : <span th:utext="${data}"></span></li>
    <li>컨텐츠 안에서 직접 출력하기 : [(${data})]</li>
</ul>
출력
  • th:text 사용 : Hello Spring!
  • 컨텐츠 안에서 직접 출력하기 : Hello Spring!

이렇게 한다면 개발자가 의도한 대로 "Hello Spring!"이 출력되는 것을 확인할 수 있다.


✅ 변수 - SpringEL

타임리프에서 변수를 사용할 때는 변수 표현식을 사용한다. 변수 표현식은 다음과 같다. "${...}"
그리고 이 변수 표현식에는 SpringEL이라는 스프링이 제공하는 표현식을 사용할 수 있다.

⭐ controller

@GetMapping("...")
public String ...(Model model) {

    User userA = new User("userA");
    
    List<User> list = new ArrayList<>();
    list.add(userA);
    
    Map<String, Object> map = new Map<>();
    map.put("userA", userA);
    
    model.addAttribute("userObject", user);
    model.addAttribute("userList", list);
    model.addAttribute("userMap", map);
    
    return "...";
}

class User {
    private String name;
    
    public User(String name) {
    	this.name = name;
    }
}

✔ SpringEL 표현식

⭐ Object

<ul>
    <li>${userObject.name} = <span th:utext="${userObject.name}"></span></li>
 	<li>${userObject.['name']} = <span th:utext=">${userObject.['name']}"></span></li>
 	<li>${userObject.getName()} = <span th:utext="${userObject.getName()}"></span></li>
</ul>
출력
  • ${userObject.name} = userA
  • ${userObject.['name']} = userA
  • ${userObject.getName()} = userA

⭐ List

<ul>
    <li>${userList[0].name} = <span th:utext="${userList[0].name}"></span></li>
 	<li>${userList[0].['name']} = <span th:utext=">${userList[0].['name']}"></span></li>
 	<li>${userList[0].getName()} = <span th:utext="${userList[0].getName()}"></span></li>
</ul>
출력
  • ${userList[0].name} = userA
  • ${userList[0].['name']} = userA
  • ${userList[0].getName()} = userA

⭐ Map

<ul>
    <li>${userMap['userA'].name} = <span th:utext="${userMap['userA'].name}"></span></li>
 	<li>${userMap['userA'].['name']} = <span th:utext=">${userMap['userA'].['name']}"></span></li>
 	<li>${userMap['userA'].getName()} = <span th:utext="${userMap['userA'].getName()}"></span></li>
</ul>
출력
  • ${userMap['userA'].name} = userA
  • ${userMap['userA'].['name']} = userA
  • ${userMap['userA'].getName()} = userA

✔ 지역변수

타임리프 내에서도 지역변수를 선언하여 사용할 수 있으며 선언한 태그안에서만 사용가능 하다.

<div th:with="person=${users[0]}"> <!-- (1) -->
 <p>사람의 이름은 <span th:text="${person.name}"></span></p> <!-- (2) -->
</div>
  • (1): th:with를 이용하여 변수명이 person이고 users[0]을 값으로 가지는 지역변수 선언.
  • (2): personusers[0]을 가지고 있으므로 변수표현식 ${person.name}을 통하여 출력.
  • 해당 person 변수는 div 내에서만 사용가능.
출력

사람의 이름은 userA


✅ 타임리프 기본 객체

타임리프는 다음과 같은 기본 객체들을 제공한다.

✔ 기본객체

  • ${#request}
  • ${#response}
  • ${#session}
  • ${#servletContext}
  • ${#locale}

#requestHttpServletRequest 객체에 접근하듯 reqeust.geParameter("...")와 같이 접근해야 하지만 이러한 점을 해결하기 위한 편의 객체를 제공한다.


✔ 편의객체

⭐ params

<p th:text="${params.username}">username</p>

http 호출시 http://....?username=gwjeon로 접속되었다면 username이 parameter에 존재하기 때문에 출력 결과는 gwjeon 이 된다.


⭐ session

Controller
@GetMapping("...")
public String ...(HttpSession session) {
     session.setAttribute("sessionData", "Hello Spring");
     return "...";
}
<p th:text="${session.sessionData}">sessionData</p>

세션 접근도 지원한다. 컨트롤러에서 세션에 sessionData를 key로 한 Hello Spring 문자열을 저장하였다. 출력 결과는 Hello Spring이 된다.


⭐ 스프링 빈 접근

스프링 빈에 접근하는것도 지원한다.

@Component("helloBean")
class HelloBean {
    public String hello(String data) {
        return "Hello " + data;
    }
}

다음과 같은 스프링 빈이 있다고 가정 했을때

<p th:text="${@helloBean.hello('Hello Srping')}">springBean</p>

와 같이 한다면 출력 결과는 Hello Spring이 된다. @helloBean은 빈이름이 되며 첫글자는 소문자로 변경되어 표기하고 .hello를 통해 빈에 등록된 메서드에 Hello Spring 문자열을 argument로 전달한다.


✅ 유틸리티 객체

타임리프에서 지원하는 유틸리 객체는 다음과 같다.

  • #message : 메시지, 국제화 처리
  • #uris : URI 이스케이프 지원
  • #dates : java.util.Date 서식 지원
  • #calendars : java.util.Calendar 서식 지원
  • #temporals : 자바8 날짜 서식 지원
  • #numbers : 숫자 서식 지원
  • #strings : 문자 관련 편의 기능
  • #objects : 객체 관련 기능 제공
  • #bools : boolean 관련 기능 제공
  • #arrays : 배열 관련 기능 제공
  • #lists , #sets , #maps : 컬렉션 관련 기능 제공
  • #ids : 아이디 처리 관련 기능 제공

워낙 종류가 많으니 어떠한 것이 있는지만 기억하고 사용하기 전 매뉴얼을 참고하자.

✔ JAVA8 날짜 객체

타임리프에서 JAVA8 날짜인 LocalDate , LocalDateTime , Instant 를 사용하려면 추가 라이브러리 thymeleaf-extras-java8time가 필요하며 스프링 부트 타임리프를 사용하면 해당 라이브러리가 자동으로 추가된다. JAVA8 날짜 유틸리티 객체는 #temporals 이다.


✅ Url

타임리프에서는 기본적으로 Url은 모두 @{}안에 정의 되어야 한다.

⭐ controller

@GetMapping("...")
public String ...(Model model) {
    model.addAttribute("param1", "data1");
    model.addAttribute("param2", "data2");
    
    return "...";
}

✔ 여러가지 url 타입

<ul>
    <li><a th:href="@{/hello}">basic url</a></li> <!-- (1) -->
    <li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query param</a></li> <!-- (2) -->
    <li><a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">path variable</a></li> <!-- (3) -->
    <li><a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">path variable + query parameter</a></li> <!-- (4) -->
</ul>

기본적으로 url 구조를 먼저 작성하고 뒤에 ()괄호안에 동적으로 들어갈 value를 할당하는 구조이다.

  • (1): 기본 url 타입으로 /hello로 이동한다.
  • (2): parameter 타입으로 /hello?param1=data1&param=data2로 이동한다.
  • (3): path variable 타입으로 /hello/{...}/{...}로 이동한다.
  • (4): parameter와 path variable을 같이 사용 하는것으로 /hello/{...}?param2=data2로 이동한다. 이 경우는 url상에 variable인 param1까지만 할당되고 param2는 해당되는것이 없음으로 자동으로 parameter로 할당된다.

✅ 리터럴(Literals)

타임리프에는 다음과 같은 리터럴이 있다.

  • 문자
  • 숫자
  • boolean
  • null

✔ 문자 리터럴

타임리프 문자 리터럴은 항상 '...' 작은 따옴표로 감싸야 한다.

⭐ 공백 없이 쭉 이어지는 문자인 경우

<span th:text="hello"></span>

위 처럼 공백 없이 쭉 이어지는 문자라면 작은 따옴표를 생략할 수 있다.

⭐ 공백이 있는 문자인 경우

<span th:text="hello spring!"></span> <!-- (1) -->
<span th:text="'hello spring!'"></span> <!-- (2) -->
  • (1): 공백이 있는 문자의 경우는 에러가 발생한다.
  • (2): 공백이 있을 경우 작은 따옴표로 감싸야 한다.

✔ 리터럴 대체(Literal substitutions)

하지만 매번 이렇게 리터럴을 작은 따옴표를 통하여 작성하는 것은 비효율적이다.
리터럴 대체문법을 사용하면 매우 간편하게 해결할 수 있다.

<span th:text="|hello spring!|"></span>

위 처럼 문자를 |...|으로 감싸주면 작은 따옴표로 감싸지 않아도 된다. 여기서 의문이 든다.
"'hello spring!'"이나 "|hello spring!|"이나 똑같이 문자를 감싸야 하는건 동일한데 도대체 무엇이 다른것인가?

리터럴 대체문법은 마치 JavaScript의 리터럴 템플릿 문법과 같다.

예를들어 다음과 같은 controller가 있다고 가정하자.

⭐ controller

@GetMapping("...")
public String ...(Model model) {
    model.addAttribute("data", "Spring!");
    return "...";
}

⭐ 리터럴 대체 문법을 써야하는 이유

<span th:text="'hello ' + ${data}"></span> <!-- (1) -->
<span th:text="|hello ${data}|"></span> <!-- (2) -->

똑같은 hello Spring! 이라는 문자를 출력하는 내용이지만 2가지 방법으로 갈린다.

  • (1): 모델로부터 전달받은 data를 출력하기 위해선 + 연산자를 통해야 하며 공백을 위하여 hello 문자 뒤에 공백을 추가했다.
  • (2): 리터럴 대체 문법을 통하여 |...| 로 감싸기만 했을 뿐인데 마치 문자를 사용하듯 편리하다. |...|안에 있는 공백을 포함한 문자들을 출력하고 ${data} 변수부분은 Spring!로 치환된다.

profile
ansuzh
post-custom-banner

0개의 댓글