공부함

타임리프 본문

스프링/스프링mvc2

타임리프

찌땀 2023. 10. 9. 18:58

https://www.inflearn.com/course/lecture?courseSlug=%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2&unitId=83253&tab=curriculum

 

학습 페이지

 

www.inflearn.com

 

 

타임리프란?

✅ SSR 

백엔드 서버에서 HTML을 동적으로 렌더링 하는데 사용 

✅ 네츄럴 탬플릿 

순수 HTML을 유지하는 특징이 있다.

JSP 같은 경우 웹브라우저를 거치지 않고 그냥 파일을 열면 코드가 전부 깨진다.

타임리프는 HTML을 유지기 때문에 열어서 보면 렌더링 해서 잘 보인다. 

물론 그냥 연다고 동적 렌더링이 되는 것은 아니지만 마크업 한 내용을 볼 수 있다.

✅ 스프링 통합 지원 

스프링과 통합되어서 편리한 기능을 많이 제공한다 

✅ 타임리프 사용 선언

<html xmlns:th="http://www.thymeleaf.org">

 

text, utext 

@GetMapping("text-basic")
public String textBasic(Model model){
    model.addAttribute("data","Hello Spring");
    return "basic/text-basic";
}

컨트롤러에서는 위와 같이 model에 값을 넣어 뷰로 넘겨준다.

 

<li>th:text 사용 <span th:text="${data}"></span></li>
<li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>

타임리프에서 두가지 방법으로 model에 넘겨준 값을 사용할 수 있다.

th:text="${attribute명}" 문법을 사용할 수 있다.

이 문법은 태그가 반드시 있어야 하며 속성값을 통해 사용한다.

컨텐츠 안에서 직접 출력하려면 [[${data}]] 문법을 사용할 수 있다. 

 

렌더링 결과는 아래와 같다.

escape

    @GetMapping("text-basic")
    public String textBasic(Model model){
        model.addAttribute("data","Hello <b>Spring</b>");
        return "basic/text-basic";
    }

Spring에 굵은 글자 효과를 의도하고 모델에 이렇게 데이터를 전달해도 실제 렌더링 된 결과는 <b>Spring</b> 이다. 

타임리프는 html 엔티티 (html에서 구조를 정하기 위해 쓰는 기호들)에 대해 이렇게 자동으로 이스케이프 처리를 해준다. 

 

unescape

그렇다면 이스케이프 처리를 하지 않고 싶다면 ? 두가지 방법이 있다.

<li>th:text 사용 <span th:utext="${data}"></span></li>
<li>컨텐츠 안에서 직접 출력하기 = [(${data})]</li>

th:text 대신 th:utext 속성을 사용하거나, [[ .. ]] 대신 [( .. )]를 사용해주면 된다.

 

주의 : 꼭 필요할 때만 unescape를 사용하자!

변수 Spring EL

변수 표현식 : ${ .. }

타임리프의 변수 표현식 안에는 SpringEL이라는 스프링이 제공하는 표현식을 사용할 수 있다.

@GetMapping("/variable")
public String variable(Model model){
    User userA = new User("userA", 10);
    User userB = new User("userA", 10);

    ArrayList<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;
    }
}

model에 User 객체, User 객체의 리스트, User 객체의 Map을 각각 넣어줬다. 

 

SpringEL을 사용해서 객체의 값에 접근할 수 있다.

 

기본 객체들 

@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;
    }
}

컨트롤러에서 HttpServletRequest, HttpServletResponse, HttpSession을 받아서 session.setAttribute, model.addAttribute로 값을 넣어준다. 그리고 HelloBean을 컴포넌트 스캔에 의해 빈으로 등록해준다.

session은 사용자가 웹 브라우저를 끄지 않는 이상 계속 유지되는 데이터다. 

반면 http request, response는 요청,응답이 끝나면 사라진다. 

타임리프에서 session의 데이터를 꺼낼 수 있다. 

 

<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>

html 코드와 렌더링 결과는 이러하다.

${request}, ${response} 객체는 톰캣에서 제공하는 구현체인 Facade 객체가 들어온다.  

편의 객체를 사용해 요청 파라미터를 꺼낼 수 있다.

요청을 ?paramData=HelloParam으로 보냈기 때문에 위와 같이 렌더링 된 것이다. 

세션에 넣은 값을 ${session.sessionData}로 꺼냈다. 

마지막으로 등록된 스프링 빈을 꺼내서 메서드를 호출할 수 있다. 

 

유틸리티 객체

다양한 유틸리티 객체를 제공한다. 필요할 때 찾아서 쓰자.

 

url 

@{ ... } 문법을 사용해 타임리프에서 url 링크를 표현한다.

@{/hello}
@{/hello(username=${name})}
@{/hello/{name}}
@{/hello/{name}(username=${name},age=${age})}

차례대로

/hello

/hello?username=jidam

/hello/jidam

/hello/jidam?username=jidam&age=20

과 같이 적용된다. 경로변수와 쿼리파라미터를 활용할 수 있다. 

 

리터럴 

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

타임리프에는 문자, 숫자, 불린, null 리터럴이 있다 

문자 리터럴은 항상 ' ' 로 감싸야 한다. 

공백이 없는 문자 리터럴의 경우 ' ' 를 생략할 수 있다. 

