5 분 소요

 인프런 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술편을 학습하고 정리한 내용 입니다.

텍스트 - text, utext

타임 리프의 가장 기본 기능인 텍스트를 출력하는 기능을 알아보자.

타임리프는 기본적으로 HTML 태그의 속성에 기능을 정의해서 동작한다.

HTML의 콘텐츠(content)에 데이터를 출력할 때는 다음과 같이 th:text를 사용하면 된다.

<span th:text="${data}">

HTML 태그의 속성이 아니라 HTML 콘텐츠 영역 안에서 직접 데이터를 출력하고 싶다면

[[...]] 이런 식으로 사용

컨텐츠 안에서 직접 출력하기 = [[${data}]]

이제 직접 테스트 해보자.

hello.thymeleaf.basic.BasicController

1
2
3
4
5
6
7
8
9
10
@Controller  
@RequestMapping("/basic")  
public class BasicController {  
  
    @GetMapping("/text-basic")  
    public String textBasic(Model model) {  
        model.addAttribute("data", "Hello Spring!");  
        return "basic/text-basic";  
    }  
}

간단한 컨트롤러 만들었다.

resources/templates/basic/text-basic.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>  
<html lang="en" xmlns:th="http://www.thymeleaf.org">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</title>  
</head>  
<body>  
<h1>컨텐츠에 데이터 출력하기</h1>  
<ul>  
    <li>th:text 사용 <span th:text="${data}"></span></li>  
    <li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>  
</ul>  
</body>  
</html>

다음과 같이 두 케이스를 보면

잘 나오는 모습이다.

Escape

HTML 문서는 <, >같은 특수 문자를 기반으로 정의된다.

따라서 뷰 템플릿으로 HTML 화면을 생성할 때는 출력하는 데이터에 이러한 특수 문자가 있는 것을 주의해서 사용해야 한다.

앞에서 만든 예제의 데이터를 다음과 같이 변경해 보자.

1
model.addAttribute("data", "Hello <b>Spring!</b>");  

나는 Hello Spring! 이런 식으로 출력 되길 원하지만, 실제로는

이런 식으로 그대로 나와버리고, 소스를 확인해 보면

치환돼서 나와버린다.

HTML 엔티티

웹 브라우저는 <를 HTML 태그의 시작으로 인식한다. 그래서 <를 태그의 시작이 아니라 문자로 표현할 수 있는 방법이 필요한데, 이것을 HTML 엔티티라 한다. 그리고 이렇게 HTML에서 사용하는 특수 문자를 HTML 엔티티로 변경하는 것을 이스케이프 (escape)라 한다.

그리고 타임리프가 제공하는 th:text, [[...]]기본적으로 이스케이프를 제공한다.

Unescape

이스케이프 기능을 사용하지 않으려면,

타임리프에선 다음 두 기능을 이용하자.

  • th:textth:utext
  • [[...]][(...)]
1
2
3
4
5
@GetMapping("/text-unescaped")  
public String textUnescaped(Model model) {  
    model.addAttribute("data", "Hello <b>Spring!</b>");  
    return "basic/text-unescaped";  
}

컨트롤러에 다음과 같이 추가하고,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!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>

th:inline="none" : 타임리프는 [[...]] 를 해석하기 때문에, 화면에 [[...]] 글자를 보여줄 수 없다.

이 태그 안에서는 타임리프가 해석하지 말라는 옵션이다

내가 원하는 대로 나왔다.

주의!!
실제 서비스를 개발하다 보면 escape를 사용하지 않아서 HTML이 정상 렌더링 되지 않는 수 많은 문제가 발생한다. escape를 기본으로 하고, 꼭 필요할 때만 unescape를 사용하자.

변수 - SpringEL

타임리프에서 변수를 사용할 때는 변수 표현식을 사용한다.

변수 표현 식 : ${...}

그리고 이 변수 표현 식에는 스프링 EL이라는 스프링이 제공하는 표현 식을 사용할 수 있다.

BasicController 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@GetMapping("/variable")  
public String variable(Model model) {  
    User userA = new User("userA", 10);  
    User userB = new User("userB", 20);  
  
    List<User> list = new ArrayList<>();  
    list.add(userA);  
    list.add(userB);  
  
    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;  
    }  
}

모델에 다양한 자료구조(객체, 리스트, 맵)을 넣어서 타임리프에서 표현해 보자.

resources/templates/basic/variable.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>  
<html xmlns:th="http://www.thymeleaf.org">  
<head>  
    <meta charset="UTF-8">  
    <title>Title</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>  
  
<h1>지역 변수 - (th:with)</h1>  
<div th:with="first=${users[0]}">  
    <p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>  
</div>  
  
</body>  
</html>

이제 하나씩 보자.

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를 사용하면 지역 변수를 선언해서 사용할 수 있다.

지역 변수는 선언한 태그 안에서만 사용할 수 있다.

1
2
3
4
<h1>지역 변수 - (th:with)</h1>  
<div th:with="first=${users[0]}">  
    <p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>  
</div> 

기본 객체들

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

  • ${#request} - 스프링 부트 3.0부터 제공하지 않는다.
  • ${#response} - 스프링 부트 3.0부터 제공하지 않는다.
  • ${#session} - 스프링 부트 3.0부터 제공하지 않는다.
  • ${#servletContext} - 스프링 부트 3.0부터 제공하지 않는다.
  • ${#locale}

BasicController 추가

1
2
3
4
5
6
7
8
9
10
11
12
@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;  
    }  
}

resources/templates/basic/basic-objects.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!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>

결론부터 말하자면 스프링 부트 3.0 이상에선 오류난다.

스프링 부트 3.0 이상이라면 다음과 같이 작성한다. (미만이라면 위에 코드처럼 작성 가능)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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;  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!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>

스프링 부트 3.0이라면 직접 model 에 해당 객체를 추가해서 사용해야 한다.

이제 잘 나온다.

편의 객체

1
2
3
4
5
6
<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>  

지금 호출 url이

  • http://localhost:8080/basic/basic-objects?paramData=HelloParam

다음과 같다.

<span th:text="${param.paramData}"></span> 다음과 같이 호출할 수 있다.

정리

  • HTTP 요청 파라미터 접근 : param
    • 예) ${param.paramData}
  • HTTP 세션 접근 : session
    • 예) ${session.sessionData}
  • 스프링 빈 접근 : @
    • 예) ${@helloBean.hello('Spring !')}

댓글남기기