ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Thymeleaf - 1, 기본 기능
    Spring 2024. 4. 12. 11:25
    728x90
    반응형

    Thymeleaf - 1, 기본 기능

    모든 내용을 다 다루기에는 양이 너무 많으므로,

    내가 생각하기에 유용하거나, 특별한 기능들 위주로 짧게 다루겠다.

     

    1. 타임리프 특징

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

    2. 네츄럴 템플릿

    3. 스프링 통합 지원

     

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

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

     

    1 - 2. 네츄럴 템플릿

    타임리프는 순수 HTML을 최대한 유지하는 특징이 있다.

    타임리프로 작성한 파일은 HTML을 유지하기 때문에 웹 브라우저에서 파일을 직접 열어도 내용을 확인할 수 있고,

    서버를 통해 뷰 템플릿을 거치면 동적으로 변경된 결과를 확인할 수 있다.

     

    JSP를 포함한 다른 뷰 템플릿들은 해당 파일을 열면,

    예를 들어서 JSP 파일 자체를 그대로 웹 브라우저에서 열어보면

    JSP 소스코드와 HTML이 뒤죽박죽 섞여서 웹 브라우저에서 정상적인 HTML 결과를 확인할 수 없다.

    오직 서버를 통해서 JSP가 렌더링 되고 HTML 응답 결과를 받아야 화면을 확인할 수 있다.

     

    반면, 타임리프로 작성된 파일은 해당 파일을 그대로 웹 브라우저에서 열어도 정상적인 HTML 결과를 확인할 수 있다.

    물론 이 경우 동적으로 결과가 렌더링 되지는 않는다.

    하지만 HTML 마크업 결과가 어떻게 되는지 파일만 열어도 바로 확인할 수 있다.

    이렇게 순수 HTML을 그대로 유지하면서 뷰 템플릿도 사용할 수 있는 타임리프의 특징을

    네츄럴 템플릿(natural templates)이라 한다.

     

    1 - 3. 스프링 통합 지원

    타임리프는 스프링과 자연스럽게 통합되고, 스프링의 다양한 기능을 편리하게 사용할 수 있게 지원한다.

    이 부분은 '스프링 통합과 폼' 장에서 자세히 알아보겠다.

     

    2. 기본 표현식

    타임리프는 다음과 같은 기본 표현식들을 제공한다.

     

    2 - 1. 간단한 표현

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

    선택 변수 표현식: *{...}

    메시지 표현식: #{...}

    링크 URL 표현식: @{...}

    조각 표현식: ~{...}

     

    2 - 2. 리터럴

    텍스트

    'one text', 'Another one!'

     

    숫자

    0, 34, 3.0, 12.3

     

    불린

    true, false

     

    널: null

     

    리터럴 토큰

    one, sometext, main

     

    2 - 3. 문자 연산

    문자 합치기

    +

     

    리터럴 대체

    |The name is ${name}|

     

    2 - 4. 산술 연산

    Binary operators

    +, -, *, /, %

     

    Minus sign (unary operator)

    -

     

    2 - 5. 불린 연산

    Binary operators

    and, or

     

    Boolean negation (unary operator)

    !, not

     

    2 - 6. 비교와 동등

    비교

    >, <, >=, <= (gt, lt, ge, le)

     

    동등 연산: ==, != (eq, ne)

     

    2 - 7. 조건 연산

    If-then

    (if) ? (then)

     

    If-then-else

    (if) ? (then) : (else)

     

    Default

    (value) ?: (defaultvalue)

     

    2 - 8. 특별한 토큰

    No-Operation

    _

     

     

    3. HTML 엔티티

    웹 브라우저는 < 를 HTML 테그의 시작으로 인식한다.

    따라서 < 를 테그의 시작이 아니라 문자로 표현할 수 있는 방법이 필요한데,

    이것을 HTML 엔티티라 한다.

     

    그리고 이렇게 HTML에서 사용하는 특수 문자를 HTML 엔티티로 변경하는 것을 이스케이프(escape)라 한다.

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

     

    < 는 &lt;로 표시

    > 는 &gt;로 표시

    기타 수 많은 HTML 엔티티가 있다.

     

    참고

    HTML 엔티티와 관련해서 더 자세한 내용은 HTML 엔티티로 검색해보자.

     

    3 - 1. Unescape

    이스케이프 기능을 사용하지 않으려면 어떻게 해야할까?

    타임리프는 다음 두 기능을 제공한다.

     

    th:text => th:utext

    [[...]] => [(...)]

     

    주의

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

     

     

    4. 리터럴

    타임리프에서 문자 리터럴은 항상 ' (작은 따옴표)로 감싸야 한다.

    그런데 문자를 항상 ' 로 감싸는 것은 너무 귀찮은 일이다.

    <span th:text="'hello'">

     

     

    공백 없이 쭉 이어진다면 하나의 의미있는 토큰으로 인지해 서 다음과 같이 작은 따옴표를 생략할 수 있다.

    룰: A-Z , a-z , 0-9 , [] , . , - , _

    <span th:text="hello">

     

    오류

    문자 리터럴은 원칙상 ' 로 감싸야 한다. 중간에 공백이 있어서 하나의 의미있는 토큰으로도 인식되지 않는다.

    <span th:text="hello world!"></span>

     

    리터럴 대체(Literal substitutions)

    리터럴 대체 문법을 사용하면 마치 템플릿을 사용하는 것 처럼 편리하다.

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

     

     

    5. 연산

    비교연산

    HTML 엔티티를 사용해야 하는 부분을 주의하자

    > (gt),

    < (lt),

    >= (ge),

    <= (le),

    ! (not),

    == (eq),

    != (neq, ne)

     

    Elvis 연산자

    조건식의 편의 버전

    <span th:text="${data}?: '데이터가 없습니다.'"></span>

     

     

    No-Operation

    _ 인 경우 마치 타임리프가 실행되지 않는 것 처럼 동작한다.

    이것을 잘 사용하면 HTML의 내용 그대로 활용할 수 있다.

    마지막 예를 보면 '데이터가 없습니다.' 부분이 그대로 출력된다.

    <span th:text="${nullData}?: _">데이터가 없습니다.</span>

     

     

    6. 반복

    타임리프에서 반복은 th:each 를 사용한다. 추가로 반복에서 사용할 수 있는 여러 상태 값을 지원한다.

     

    반복 상태 유지

    <tr th:each="user, userStat : ${users}">

     

    반복의 두번째 파라미터를 설정해서 반복의 상태를 확인 할 수 있습니다.

    두번째 파라미터는 생략 가능한데, 생략하면 지정한 변수명( user ) + Stat 가 됩니다.

    여기서는 user + Stat = userStat 이므로 생략 가능합니다.

     

    반복 상태 유지 기능

    index

    0부터 시작하는 값

     

    count

    1부터 시작하는 값

     

    size

    전체 사이즈

     

    even , odd

    홀수, 짝수 여부( boolean )

     

    first , last

    처음, 마지막 여부( boolean )

     

    current

    현재 객체

     

     

    7. 자바스크립트 인라인

    타임리프는 자바스크립트에서 타임리프를 편리하게 사용할 수 있는 자바스크립트 인라인 기능을 제공한다.

    자바스크립트 인라인 기능은 다음과 같이 적용하면 된다.

    <script th:inline="javascript">

     

    예시

    @GetMapping("/javascript")
    public String javascript(Model model) {
        model.addAttribute("user", new User("userA", 10));
        addUsers(model);
        return "basic/javascript";
    }
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <!-- 자바스크립트 인라인 사용 전 -->
    <script>
        var username = [[${user.username}]];
        var age = [[${user.age}]];
        //자바스크립트 내추럴 템플릿
        var username2 = /*[[${user.username}]]*/ "test username";
        //객체
        var user = [[${user}]];
    </script>
    <!-- 자바스크립트 인라인 사용 후 -->
    <script th:inline="javascript">
        var username = [[${user.username}]];
        var age = [[${user.age}]];
        //자바스크립트 내추럴 템플릿
        var username2 = /*[[${user.username}]]*/ "test username";
        //객체
        var user = [[${user}]];
    </script>
    </body>
    </html>

     

    자바스크립트 인라인 사용 전 - 결과

    <script>
        var username = userA;
        var age = 10;
        //자바스크립트 내추럴 템플릿
        var username2 = /*userA*/ "test username";
        //객체
        var user = BasicController.User(username=userA, age=10);
    </script>

     

    자바스크립트 인라인 사용 후 - 결과

    <script>
        var username = "userA";
        var age = 10;
        //자바스크립트 내추럴 템플릿
        var username2 = "userA";
        //객체
        var user = {"username":"userA","age":10};
    </script>

     

    자바스크립트 인라인을 사용하지 않은 경우 어떤 문제들이 있는지 알아보고,

    인라인을 사용하면 해당 문제들이 어떻게 해결되는지 확인해보자.

     

    7 - 1. 텍스트 렌더링

    var username = [[${user.username}]];

     

    인라인 사용 전

    var username = userA;

     

    인라인 사용 후 

    var username = "userA";

     

    인라인 사용 전 렌더링 결과를 보면 userA 라는 변수 이름이 그대로 남아있다.

    타임리프 입장에서는 정확하게 렌더링 한 것이지만 아마 개발자가 기대한 것은 "userA"라는 문자일 것이다.

    결과적으로 userA가 변수 명으로 사용되어서 자바스크립트 오류가 발생한다.

     

    다음으로 나오는 숫자 age의 경우에는 " 가 필요 없기 때문에 정상 렌더링 된다.

    인라인 사용 후 렌더링 결과를 보면 문자 타입인 경우 " 를 포함해준다.

     

    추가로 자바스크립트에서 문제가 될 수 있 는 문자가 포함되어 있으면 이스케이프 처리도 해준다.

    예) " => \"

     

    7 - 2. 자바스크립트 내추럴 템플릿

    타임리프는 HTML 파일을 직접 열어도 동작하는 내추럴 템플릿 기능을 제공한다.

    자바스크립트 인라인 기능을 사용하면 주석을 활용해서 이 기능을 사용할 수 있다.

    var username2 = /*[[${user.username}]]*/ "test username";

     

    인라인 사용 전

    var username2 = /*userA*/ "test username";

     

    인라인 사용 후 

    var username2 = "userA";

     

    인라인 사용 전 결과를 보면 정말 순수하게 그대로 해석을 해버렸다.

    따라서 내추럴 템플릿 기능이 동작하지 않고, 심지어 렌더링 내용이 주석처리 되어 버린다.

     

    인라인 사용 후 결과를 보면 주석 부분이 제거되고, 기대한 "userA"가 정확하게 적용된다.

     

    7 - 3. 객체

    타임리프의 자바스크립트 인라인 기능을 사용하면 객체를 JSON으로 자동으로 변환해준다.

    var user = [[${user}]];

     

    인라인 사용 전

    var user = BasicController.User(username=userA, age=10);

     

    인라인 사용 후

    var user = {"username":"userA","age":10};

     

    인라인 사용 전은 객체의 toString()이 호출된 값이다.

    인라인 사용 후는 객체를 JSON으로 변환해준다.

     

    7 - 4. 자바스크립트 인라인 each

    자바스크립트 인라인은 each를 지원하는데, 다음과 같이 사용한다.

    <!-- 자바스크립트 인라인 each -->
    <script th:inline="javascript">
        [# th:each="user, stat : ${users}"]
        var user[[${stat.count}]] = [[${user}]];
        [/]
    </script>

     

    자바스크립트 인라인 each 결과

    <script>
        var user1 = {"username":"userA","age":10};
        var user2 = {"username":"userB","age":20};
        var user3 = {"username":"userC","age":30};
    </script>

     

     

    8. 템플릿 레이아웃

    웹 페이지를 개발할 때는 공통 영역이 많이 있다.

    예를 들어서 상단 영역이나 하단 영역, 좌측 카테고리 등등 여러 페이지에서 함께 사용하는 영역들이 있다.

    이런 부분을 코드를 복사해서 사용한다면 변경시 여러 페이지를 다 수정해야 하므 로 상당히 비효율적이다.

     

    타임리프는 이런 문제를 해결하기 위해 템플릿 조각과 레이아웃 기능을 지원한다.

    th:insert="~{}"
    th:replace="~{}"

    insert는 태그 내부에 삽입,

    replace는 태그를 대체하는 기능이다.

     

    예를 들어서 에 공통으로 사용하는 css , javascript 같은 정보들이 있는데,

    이러한 공통 정보들을 한 곳에 모아두고, 공통으로 사용하지만,

    각 페이지마다 필요한 정보를 더 추가해서 사용하고 싶다면 다음과 같이 사용하면 된다.

    <html xmlns:th="http://www.thymeleaf.org">
    <head th:fragment="common_header(title,links)">
        <title th:replace="${title}">레이아웃 타이틀</title>
        <!-- 공통 -->
        <link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}">
        <link rel="shortcut icon" th:href="@{/images/favicon.ico}">
        <script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script>
        <!-- 추가 -->
        <th:block th:replace="${links}" />
    </head>

     

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head th:replace="template/layout/base :: common_header(~{::title},~{::link})">
        <title>메인 타이틀</title>
        <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
        <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">
    </head>
    <body>
    메인 컨텐츠
    </body>
    </html>

     

    결과

    <!DOCTYPE html>
    <html>
    <head>
        <title>메인 타이틀</title>
        <!-- 공통 -->
        <link rel="stylesheet" type="text/css" media="all" href="/css/awesomeapp.css">
        <link rel="shortcut icon" href="/images/favicon.ico">
        <script type="text/javascript" src="/sh/scripts/codebase.js"></script>
        <!-- 추가 -->
        <link rel="stylesheet" href="/css/bootstrap.min.css">
        <link rel="stylesheet" href="/themes/smoothness/jquery-ui.css">
    </head>
    <body>
    메인 컨텐츠
    </body>
    </html>

     

    common_header(~{::title},~{::link}) 이 부분이 핵심이다.

    ::title 은 현재 페이지의 title 태그들을 전달한다.

    ::link 는 현재 페이지의 link 태그들을 전달한다.

     

    결과를 보자.

    메인 타이틀이 전달한 부분으로 교체되었다.

    공통 부분은 그대로 유지되고, 추가 부분에 전달한 들이 포함된 것을 확인할 수 있다.

     

    이 방식은 사실 앞서 배운 코드 조각을 조금 더 적극적으로 사용하는 방식이다.

    쉽게 이야기해서 레이아웃 개념을 두고,

    그 레이아웃에 필요한 코드 조각을 전달해서 완성하는 것으로 이해하면 된다.

     

    앞서 이야기한 개념을 정도에만 적용하는게 아니라 전체에 적용할 수도 있다.

    728x90
    반응형
Designed by Tistory.