| | 를 사용해서 리터럴 대체 문법을 사용할 수 있다.

 

속성 값 설정 

렌더링 전 
<input type="text" name="mock" th:name="jidam"/>
렌더링 후 
<input type="text" name="jidam"/>

타임리프는 html 태그에 th:* 속성을 지정해서 동작한다. 

기존 속성을 대체하거나 없으면 새로 만든다. 

 

반복문, 조건문 

<tr th:each="user : ${users}">
    <td th:text="${user.username}">username</td>
    <td th:text="${user.age}">0</td>
</tr>

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

반복문은 th:each 문법을 사용한다. 

반복의 두번째 파라미터로 반복 상태를 확인할 수 있다. 

${userStat.index} , ${userStat.count} : 각각 0, 1부터 시작 

 

<span th:text="'미성년자'" th:if="${user.age lt 20}"></span>
<span th:text="'미성년자'" th:unless="${user.age ge 20}"></span>

조건문은 th:if와 th:unless를 사용할 수 있다.

 

th:block

타임리프 속성을 사용하기 위해서는 html 태그 안에 속성으로 기능을 정의해서 사용한다. 

그런데 html 태그를 따로 사용하기 애매한 경우는 <th:block th:each="user : ${users}">와 같이 타임리프의 자체 태그를 활용할 수 있다. <th:block>은 렌더링 시 제거된다. 

템플릿 조각 

<footer th:fragment="copy">
    푸터 자리 입니다.
</footer>
<footer th:fragment="copyParam (param1, param2)">
    <p>파라미터 자리 입니다.</p>
    <p th:text="${param1}"></p>
    <p th:text="${param2}"></p>
</footer>

footer.html에 위와 같이 th:fragment 속성과 값을 준다.

<h2>부분 포함 insert</h2>
<div th:insert="~{template/fragment/footer :: copy}"></div>
<h2>부분 포함 replace</h2>
<div th:replace="~{template/fragment/footer :: copy}"></div>
<h1>파라미터 사용</h1>
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터2')}"></div>

 fragmentMain.html에서 템플릿 조각들을 가져다 쓸것이다. 

부분 포함 insert의 경우 <div> 태그 안에 template/fragment/footer 파일의 fragment 속성값의 이름이 copy 인 태그를 가져와 넣는 것이다.

replace의 경우 <div> 태그를 <footer>가 아예 대체해버리게 된다. 

또한 파라미터도 사용할 수 있다.

 

이렇게 재사용 해야 하는 템플릿들을 구조화 해 놓고 가져다 쓸 수 있다.

 

템플릿 레이아웃 

css, js같은 파일을 공통으로 쓰고 싶을 때 

header에 정해진 틀이 있고 거기에 동적으로 변경을 주고 싶을 때 

큰 모양이 있고 그 모양에 내 조각을 맞춰 넣는다 ? . ? 

 

<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>

 

위 파일이 base.html이고 이 파일을 layoutMain.html에서 가져다 쓴다.

보면 title,links를 파라미터로 받는다. 

<!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>

 layoutMain.html은 위와 같다. head 태그가 base.html로 replace 된다.

이 때 파라미터로 ~{::title},~{::link}와 같이 전달해 준다. 이렇게 전달하면 파라미터로 태그 자체가 전달된다

즉 <title>메인 타이틀</title> 전체가 파라미터로 전달된다. 

link 같은 경우 태그가 2개인데 두개 다 전달되어서 한줄 한줄 렌더링 되어 나오게 된다. 

 


이번에는 html 파일 전체를 레이아웃으로 만들고 거기에 원하는 내용을 넣는 방식으로 진행해보자.

 

<!DOCTYPE html>
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
    <title th:replace="${title}">레이아웃 타이틀</title>
</head>
<body>
<h1>레이아웃 H1</h1>
<div th:replace="${content}">
    <p>레이아웃 컨텐츠</p>
</div>
<footer>
    레이아웃 푸터
</footer>
</body>
</html>

layoutFile.html이다. 사이트에 100개의 페이지가 있고 모두 이러한 구성이라고 가정하자. 

바뀌는 부분은 "레이아웃 타이틀" , "레이아웃 컨텐츠" 부분 뿐이다. 

 

<!DOCTYPE html>
<html th:replace="~{template/layoutExtend/layoutFile :: layout(~{::title},~{::section})}" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>메인 페이지 타이틀</title>
</head>
<body>
<section>
    <p>메인 페이지 컨텐츠</p>
    <div>메인 페이지 포함 내용</div>
</section>
</body>
</html>

실제 페이지인 layoutExtendMain.html은 이렇게 생겼다.

보면 html 태그 자체를 th:replace로 layoutFile.html로 replace 해버린다. 그리고 이 때 title과 section 태그는 파라미터로 넘긴다. 

 

이렇게 layout을 적극 활용하면 중복되는 레이아웃을 한번에 수정, 관리할 수 있어서 편리하다!

페이지가 많지 않을 때는 템플릿 조각을 써도 괜찮다.

페이지가 많아지면 템플릿 레이아웃으로 관리하자.

 

 

'스프링 > 스프링mvc2' 카테고리의 다른 글

메세지, 국제화  (0) 2023.10.11
타임리프 스프링 통합, 폼  (0) 2023.10.10