백엔드/Spring Boot

Thymeleaf

두개의 문 2023. 8. 2. 12:23
Thymeleaf 

 

 

▪︎ Thymeleaf란?

  - View Template Engine으로, JSP, Freemarkerd와 같이 서버에서 클라이언트에게 응답할 브라우저 화면을 만들어주는 역할 

 

 

▪︎ Thymeleaf의 목표 

  - 주요 목표 : 템플릿을 만들 때 유지관리가 쉽도록 하는 것

    → 디자인 프로토타입으로 사용되는 템플릿에 영향을 미치지 않는 방식인 Natural Templates을 기반으로 함 

  - Natural Templates : 기존 HTML 코드와 구조를 변경하지 않고 덧붙이는 방식 

 

▪︎ Thymeleaf의 장점 

  - 코드를 변경하지 않기 때문에 디자인 팀과 개발 팀 간의 협업이 편리해짐 

  - JSP와 달리, Servlet Code로 변환되지 않기 때문에 비즈니스 로직과 분리되어 오로지 View에 집중할 수 있음 

  - 서버 상에서 동작하지 않아도 되기 때문에, 서버 동작 없이 화면 확인 가능 → 더미 데이터를 넣고 화면 디자인 및 테스트에 용이함 

 

▪︎ Thymeleaf와 Spring Boot 

  - 위와 같은 타임리프 장점때문에 Spring에서도 Spring Boot와 타임리프를 함께 사용하는 것을 권장함 

  - Spring Boot에서는 JSP 사용 시 호환 및 환경 설정에 어려움이 많기 때문

    반대로 타임리프는 간편하게 Dependency 추가 작업으로 사용 가능 

 

 

▪︎ Thymeleaf의 특징 

  1. 서버 사이드 HTML 렌더링 ( SSR )

    - 타임리프는 백엔드 서버에서 HTML을 동적으로 렌더링하는 용도로 사용됨 

    - 사용법은 SSR이 다 비슷하기에 학습하기에도 어렵지 않고, 페이지가 어느 정도 정적이고 빠른 생산성이 필요한 경우 백엔드 개발자가 개발해야 하는 일이 생기는데 이 경우 타임리프는 좋은 선택지임 

 

  2. 네츄럴 템플릿 

    - 타임리프는 순수한 HTML을 최대한 유지하려는 특징이 있음 

      → 이게 JSP와의 큰 차이점! 

    - 타임리프로 작성한 파일은 확장자도 '.html'이고, 웹 브라우저에서 직접 파일을 열어도 내용 확인 가능 

      → 물론 이 경우 동적인 결과 렌더링은 되지 않지만, HTML 마크업 언어가 어떻게 되는지 확인 가능 

 

   3. 스프링 통합 지원 

    - 타임리프는 스프링과 자연스럽게 통합되어 스프링의 다양한 기능을 쉽게 사용 가능 

 

 


1. 타임리프 사용 선언 

  ▪︎ 타임리프를 사용하기 위해서는 적용할 HTML 문서에 네임스페이스 추가 

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

 

  - xmlns:th 

    : 타임리프의 th속성을 사용하기 위해 선언된 네임스페이스 

      순수 HTML로만 이루어진 페이지의 경우, 선언하지 않아도 무관함 

 

 


2.  타임리프 표현식과 문법 

  ▪︎ 표현식 : 전달받은 데이터를 사용자들이 볼 수 있는 뷰로 만들기 위해 사용됨 

표현식 설명
${ ... } 변수의 값 표현식
#{ ... } 속성 파일 값 표현식 
@{ ... } URL 표현식
*{ ... } 선택한 변수의 표현식
th:object에서 선택한 객체에 접근 

 

  ▪︎ 문법 

표현식 설명 예제
th:text 텍스트를 표현할 때 사용  th:text=${ person.name }
th:each 컬렉션을 반복할 때 사용  th:each="person : ${ persons }"
th:if 조건이 true인 때만 표시  th:if="${ person.age } >= 20"
th:unless 조건이 false인 때만 표시  th:unless="${ person.age } >= 20"
th:href 이동 경로  th:href="@{ /person(id=${ person.id })}"
th:with 변수값으로 지정  th:with="name=${ person.name }"
th:object 선택한 객체로 지정  th:object="${ person }" 

 

 


3. 텍스트 - text, utext 

 🔹 서버에서 Model에 담아준 각종 속성(attribute)들을 서버사이드 템플릿 엔진인 타임리프에서는 여러 방법으로 표현 가능

 

 1) 텍스트 출력 기능

    ① HTML 태그의 콘텐츠에 직접 출력하는 기능 - th:text

    ② 콘텐츠 안에서 직접 출력하는 기능 - [[ ... ]]

 

  ▪︎ ExampleController.java

