생각해 보니까 지금까지 혼자서 리액트나 팀프로젝트만 했지 내가 서버 측에서 프론트 작업을 크게 해 본 적이 없다는 것을 깨달았다. 그래서 혼자 간단한 프로젝트 진행을 할 수도 있으니 이 기회에 타임리프 사용법에 대해 제대로 배워보기로 했다.
초기 설정
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
}
Spring MVC와 연결할 타임리프 라이브러리를 추가한다.
타임리프 파일은 html로 만든다. 그러니 html 확장자로 생성해주면 된다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>
위처럼 html 태그 내에 타임리프 주소를 지정해주면 타임리프 문법을 사용할 수 있다.
text
text는 태그로 감싼 내용을 덮어 쓸 수 있는 데이터를 출력하는 기능이다.
@GetMapping("text-basic")
public String textBasic(Model model) {
model.addAttribute("data", "Hello Spring!!");
return "basic/text-basic";
}
<li>th:text 사용 <span th:text="${data}"></span></li>

th:text=""
를 이용하면 된다.
위 예시처럼 내부에는 컨트롤러에서 전달한 값이 될 수 있는데 이 때는 해당 key를 ${}로 감싸면 값이 출력된다.
utext
text는 특수문자도 그대로 출력되는 escape 처리가 되어있어서 내부에 특정 태그를 집어넣으면 태그 그 자체가 출력된다. utext는 unscape 처리를 하여 실제 html 요소들이 처리되는 것 같이 출력해 준다.
@GetMapping("text-unescaped")
public String textUnescaped(Model model) {
model.addAttribute("data", "Hello <b>Spring!!</b>");
return "basic/text-unescaped";
}
<ul>
<li>th:text = <span th:text="${data}"></span></li>
<li>th:utext = <span th:utext="${data}"></span></li>
</ul>

위의 결과를 보면 utext를 사용하면 <b></b>가 실제 태그로 작성했을 때처럼 작동하는 것을 알 수 있다.
자주 사용하는 text에 escape 처리를 한 이유는 아마 악용 때문일 것이다.
만약 이런 처리를 하지 않으면 사용자가 게시글 내에서 각종 태그와 스크립트로 여러 장난질을 할 수도 있을 것이다.
[[...]] vs [(...)]
<ul>
<li><span th:inline="none">[[...]] = </span>[[${data}]]</li>
<li><span th:inline="none">[(...)] = </span>[(${data})]</li>
</ul>

text를 [[]]로,
utext [()]로 변경하여 사용할 수 있다.
SpringEL
SpringEL은 런타임에 객체를 조작할 수 있도록 해주는 표현 언어라고 한다.
타임리프에서는 SpringEL을 통해서 변수 표현식을 사용할 수 있다.
@Data
static class User {
private String username;
private int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
}
@GetMapping("/variable")
public String variable(Model model) {
User userA = new User("AAA", 15);
User userB = new User("BBB", 27);
List<User> list = List.of(userA, userB);
Map<String, User> map = Map.of("userA", userA, "userB", userB);
model.addAttribute("user", userA);
model.addAttribute("users", list);
model.addAttribute("userMap", map);
return "basic/variable";
}
<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>

