-
[Spring Boot] thymeleaf, 타임리프 기본 기능 - 1편Back-End/Spring Boot 2022. 10. 21. 23:50
스프링 레거시 프로젝트로 MVC 패턴을 공부하면서 JSP로 뷰 템플릿을 사용하였다. Spring Boot를 통해 MVC 패턴을 다시 공부하고 있는데 스프링 부트에서 밀고있는 뷰 템플릿은 thymeleaf라고 한다.
Thymeleaf는 html, xml, js, css 등을 처리할 수 있는 웹 및 독립형 환경에서 사용이 가능한 java 템플릿 엔진이다. 따라서 html 파일을 가져와서 파싱하고 그걸 분석하여 정해진 위치에 데이터를 뿌려준다는 특징이 있다. 이런 타입리프의 개념과 기본 기능에 대해 이번 포스팅에서 정리해볼까 한다.
📑 타임리프 특징
🎃 1. 서버 사이드 HTML 렌더링 (SSR)
: SSR이란 ? 서버로부터 완전하게 만들어진 html파일을 받아와 페이지 전체를 렌더링 하는 방식으로 먼저 클라이언트가 초기 화면을 로드하기위해 서버에 요청을 보낸다. 그럼 서버는 화면에 표시하는데 필요한 데이터를 얻어와 모두 삽입하고 css 까지 모두 적용해서 렌더링 준비를 마친 HTML과 JS코드를 브라우저에 응답으로 전달한다. 브라우저에서는 바로 전달 받은 페이지를 띄우고, 이어, 브라우저가 JS 코드를 다운로드하고 html에 실행시킨다.
타임리프는 백엔드 서버에서 HTML을 동적으로 렌더링 하는 용도로 사용된다.
🎃 2. 네츄렬 템플릿
: 타임리프는 순수 HTML을 최대한 유지하는 특징이 있다. 타임리프로 작성한 파일은 HTML을 유지하기 때문에 웹 브라우저에서 파일을 직접 열어도 내용을 확인할 수 있고, 서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과를 확인할 수 있다.
JSP의 경우 파일 자체를 그대로 웹 브라우저에서 열어보면 JSP 소스코드와 HTML이 뒤죽박죽 섞여서 웹 브라우저에 정상적인 HTML 결과를 확인할 수 없다. 오직 서버를 통해 JSP가 렌더링 되고 HTML 응답 결과를 받아야 화면을 확인 가능!!
하지만 타임리프는 작성된 파일은 해당 파일을 그대로 웹 브라우저에서 열어도 정상적인 html 결과를 확인할 수 있다. 물론 이 경우 동적으로 결과가 렌더링 되지 않고 입력한 소스 코드 그대로 출력된다. 이렇게 HTML을 그대로 유지하면 뷰 템플릿도 사용할 수 있다는 게 타임리프의 특징 중 NATURAL TEMPLATES라고 한다.
< 참고 ! >
파일 경로 확인하기
🎃 3. 스프링 통합 지원
: 타임리프는 스프링과 자연스럽게 통합되고, 스프링의 다양한 기능을 편리하게 사용할 수 있게 지원한다. 이 부분은 다음 포스팅에서 자세히 다뤄볼 예정!
📑 타임리프 기본 기능들
타임리프를 사용하려면 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: 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) ?: (defaultvalue)
📌특별한 토큰:
◦ No-Operation: _📑 텍스트 - text, utext
타임리프는 기본적으로 HTML 태그의 속성에 기능을 정의해 동작한다. HTML 컨텐츠에 데이터를 출력할때 th:text를 사용하면 된다. ex) <span th:text="${data}">
HTML 태그 속성이 아니라, HTML 컨텐츠 영역 안에 직접 데이터를 출력하고 싶다면 [[...]]를 사용하면 된다.
ex) [[${data}]]
🎃 Escape와 HTML 엔티티
: HTML 문서는 <,>같은 특수 문자를 기반으로 정의된다. 따라서 뷰 템플릿으로 HTML 화면을 생성할 때 출력하는 데이터에 이러한 특수문자가 있는 것을 주의해서 사용해야 한다.
만약 데이터에 <b>Spring!</b>와 같은 태그가 포함되어 있을 때 브라우저에서 결과를 실행해보면 다음과 같다.
th:text를 소스보기 해보면 Hello <b>Spring!</b>
th:utext를 소스보기 해보면 Hello <b>Spring!</b>개발자가 의도한 것은 <b>가 있으면 해당 부분을 강조하는 것인데 <b> 태그가 text <로 변경 된 것을 확인할 수 있다. 웹 브라우저는 <를 HTML 태그로 인식한다. <를 태그의 시작이 아닌 문자로 표현할 수 있는 방법을 바로 HTML 엔티티라고 한다.
그리고 이렇게 HTML에서 사용하는 특수 문자를 HTML 엔티티로 변경하는 것을 이스케이프(escape)라 한다. 그리고 타임리프가 제공하는 th:text , [[...]] 는 기본적으로 이스케이스(escape)를 제공한다
th:text - > th:utext
[[ .. ]] -> [( .. )]<컨트롤러>
@GetMapping("text-unescaped") public String textUnescaped(Model model){ model.addAttribute("data", "Hello <b>Spring!</b>"); return "basic/text-unescaped"; }
<뷰 템플릿>
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <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> </body> </html>
💡 기본을 escape를 사용하고, 꼭 필요할때만 unescape를 사용하자.
📑 변수 - 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을 선언해서 사용할 수 있다. 지역변수는 선언한 태그 안에서만 사용할 수 있다.
<h1>지역 변수 - (th:with)</h1> <div th:with="first=${users[0]}"> <p>처음 사람의 이름은 <span th:text="${first.username}"></span></p> </div>
📑 기본 객체들
타임리프는 기본 객체들을 제공한다. 이 말뜻이 무엇이냐,,!!
request, response 등등 자주 사용하는 객체들을 조회하려면 HttpServletRequest 객체인 request를 매개변수로 받아서 request.getParameter("data") 이런 식으로 접근해야 하지만 타임리프에서는 이런 작업 없이 그냥 ${#request} 이런식으로 접근이 가능하다.
<컨트롤러>
@GetMapping("/basic-objects") public String basicObjects(HttpSession session){ session.setAttribute("sessionData", "Hello Session"); return "basic/basic-objects"; } @Component("helloBean") static class HelloBean{ public String hello(String data){ return "Hello" + data; } }
<뷰 템플릿>
<!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> <!--원래대로라면 사용자가 입력한 정보를 파라미터로 넘겨서 ?paramData=HelloParam 넘어온 파라미터를 컨트롤러에서 받고 그걸 다시 모델에 담아서 뷰에서 출력해야 되지만 타임리프는 자주 사용하는 객체들을 편리하게 사용할 수 있는 기능을 제공한다. --> <li>Request Parameter = <span th:text="${param.paramData}"></span></li> <!--session.setAttribute("sessionData", "Hello Session"); 세션에 저장해둔 데이터에 접근도 가능! --> <li>session = <span th:text="${session.sessionData}"></span></li> <!--스프링 빈에 접근하려면 @로 ! --> <li>spring bean = <span th:text="${@helloBean.hello('Spring!')}"></span></li> </ul> </body> </html>
📑 URL 링크
타임 리프에서 URL을 생성할 때 @{...} 문법을 사용하면 된다.
<컨트롤러>
@GetMapping("/link") public String link(Model model){ model.addAttribute("param1", "data1"); model.addAttribute("param2", "data2"); return "basic/link"; }
<뷰 템플릿>
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>URL 링크</h1> <ul> <li><a th:href="@{/hello}">basic url</a></li> <!-- 쿼리 파라미터 붙이기 /hello?param1=data1¶m2=data2 --> <li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query param</a></li> <!-- url 호출 시 전달된 매개변수 값, 경로변수 (PathVariable 붙이기) /hello/{param1}/{param2} --> <li><a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">path variable</a></li> <li><a th:href="|@{/hello/${param1}/${param2}}|"> path variable 2</a> </li> <!-- 경로 변수 + 쿼리 파라미터 /hello/{param1}?param2=data2 --> <li><a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">path variable + query parameter</a></li> </ul> </body> </html>
📌 단순한 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"라는 문자 리터럴, 10, 20은 숫자 리터럴이다.
String a = "Hello"
int a = 10 * 20타임리프에서 문자 리터럴은 항상 ' (작은따옴표)로 감싸야 한다. <span th:text =" ' hello ' " > 이런 형식으로
그런데) 문자를 항상 작은따옴표로 감싸는 게 여간 번거로운일이 아니다. 따라서 공백 없이 쭉 이어진 하나의 단어라면 의미 있는 토큰으로 인지해 작은 따옴표를 생략할 수 있다. <span th:text ="hello " >
💡 하지만 문자 중간에 띄어쓰기가 있다면, 오류가 난다. <span th:text="'hello' + ' world!'"> 이렇게 + 연산으로 처리해줘야 되는데 이 또한 번거롭다.
이럴때 리터럴 대체를 사용하는데 그 표현식은 바로 | | 이다. <span th:text="|hello ${data}|"> 이런식으로 덧셈 연산 없이 || 안에선 하나의 문자 처럼 사용이 가능하다.
<컨트롤러>
@GetMapping("/literal") public String literal(Model model){ model.addAttribute("data", "Spring!"); return "basic/literal"; }
<뷰 템플릿>
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>리터럴</h1> <ul> <!--주의! 다음 주석을 풀면 예외가 발생함--> <!-- <li>"hello world!" = <span th:text="hello world!"></span></li>--> <li>'hello' + ' world!' = <span th:text="'hello' + ' world!'"></span></li> <li>'hello world!' = <span th:text="'hello world!'"></span></li> <li>'hello world!' = <span th:text="|hello world!|"></span></li> <li>'hello ' + ${data} = <span th:text="'hello ' + ${data}"></span></li> <li>리터럴 대체 |hello ${data}| = <span th:text="|hello ${data}|"></span></li> </ul> </body> </html>
📑 연산
:타임리프 연산은 자바와 크게 다르지 않다. HTML 안에 사용하기 때문에 HTML 엔티티를 사용하는 부분만 주의하면 될 것 같다.
<컨트롤러>
@GetMapping("/operation") public String operation(Model model){ model.addAttribute("nullData", null); model.addAttribute("data", "Spring!"); return "basic/operation"; }
<뷰 템플릿>
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <ul> <li>산술 연산 <ul> <li>10 + 2 = <span th:text="10 + 2"></span></li> <li>10 % 2 == 0 = <span th:text="10 % 2 == 0"></span></li> </ul> </li> <li>비교 연산 <ul> <li>1 > 10 = <span th:text="1 > 10"></span></li> <li>1 gt 10 = <span th:text="1 gt 10"></span></li> <li>1 >= 10 = <span th:text="1 >= 10"></span></li> <li>1 ge 10 = <span th:text="1 ge 10"></span></li> <li>1 == 10 = <span th:text="1 == 10"></span></li> <li>1 != 10 = <span th:text="1 != 10"></span></li> </ul> </li> <li>조건식 <ul> <li>(10 % 2 == 0)? '짝수':'홀수' = <span th:text="(10 % 2 == 0)?'짝수':'홀수'"></span></li> </ul> </li> <li> 기본 객체 출력 <ul> <li>데이터 = <span th:text="${data}">데이터가 없습니다.</span></li> <li>null 데이터 = <span th:text="${nullData}">데이터가 없습니다.</span></li> </ul> </li> <li>Elvis 연산자 <ul> <li>${data}?: '데이터가 없습니다.' = <span th:text="${data}?: '데이터가없습니다.'"></span></li> <li>${nullData}?: '데이터가 없습니다.' = <span th:text="${nullData}?:'데이터가 없습니다.'"></span></li> </ul> </li> <!--no-operation token의 사용은 템플릿 코드의 복잡도를 낮춰준다--> <li>No-Operation <ul> <li>${data}?: _ = <span th:text="${data}?: _">데이터가 없습니다.</span></li> <li>${nullData}?: _ = <span th:text="${nullData}?: _">데이터가없습니다.</span></li> </ul> </li> </ul> </body> </html>
📑 속성 값 설정
타임리프는 주로 태그에 th:* 속성을 지원하는 방식으로 동작한다. 만약 th:*로 속성을 적용하면 기존 속성을 대체한다. 기존 속성이 없으면 새로 만든다.
<컨트롤러>
@GetMapping("/attribute") public String attribute(Model model){ model.addAttribute("Checked", true); model.addAttribute("NonChecked", false); return "basic/attribute"; }
<뷰 템플릿>
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>속성 설정</h1> <input type="text" name="mock" th:name="userA" /> <h1>속성 추가</h1> - th:attrappend = <input type="text" class="text" th:attrappend="class=' large'" /><br/> - th:attrprepend = <input type="text" class="text" th:attrprepend="class='large '" /><br/> - th:classappend = <input type="text" class="text" th:classappend="large" /><br/> <h1>checked 처리</h1> - checked o <input type="checkbox" name="active" th:checked="true" /><br/> - checked x <input type="checkbox" name="active" th:checked="false" /><br/> - checked=false <input type="checkbox" name="active" checked="false" /><br/> <br> <h1> 컨트롤러에 데이터 추가</h1> - <input type="checkbox" name="active" th:checked="${Checked}" /> - <input type="checkbox" name="active" th:checked="${NonChecked}" /> </body> </html>
📌 th:* 속성을 지정하면 타임 리프는 기본 속성을 th:*로 지정한 속성으로 대체한다. 기존 속성이 없다면 새로 만든다.
<input type="text" name="mock" th:name="userA" /> 타임리프 렌더링 후 <input type="text" name="userA" />
name에 값을 th:name으로 지정한 값으로 대체하는 것!
📌 th:classappend
: class속성에 자연스럽게 추가한다. append, prepend보다 편리해서 이 속성 설정을 더 자주 씀.
📌 checked 처리
:HTML에서 th:checked 값을 설정하지 않고 그냥 checked="false"로 설정할 경우 이미 checked라는 속성 값이 언급 되었기에 HTML에서 체크 되어 나오는 걸 확인할 수 있다. 그렇기에 th:checked로 true, false를 설정해서 check 박스 설정이 가능하다.
반응형'Back-End > Spring Boot' 카테고리의 다른 글
[Spring Boot] 타임리프가 지원하는 form 속성 기능 (check box, radio button, select box) (0) 2022.10.25 [Spring Boot] thymeleaf, 타임리프 기본기능 - 2편 (0) 2022.10.24 [Spring] 스프링 MVC 구조 (핸들러 매핑, 핸들러 어댑터, 뷰 리졸버) (0) 2022.10.19 [Spring] HTTP 응답 메시지 만들기 ( Model, @ResponseBody, @ResponseEntity) (0) 2022.10.19 [Spring Boot] HTTP 요청 파라미터, 메시지 ( @RequestParam, @ModelAttribute, @RequestBody, HttpEntity) (0) 2022.10.14