package org.choongang.ewha.hithymeleaf.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ExampleController {
	@GetMapping("/example")
	public void example(Model model) {
		model.addAttribute("data", "Hello Spring!");
	}
}

 

 

  ▪︎ Example.html

<!DOCTYPE html>
<html xmlns:th="http://thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>텍스트 출력 예시</h1>
<ul>
	<li>① th:text 사용 <span th:text="${ data }"></span></li> 
	<li>② 컨텐츠 안에서 직접 출력하기 [[${ data }]]</li>
</ul>
</body>
</html>

 

 

  ▪︎ 실행결과 

 

 2) Escape 

   🔹 만약 서버에서 속성으로 추가할 데이터에 HTML 태그를 추가하여 타임리프에서 태그효과까지 같이 사용하고 싶다면 어떻게 해야할까?

     컨트롤러에서 "Hello Spring!"이 아닌 "<b> Hello Spring! </b>"을 데이터로 전달해보자 

          ( <b> 태그 : 다른 부가적인 목적 없이 단순히 굵게 표현하는 텍스트를 정의할 때 사용 )

 

 

  ▪︎ ExampleController.java

package org.choongang.ewha.hithymeleaf.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ExampleController {
	@GetMapping("/example")
	public void example(Model model) {
		model.addAttribute("data", "<b>Hello Spring!</b>");
	}
}

 

 

  ▪︎ 실행결과 

    : 의도와는 다르게 태그가 그대로 출력되었음 

 

 

 ▪︎ 페이지 소스 보기 

   : <b> 태그가 &lt;b&gt;로 되어 있음

 

- 타임리프가 제공하는 th:text, [[ ... ]]는 기본적으로 이스케이프(escape)를 제공

   ( ∵ 특수문자로 인하여 HTML이 깨지는 것을 방지하기 위해 지원됨 )

    • HTML 엔티티

       : HTML에서 사용하는 특수 문자( ex. <, > )를 태그의 시작이 아닌 문자로 표현할 수 있도록 &It;, &gt; 와 같이 변경됨 

    • 이스케이프 

      : HTML에서 사용하는 특수 문자을 HTML 엔티티로 변경하는 것을 의미  

HTML 태그  문자 표현 
< &It;
> &gt;

 

 

 

 3) Unescape 

 🔹 HTML 엔티티로 변경하지 않고 HTML 태그로 사용하고 싶다면 어떻게 해야할까? Unescape!

 

 - 이스케이프 기능을 사용하지 않는 방법 

escape 사용 O escape 사용 X
th:text th:utext
[[ ... ]]  [( ... )]

 

 

  ▪︎ Example.html

    ※ 참고 

        th:inline - 이 태그 안에 있는 내용은 타임리프가 해석하지 말라는 의미의 옵션  

<h1> text vs utext </h1>
<ul>
	<li>th:text 사용 <span th:text="${ data }"></span></li>
	<li>th:utext 사용 <span th:utext="${ data }"></span></li> 
</ul>
<h1><span th:inline="none">[[...]] vs [(...)]</span></h1>
<ul>
	<li><span th:inline="none">[[...]]=</span>[[${data}]]</li>
	<li><span th:inline="none">[(...)]=</span>[(${data})]</li>
</ul>

 

 

  ▪︎ 실행결과 


4. 변수 - SpringEL 

 

 - 기본적으로 변수 표현식은 ${ ... } 으로 사용하여 단순히 값을 표시

    → 이 변수 표현식에는 SpringEL이라는 스프링이 제공하는 표현식을 사용할 수 있음 

 

 1) SpringEL 표현식 

   • 단순한 변수일 경우, ${ data }로 바로 표현 가능하지만, Object나 List같은 객체의 경우 다음과 같이 사용함 

 

   ◎ Object

   • data.field : data의 field 프로퍼티에 접근 ( data.getField( ) )

   • data['field'] : 위와 동일 ( data.getField( ) )

   • data.getField( ) : data의 getField( ) 메서드를 직접 호출 가능 

 

   ◎ List 

   • list[0].field : List의 첫 번째 데이터를 찾아 field 프로퍼티에 접근 

   • list[0]['field'] : 위와 동일

   • list[0].getField( ) : List에서 첫 번째 데이터를 찾아 메서드를 직접 호출 가능 

   • list.get(0).xxx : List의 get 메서드를 통해 데이터를 찾아 프로퍼티에 접근 가능 

 

   ◎ Map

   • map['key'].field : Map에서 key를 찾아 field 프로퍼티에 접근

   • map['key']['field'] : 위와 동일

   • map['key'].getField( ) : Map에서 key를 찾아 직접 메서드 호출 가능 

 

 

 

 2) 지역 변수 선언 

  - th:with를 이용해 지역변수를 사용 가능 

     단, 지역변수이므로 선언한 태그 안에서만 사용 가능 

 

 

  ▪︎ variable.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>SpringEL 표현식</h1>