컨트롤러에서 전달한 객체에 접근하는 방법은 위와 같이 여러 가지가 있다.
- 변수명을 직접 작성 -> user.username
- 배열 + Map 접근법처럼 작성 -> user['username']
- 자바 빈 프로퍼티 규약을 따르는 Getter를 통해 접근 -> user.getUsername()
지역 변수
<div th:with="first=${users[0]}">
<p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>
th:with를 통해 지역 변수를 생성할 수 있다.
생성된 지역 변수는 해당 태그 내에서만 사용 가능하다.
각종 Scope
// Spring Boot 3.0 이상
@GetMapping("/basic-objects")
public String basicObjects(Model model, HttpServletRequest request,
HttpServletResponse response, HttpSession session) {
session.setAttribute("sessionData", "Hello Session");
model.addAttribute("request", request);
model.addAttribute("response", response);
model.addAttribute("servletContext", request.getServletContext());
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>
<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>

전에는 Session, Request, Response 등의 각종 Scope의 기본 객체들을 타임리프에서 제공했지만, 스프링 3.0부터는 사용이 불가능하여 위와 같이 컨트롤러에서 직접 추가해주어야 한다.
HttpServletRequest와 같은 객체는 request.getParameter()와 같이 데이터를 꺼내는 과정이 번거롭기 때문에 편의 객체를 통해 쉽게 접근할 수 있다.
- paramData는 파라미터의 쿼리 파라미터 정보를 제공
- sessionData는 세션 정보를 제공
- @를 사용하면 스프링 빈의 객체를 제공
날짜
@GetMapping("/date")
public String date(Model model) {
model.addAttribute("localDateTime", LocalDateTime.now());
return "basic/date";
}
<ul>
<li>default = <span th:text="${localDateTime}"></span></li>
<li>yyyy-MM-dd HH:mm:ss = <span th:text="${#temporals.format(localDateTime, 'yyyy-MM-dd HH:mm:ss')}"></span></li>
</ul>
<h1>LocalDateTime - Utils</h1>
<ul>
<li>${#temporals.day(localDateTime)} = <span th:text="${#temporals.day(localDateTime)}"></span></li>
<li>${#temporals.month(localDateTime)} = <span th:text="${#temporals.month(localDateTime)}"></span></li>
<li>${#temporals.monthName(localDateTime)} = <span th:text="${#temporals.monthName(localDateTime)}"></span></li>
<li>${#temporals.monthNameShort(localDateTime)} = <span th:text="${#temporals.monthNameShort(localDateTime)}"></span></li>
<li>${#temporals.year(localDateTime)} = <span th:text="${#temporals.year(localDateTime)}"></span></li>
<li>${#temporals.dayOfWeek(localDateTime)} = <span th:text="${#temporals.dayOfWeek(localDateTime)}"></span></li>
<li>${#temporals.dayOfWeekName(localDateTime)} = <span th:text="${#temporals.dayOfWeekName(localDateTime)}"></span></li>
<li>${#temporals.dayOfWeekNameShort(localDateTime)} = <span th:text="${#temporals.dayOfWeekNameShort(localDateTime)}"></span></li>
<li>${#temporals.hour(localDateTime)} = <span th:text="${#temporals.hour(localDateTime)}"></span></li>
<li>${#temporals.minute(localDateTime)} = <span th:text="${#temporals.minute(localDateTime)}"></span></li>
<li>${#temporals.second(localDateTime)} = <span th:text="${#temporals.second(localDateTime)}"></span></li>
<li>${#temporals.nanosecond(localDateTime)} = <span th:text="${#temporals.nanosecond(localDateTime)}"></span></li>
</ul>

타임리프는 자바 8의 LocalDateTime과 같은 날짜, 시간 관련 처리를 도와주는 유틸리티 객체를 제공한다.
#temporal이 날짜 처리를 도와주는 유틸리티 객체이다.
그 외에도 #message나 #numbers 등과 같은 많은 유틸리티 객체를 제공한다.
URL
@GetMapping("/link")
public String link(Model model) {
model.addAttribute("param1", "data1");
model.addAttribute("param2", "data2");
return "basic/link";
}
<ul>
<li><a th:href="@{/hello}">basic url</a></li>
<li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query param</a></li>
<li><a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">path variable</a></li>
<li><a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">path variable + query parameter</a></li>
</ul>


@{}로 감싼 내용을 링크로 사용하는 기능이다.
기능은 크게 Path Variable과 Query Parameter를 붙이는 것으로 나눌 수 있다.
Path Variable
{} 내에 원하는 경로명을 지정한다.
마지막에 () 내에 경로명=${변수명}과 같이 작성하면 변수명으로 경로가 추가된다. 여러 개를 잇고 싶으면 소괄호 내에서 콤마로 구분하면 된다.
Query Parameter
앞서 Path Variable에서 앞의 중괄호로 감싼 내용을 없애고 뒤의 () 부분만 추가하면 해당 이름과 값으로 Query Parameter가 추가된다.
두 기능을 혼합하여 사용할 수도 있다.
다음 게시글
https://megamaker.tistory.com/399
[Spring] 타임리프(Thymeleaf) 알아보기 2
https://megamaker.tistory.com/397 [Spring] 타임리프 알아보기 1생각해 보니까 지금까지 혼자서 리액트나 팀프로젝트만 했지 내가 서버 측에서 프론트 작업을 크게 해 본 적이 없다는 것을 깨달았다. 그래
megamaker.tistory.com
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의 | 김영한 - 인프런
김영한 | 웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습
www.inflearn.com
'공부 > Spring' 카테고리의 다른 글
| [Spring] STOMP 웹소켓 채팅 테스트해보기 (0) | 2024.09.20 |
|---|---|
| [Spring] 타임리프(Thymeleaf) 알아보기 2 (0) | 2024.09.19 |
| [Spring Boot] 그라파나(Grafana) 대시보드 사용하기 (0) | 2024.09.15 |
| [Spring Boot] 마이크로미터(Micrometer), 프로메테우스(Prometheus) 알아보기 (0) | 2024.09.09 |
| [Spring Boot] Environment, @Value, @ConfigurationProperties (0) | 2024.09.07 |