블로그 화면 구성하기 - 타임리프1 (기초)
2023.08.01 ~ 08.03 수업
교재 : 스프링부트3 백엔드 개발자 되기
jsp을 사용하여 개발하는 방법 : 모델1
서블릿을 사용하여 개발하는 방법 : 모델2
스프링을 사용하여 개발하는 방법 : MVC
▶ 타임리프 (템플릿 엔진)
▷ 템플릿 엔진이란?
- 스프링 서버에서 데이터를 받아 웹 페이지(HTML)상에 데이터를 넣어 보여주는 도구
- 지정된 템플릿 양식과 데이터가 합쳐져 HTML 문서를 출력하는 소프트웨어
- 웹사이트 화면을 어떤 형태로 만들지 도와주는 양식
- HTML과 템플릿엔진 문법(th:속성)을 섞어 사용
-> 보통 jquery를 사용하거나 javascript를 사용하여 HTML에 가공된 데이터를 보여주는 것이 정석이다.
- 쉬운 표현으로 서버에서 받아온 데이터를 효과적으로 보여줄 중간 매개체인 템플릿 엔진을 사용한다
- 템플릿 엔진을 사용하면 비교적 간단한 표현 (조건문, 변수, 반복문) 을 통해 효과적으로 데이터를 가공하여 웹 페이지를 보여줄 수 있다
○ 템플릿 엔진 종류
Mustache : 로직이 없다, EL, {{}}
타임리프 : 엣 기호로 로직을 준다, EL + JSTL, ${}
JSP : 비즈니스 로직, EL + JSTL + 서블릿태그, ${}, 스프링부트에서 사용을 권고하지 않음
프리마커
※ 스프링은 타임리프를 권고하기 때문에 타임리프를 사용할 것(JSP에서 로직이 없는 ver)
(참고) 머스타치로는 화면에 여러개의 글 구현(반복문) 불가
jsp는 서블릿의 도움 없이 자바코드 마음껏 사용
▷ 타임리프의 표현식
${ } 변수의 값
#{ } 속성 파일 값
@{ } URL 표현식
*{ } 선택한 변수의 표현식. th:object에서 선택한 객체에 접근
-> {...}는 " "로 묶어주기 "${ }"
* 서버에서 받아온 데이터를 ${ } 등을 이용하여 표기
▷ 타임리프의 문법
th:text 텍스트를 표현할 때 사용
th:utext 태그를 태그로 인식할 때 사용
th:each 컬렉션을 반복할 때 사용
th:if 조건이 true일 때만 표시
th:unless 조건이 false일 때만 표시
th:href 이동경로
th:block 여러 태그를 반복할 경우 사용
th:with 변수값으로 지정
th:object 선택한 객체로 지정
※ 약식으로 대체하기
* 문자로 인식
기본식
th:text="${ }"
대체식
[[ ${ } ]]
* 태그로 인식 (CSS 적용 등)
기본식
th:utext="${ }"
대체식
[( ${ } )]
* th:text=${ } : 타임리프가(서버가) 만든다
- <div>Hi</div>
- <div th:text="' Hi '"></div>
->Hi자리를 대체하는 것
※ 타임리프와 궁합이 좋은 확장프로그램
크롬웹스토어 들어가서 검색하여 추가하기
LiveReload++
- 화면(View)에 변화를 주면, 자동으로 리빌드하고 브라우저에서 리로드
- 프로젝트를 Buile/Restart 해주면 열려있는 웹페이지에 변경 내용이 자동으로 적용되는 확장 프로그램
* 사용하는 법
상단에 고정하고 누르면 초록색 체크가 생김
체크를 하면 한번은 새로 실행해주기
▶ 타임리프 설정하기
▷ Spring Stater Project 만들기
name : HiThymeleaf
group : net.choongang.ewha
artifact : hithymeleaf
package : net.choongang.ewha.hithymeleaf
* 생성 후 settings.gradle의 내용이 프로젝트 이름과 같은지 비교하기
(참고) war는 외장
스프링부트는 내장 was를 사용한다. main으로부터 실행이 된다
▷ 의존성 추가(build.gradle)
- 타임리프 사용을 위한 라이브러리 추가
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
-> 프로젝트 만들때 설정해주기(자동 작성)
-> snippets에 저장하기
▷ HTML파일에 코드 추가
- 타임리프를 적용할 HTML문서에 네임스페이스 추가
xmlns:th="http://www.thymleaf.org"
-> 타임리프의 th속성을 사용하기 위해 선언된 네임스페이스
xmlns="" : url에 따라서 해석
(xml name space의 약어)
(참고)
HTML : Hyper Text Markup Language
XML : eXtensible Markup Language
SGML : Standard Generalized Language
▷ Help > 이클립스 마켓 > 타임리프 검색하여 설치하기
▷ 작동확인
* 시작페이지 만들기
HiThymeleaf/src/main/resources/static에
index.html
-> index가 보이면 서버에러가 아님을 확인 가능
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>시작페이지</h3>
</body>
</html>
▶ 문법 실습
- 순수 HTML페이지에 'th:text' 등의 속성이 발견되면 타임리프 템플릿 기능이 추가된다
- 평상시에 순수 HTML페이지로 작동하다가 'th:...' 속성이 발견되면 서버쪽 뷰 템플릿기능을 추가하는 식으로 동작
-> 사이에 작성되던 문구가 'th:...' 속성에서 나타남
▷ User객체 만들기
- 문법 실습에 활용하기 위한 객체
- 컨트롤러에서 User타입의 user객체를 전송하면 타임리프로는 ${ user }로 객체를 받는다
패키지 : net.choongang.ewha.hithymeleaf.domain
클래스 : User
package net.choongang.ewha.hithymeleaf.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
public class User {
private String name;
private int age;
}
▷ 타임리프 문법을 연습하기위한 컨트롤러와 html파일 만들기
* html
경로 : HiThymeleaf/src/main/resources/templates/pages/greeting
파일명 : hiPage.html
* 컨트롤러
패키지 : net.choongang.ewha.hithymeleaf.controller
클래스 : HiController
▷1. 텍스트 관련
1) th:text
- 텍스트를 표현할 때 사용
- ${ username }이 컨트롤러에서 전송한 값(홍길동)으로 바뀌어 화면에 출력됨
- 약식 활용 : [[${ username }]]
- 태그를 String으로 인식
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- 1. 뷰템플릿에 데이터 전달 -->
<h1 th:text="'Hi ' + ${ username }"></h1>
<h2 th:text="${ username }"></h2>
<h3 th:text="${ username }"></h3>
<h4 th:text="${ username }"></h4>
<h5 th:text="${ username }"></h5>
<h6 th:text="${ username }"></h6>
<!--/* th:text => [[ ]] 으로 대체 */-->
<p>
<h6>[[${ username }]]</h6>
<h5>[[${ username }]]</h5>
<h4>[[${ username }]]</h4>
<h3>[[${ username }]]</h3>
<h2>[[${ username }]]</h2>
<h1>[[${ username }]]</h1>
<hr>
</body>
</html>
* Model : 모델 객체는 뷰(HTML) 쪽으로 값을 넘겨주는 객체
- 인자로 선언하기만 하면 스프링이 알아서 객체를 생성해줌
- addAttribute()메서드로 모델에 값을 저장
- username이라는 키로 홍길동이라는 값을 받음
- 컨트롤러는 모델을 통해 데이터를 설정하고, 모델은 뷰로 이 데이터를 전달해 키에 맞는 데이터를 뷰에서 조회
package net.choongang.ewha.hithymeleaf.controller;
// 뷰 템플릿(HTML코드를 서버에서 생성(SSR)하는 컨트롤러
@Controller
@Slf4j
public class HiController {
// 라우터 매핑 메소드
@GetMapping("/hi")
public String hello(Model model) {
// 1. 뷰템플릿에 데이터 전달
model.addAttribute("username", "홍길동");
}
1-2) 객체 받기
- 컨트롤러에서 User타입의 user객체를 전송하면 타임리프로는 ${ user }로 객체를 받는다
- user의 name가져오기
[객체 가져오는 3가지 방법]
th:text="${객체명.멤버변수}"
th:text="${객체명.get속성메소드()}"
th:text="${객체['속성명']}"
<!-- 2. 객체 전달 -->
<div th:text="${ user.name }"></div>
<div th:text="${ user.getName() }"></div>
<div th:text="${ user['name'] }"></div>
<!-- 약식 -->
<p>
<div>[[ ${ user.name } ]]</div>
<div>[[ ${ user.getName() } ]]</div>
<div>[[ ${ user['name'] } ]]</div>
<hr>
- User.java로 객체 생성하여 활용하기
package net.choongang.ewha.hithymeleaf.controller;
// 뷰 템플릿(HTML코드를 서버에서 생성(SSR)하는 컨트롤러
@Controller
@Slf4j
public class HiController {
// 라우터 매핑 메소드
@GetMapping("/hi")
public String hello(Model model) {
// 2. User객체 생성
User user = new User("황진이", 20);
// user객체를 model에 추가
model.addAttribute(user);
1-3) 날짜 출력(#temporals : 타임리프 내장객체)
- 컨트롤러에서 전송받은 날짜값을 타임리프 내장객체를 활용하여 받기
- year(년도), month(월), day(일), hour(시)
- dayOfWeek : 요일을 수로 리턴(일요일 : 1 ~ 토요일 : 7)
<!-- 기존 방식 :localDateTime -->
<div th:text="${ localDateTime }"></div>
<!-- 약식 -->
<div>[[ ${localDateTime} ]]</div>
<!--/* #temporals : 타임리프 내장객체 */-->
<div th:text="${ #temporals.format(localDateTime, 'yyyy-MM-dd HH:mm:ss')}"></div>
<div th:text="${ #temporals.year(localDateTime) }"></div>
<div th:text="${ #temporals.month(localDateTime) }"></div>
<div th:text="${ #temporals.day(localDateTime) }"></div>
<div th:text="${ #temporals.dayOfWeek(localDateTime) }"></div>
<div th:text="${ #temporals.hour(localDateTime) }"></div>
<!-- 약식 & css 적용 -->
<p>
<div style="" th:style="${ 'color:red; border: 2px solid blue; width:5%;' }">
[[ ${#temporals.year(localDateTime)} ]]
</div>
<div style="background-color: orange; width: 5%;">
[[ ${#temporals.month(localDateTime)} ]]
</div>
<div>[[ ${#temporals.day(localDateTime)} ]]</div>
<div>[[ ${#temporals.dayOfWeek(localDateTime)} ]]</div>
<div>[[ ${#temporals.hour(localDateTime)} ]]</div>
<hr>
- 컨트롤러에서 LocalDateTime.now();를 사용하여 HTML에 날짜 값 전송
package net.choongang.ewha.hithymeleaf.controller;
// 뷰 템플릿(HTML코드를 서버에서 생성(SSR)하는 컨트롤러
@Controller
@Slf4j
public class HiController {
// 라우터 매핑 메소드
@GetMapping("/hi")
public String hello(Model model) {
// 날짜 자료형 다루기
LocalDateTime localDateTime = LocalDateTime.now();
model.addAttribute("localDateTime", localDateTime);
return "pages/greeting/hiPage.html";
}
}
1-4) 연산기능
- HTML페이지에서 바로 사용 가능
- 산술연산 : +, -, *, /, %
- 대소연산, 비교연산 : <, >, <= lt, >= gt, != ...
- 문자열 : ' ', 만일 속성의 따옴표가 작은따옴표면 큰따옴표로 묶어주기
- 문자열 사용시에는 따옴표 사용 필수
-> 숫자를 " ' ' "으로 묶어준다면 문자열로 인식
-> 공백이 허용됨(문자열로 출력하고 싶다면 " ' ' "을 권고)
* 약식형 내부에 따옴표를 사용하면 문자열로 인식을 함( " ' ' " 를 사용하지 않아도 됨)
<div th:text="100 + 200"></div>
<div th:text='100 + 200'></div>
<div th:text="'100 + 200'"></div>
<div th:text=" 문자열입니다. "></div>
<div th:text=' 문자열입니다. '></div>
<div th:text="' 문자열 입니다. '"></div>
<!-- 약식 -->
<!--/*
약식형안에 따옴표를 사용하면 문자열이 된다
*/-->
<p>
<div>[[ ${ 100 + 200 } ]]</div>
<div>[[ ${ 100 + 200 } ]]</div>
<div>[[ ${ "100 + 200" } ]]</div>
<div>[[ ${" 문자열입니다. "} ]]</div>
<div>[[ ${' 문자열입니다. '} ]]</div>
<div>[[ ${"' 문자열 입니다. '"} ]]</div>
<hr>
1-5) 리터럴( || )
- " "로 묶여있는 문자열
- 메모리상에 임의의 위치에 고정된 문자열의 값으로 하드코딩 됨
- | ... |를 사용, 따옴표와 '+연산자'(연결연산자)를 사용하지 않아도 되어 간결(따옴표의 역할)
* 상수문자열(const literal)과 표현식 동시에 출력하기
<!-- 기존 방식 -->
<div th:text="'hello ' + ${ user.name } + '!' "></div>
<!-- 리터럴 활용 -->
<div th:text="|hello ${ user.name }!|"></div>
<!-- 약식 -->
<p>
<div>[[ ${ 'hello '+ user.name + '!' } ]]</div>
<div>[[ |hello ${ user.name } !| ]]</div>
<br>
- 컨트롤러에 있는 1-2)에서 만든 객체 활용하기
package net.choongang.ewha.hithymeleaf.controller;
// 뷰 템플릿(HTML코드를 서버에서 생성(SSR)하는 컨트롤러
@Controller
@Slf4j
public class HiController {
// 라우터 매핑 메소드
@GetMapping("/hi")
public String hello(Model model) {
// User객체 생성
User user = new User("황진이", 20);
// user객체를 model에 추가
model.addAttribute(user);
}
2) th:utext
- 태그를 태그로 인식할 때 사용
- css를 적용하는 등에 활용
- th:text 를 사용하면 태그를 String으로 인식하기 때문에 th:utext 활용
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- 2. 자막자리(element 속성)에 텍스트 출력하기 - HTML태그 적용 가능 -->
<h1 th:utext="${ username2 }"></h1>
<h2 th:utext="${ username2 }"></h2>
<h3 th:utext="${ username2 }"></h3>
<h4 th:utext="${ username2 }"></h4>
<h5 th:utext="${ username2 }"></h5>
<h6 th:utext="${ username2 }"></h6>
<!--/* th:utext => [()] 으로 대체 */-->
<p>
<h1>[( ${ username2 } )]</h1>
<h2>[( ${ username2 } )]</h2>
<h3>[( ${ username2 } )]</h3>
<h4>[( ${ username2 } )]</h4>
<h5>[( ${ username2 } )]</h5>
<h6>[( ${ username2 } )]</h6>
<hr>
</body>
</html>
- username2이라는 키로 심청이이라는 값을 받음
- 값에 css style 적용하기
package net.choongang.ewha.hithymeleaf.controller;
// 뷰 템플릿(HTML코드를 서버에서 생성(SSR)하는 컨트롤러
@Controller
@Slf4j
public class HiController {
// 라우터 매핑 메소드
@GetMapping("/hi")
public String hello(Model model) {
// 2. 뷰템플릿에 화면 데이터 전달
model.addAttribute("username2",
"<i style='color: orange; border-bottom: 3px solid blue;'>심청이</i>");
}
▷ 2. URL이동
1) th:href
- a태그 작성법 : th:href="@{}" 을 이용
<!-- 특정 url로 이동 -->
<a th:href="@{https://naver.com}">네이버</a>
* k1=${ k1 }
-> k1 : 변수, ${ k } : 실제 값
- path variable : 변수로 작용할 수 있다
-> 컨트롤러에서 배웠음 /api/articles/{id}
* list(pid=100, page=3)
? => (쿼리파라미터)로 둘러싼다
& => ,로 바꾼다.
* 경로변수(/path/{id})식으로 경로 마지막 값이 변수인 경우)를 표현하는 법
예: list()/{p1}/{p2} : p1, p2...값이 변수이고 p2도 값이 변하는 변수라면 컨트롤러에서
라우터 매핑 메소드와 똑같은 표현법 사용
@{서비스명/{변수명1}/{변수명2} (변수명1=값1, 변수명2=값2) }
=> 실제 HTML코드 URL로 바꾸면 '/서비스명/값1/값2'
<!--Parameter 추가한 URL 이동 -->
<!-- /* th:href 이용 */ -->
<a th:href="@{/main}">메인으로</a>
<br>
<!-- 주소에 쿼리스트링(서비스명?키1=값1&키2=값2) -->
<a th:href="@{ /main(k1=${k1}, k2=${k2}) }">파라미터로 전송</a>
<br>
<a th:href="@{ /list(pid=100, page=3) }">리스트 서비스의 pid=100이고 page=3인 내용</a>
<br>
<a href="@{/service/{p1}/{p2} (p1=${p1}, (p2=${p2})}">path variable</a>
* 새로운 파일에 시작 : viewLogic.html 만들기
* 컨트롤러에서 매핑해주기
- 컨트롤러에서 지정한 모델 형
- 데이터 추가
@GetMapping("/viewLogic")
public String viewLogic(Model model) {
// user테이블로부터 레코드가 리턴됨
List<User> userList = new ArrayList<>();
// 레코드를 데모로 만들고 userList에 추가
userList.add(new User("홍길동", 10));
userList.add(new User("김철수", 20));
userList.add(new User("김영희", 15));
// 화면모델에 담아 뷰로 넘김
model.addAttribute("users", userList);
return "pages/greeting/viewLogic.html";
}
▷ 3. 반복문
1) th:each
th:each="리스트 값 하나 : ${리스트}"
-> for(var [리스트 값 하나] : 리스트)과 유사
- 컨트롤러의 리스트 데이터 타입을 처리
- 레코드 처리할때
- 주로 테이블의 행 데이터베이스의 레코드
<table>
<!-- 테이블 제목 -->
<thead>
<th>리스트 속성1</th>
<th>리스트 속성2</th>
<th>리스트 속성3</th>
</thad>
<!-- 테이블 내용들 -->
<tbody>
<tr th:each="요소 : ${리스트 모델이름}"
<td th:text="${요소.속성1}">
<td th:text="${요소.속성2}">
<td th:text="${요소.속성n}">
</tr>
</tbody>
</table>
* 사용자 정보 출력하기
<h3>th:each</h3>
<table>
<!-- 제목줄 -->
<tr>
<th>index</th>
<th>username</th>
<th>age</th>
</tr>
<!-- 데이터 -->
<tr th:each="user : ${ users }">
<td th:text="${ userStat.index }"><!-- 번호 출력 --></td>
<td th:text="${ user.name }"><!-- 이름 출력 --></td>
<td th:text="${ user.age }"><!-- 나이 출력 --></td>
</tr>
</table>
<hr>
▷ 4. 화면이 없는 루프로직
1) th:block
- 구분이 되어 나옴
- HTML 태그가 아닌 타임리프의 유일한 자체 태그
- 별도의 태그 없이 제어문 사용
- 렌더링시 제거되는 태그
- 화면에 HTML태그 없이 블럭으로 만들어서 그 블럭속에 HTML태그를 여러개 추가할 수 있게 해주는 그룹태그(화면에 표시가 없음)
- 주로 타임리프의 화면로직에 관련된 태그들과 같이 작업
* <th:block></th:block> 사이의 내용이 반복해서 만들어짐
<h3>th:block</h3>
<th:block th:each="user : ${ users }">
<div>
사용자 이름 : <span th:text="${ user.name }">데이터</span>
사용자 나이 : <span th:text="${ user.age }">데이터</span>
</div>
<div>
요약<span th:text="${ user.name } + '/' + ${ user.age }">사용자 이름 / 사용자 나이</span>
</div>
</th:block>
<hr>
▷ 5. 조건문
- 화면에 출력할지를 결정
5-1) th:if="${조건식}" : 조건식이 참이면 화면출력
5-2) th:unless="${조건식}" : 조건식을 만족하지 않으면(거짓) 화면 출력
* 두 조건식을 비교하여 화면에 출력하기(나이를 기준으로 성인 / 미성년자 구분)
<h3>th:조건문</h3>
<th:block th:each="user : ${ users }">
<div>
사용자 이름 : <span th:text="${ user.name }">데이터</span>
사용자 나이 : <span th:text="${ user.age }">데이터</span>
</div>
<div>
요약 : <span th:text="${ user.name } + '/' + ${ user.age }"></span>
<span th:text="'미성년자'" th:if="${ user.age < 20 }"></span>
<span th:text="'성인'" th:unless="${ user.age < 20 }"></span>
</div>
</th:block>
<hr>
▷ 6. 인라인
1) th:inline
- 타임리프를 편리하게 사용할 수 있는 자바스크립트 인라인 기능을 제공
- 자바스크립트에 변수를 담거나 객체를 담는 경우에 활용
- JSON과 같이 서버에서 생성된 데이터를 직접 자바스크립트의 변수에 일직선으로 대입하여 객체를 생성할 수 있다
* 자바스크립트 인라인 each - 자바스크립트안에서 반복을 해야하는 경우에 사용
<h3>th:inline</h3>
<th:block th:each="user : ${ users }">
<div>
사용자 이름 : <span th:text="${ user.name }">데이터</span>
사용자 나이 : <span th:text="${ user.age }">데이터</span>
</div>
<div>
요약 : <span th:text="${ user.name } + '/' + ${ user.age }"></span>
<span th:text="'미성년자'" th:if="${ user.age < 20 }"></span>
<span th:text="'성인'" th:unless="${ user.age < 20 }"></span>
</div>
<!-- JSON과 같이 서버에서 생성된 데이터를 직접 자바스크립트의 변수에 일직선으로
대입하여 객체를 생성할 수 있다 -->
<script th:inline="javascript">
let username = [[${ user.name }]];
let age = [[${ user.age }]];
// 객체를 직접대입
let user = [[${ user }]];
</script>
</th:block>
▷ 컨트롤러 작성 - 타임리프 연습
- 로그로 출력하여 작동 확인하기
패키지 : net.choongang.ewha.hithymeleaf.controller
클래스 : HelloController
package net.choongang.ewha.hithymeleaf.controller;
@Controller
@Slf4j // 스프링에서 로그를 사용하기 쉽게 만든 인터페이스
// log4j2 logback 어떤 로그라이브러리라도 slf4j규격을 사용하면 동일 사용법 적용
public class HelloController {
@GetMapping("/hello")
// 라우터 주소매핑 메소드의 리턴값을 void라고 하면
// 요청 주소와 뷰템플릿 파일의 이름이 같아야한다
// Model은 UI용 화면과 화면 사이 또는 화면과 컨트롤러사이에
// 임시데이터를 저장하기 위한 버퍼나 캐쉬같은 임시메모리 공간이다. (MVC의 데이터 모델이 아님)
// 서블릿 JSP에서 RequestDispatcher와 같은 역할
// Request객체 레벨의 공유공간
public void hello(Model model) {
log.info("{}", "'/hello'로 요청이 들어왔습니다."); // 콘솔에서 로그로 확인
// 웹 프로토콜의 상태관리(유지)를 위한 상태 트래킹용 공유공간에
// 뷰로 전달할 데이터 임시보관
// model.addAttribute("키변수", "키와 대응하는 값")
model.addAttribute("username", "황진이");
model.addAttribute("age", 20);
// 덧셈
int num1 = 100;
int num2 = 200;
int result = num1 + num2;
model.addAttribute("num1", num1);
model.addAttribute("num2", num2);
model.addAttribute("oper", "+");
model.addAttribute("result", result);
model.addAttribute("data1", "안녕하세요");
model.addAttribute("data2", "<h1 style='color : green;'> 안녕하세요 </h1>");
}
}
* Model
- 화면 작성시 필요한 데이터
- 컨트롤러와 뷰를 연결해주는 모델
- ui패키지에 들어있음
▷ 뷰 작성하기
* view Template(동적파일)의 위치 : src/main/resources/templates
경로 : HiThymeleaf/src/main/resources/templates
파일명 : hello.html
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<!-- 기본주석 -->
<!--/* [[]] */-->
<!--/*--> [[]] <!--*/-->
<h3>이름 : <span th:text="${username}" ></span></h3>
<h3>나이 : <span th:text="${age}"></span></h3>
<hr>
<h3>
<span th:text="${num1}"></span>
<span th:text="${oper}"></span>
<span th:text="${num2}"></span>
<span th:text="${'='}"></span>
<span th:text="${result}"></span>
</h3>
<h5> 대체표현식 사용하기 </h5>
<h3>
<span>[[${num1}]]</span>
<span>[[${oper}]]</span>
<span>[[${num2}]]</span>
<span>[[${'='}]]</span>
<span>[[${result}]]</span>
</h3>
<hr>
<div th:text="${ data1 }">Hi</div>
<div>[[${ data1 }]]</div>
<hr>
<div th:utext="${ data2 }">Hello</div>
<div>[(${ data2 })]</div>
</body>
</html>
* 타임리프 주석
<!-- --> : 기본주석
<!--/*--><!--*/--> : 멀티주석, 특수 기호 등이 포함된 주석문
<!--/* */-->