<ul>Object
    <li>${user.username} = <span th:text="${user.username}"></span></li>
    <li>${user['username']} = <span th:text="${user['username']}"></span></li>
    <li>${user.getUsername()} = <span th:text="${user.getUsername()}"></span></li>
</ul>

<ul>List
    <li>${users[0].username} = <span th:text="${users[0].username}"></span></li>
    <li>${users[0]['username']} = <span th:text="${users[0]['username']}"></span></li>
    <li>${users[0].getUsername()} = <span th:text="${users[0].getUsername()}"></span></li>
</ul>

<ul>Map
    <li>${userMap['userA'].username} = <span th:text="${userMap['userA'].username}"></span></li>
    <li>${userMap['userA']['username']} = <span th:text="${userMap['userA']['username']}"></span></li>
    <li>${userMap['userA'].getUsername()} = <span th:text="${userMap['userA'].getUsername()}"></span></li>
</ul>

<div th:with="item=${users[0]}">
    <ul>
        <li>이름 : <span th:text="${item.username}"></span></li>
        <li>나이 : [[${item.age}]]</li>
    </ul>
</div>

</body>
</html>

 

 

  ▪︎ ExampleController.java

@GetMapping("/variable")
	public String variable(Model model) {
		// 1. Object
		User userA = new User("userA", 10);
		User userB = new User("userB", 20);
		
		// 2. List
		List<User> list = new ArrayList<>(Arrays.asList(userA, userB));
		// * Arrays.asList() 메서드 : 배열을 리스트로 변환 
		
		// 3. Map 
		Map<String, User> map = new HashMap<>();
		map.put("userA", userA);
		map.put("userB", userB);
		
		model.addAttribute("user", userA);
		model.addAttribute("users", list);
		model.addAttribute("userMap", map);
		
		return "basic/variable";
		
	}
	
	@Data
    	static class User{
        	private String username;
        	private int age;

        	public User(String username, int age) {
            		this.username = username;
            		this.age = age;
       		}
   	}

 

 

  ▪︎ 실행결과 

 

 


 5. 기본 객체들 

 

   1) 타임리프에서 제공하는 기본 객체들

객체 설명 
${ #request }  HttpRequest 객체 접근
${ #response } HttpResponse 객체 접근
${ #locale } Locale 객체 접근 
${ #session } HttpSession 객체 접근 
${ #servletContext } ServletContext 객체 접근

 

 

 

   2) 기본객체에 대한 접근 편의 메서드 

    - 기본 객체의 프로퍼티 접근을 하기 위해서 편의 메서드가 없다면, 

       request.getParameter( "data" ) 와 같이 호출해야 함 

 

    - 타임리프에서는 편의 메서드를 제공함 

      • HTTP 요청 파라미터 접근 : param

         ⇒ ex ) ${ param.paramData }

      • HTTP 세션 접근 : session

         ⇒ ex ) ${ session.sessionData }

      • 스프링 빈 접근 : @    

         ⇒ ex ) ${ @helloBean.hello('Spring') }

 

 

 

  ▪︎ ExampleController.java

	@GetMapping("/basic-objects")
	public String basicObject(HttpSession httpSession) {
		httpSession.setAttribute("sessionData", "Hello Session");
		return "basic/basic-objects";
	}
	
	@Component("helloBean")
	// * @Component 애너테이션이 있는 클래스는 빈으로 등록됨 
	static class HelloBean{
		public String hello(String data) {
			return "Hello " + data ;
		}
	}

 

 

  ▪︎ objects.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>식 기본 객체 (Expression Basic Objects)</h1>
<ul>
    <li>request = <span th:text="${#request}"></span></li>
    <li>response = <span th:text="${#response}"></span></li>
    <li>session = <span th:text="${#session}"></span></li>
    <li>servletContext = <span th:text="${#servletContext}"></span></li>
    <li>locale = <span th:text="${#locale}"></span></li>

</ul>
<h1>편의 객체</h1>
<ul>
    <li>Request Parameter = <span th:text="${param.paramData}"></span></li>
    <li>session = <span th:text="${session.sessionData}"></span></li>
    <li>spring bean = <span th:text="${@helloBean.hello('Spring!')}"></span></li>
</ul>
</body>
</html>