1. 템플릿 엔진
뷰는 사용자에게 보여줄 내용을 처리 결과 데이터를 가지고 HTML을 생성해서 클라이언트에 응답을 알려주는 역할을 한다.
템플릿 엔진에 대해 간단하게 설명하면 '데이터를 미리 정의된 템플릿에 바인딩해서 뷰의 표시를 도와주는 것'이다.
바인딩이란 어떤 요소나 데이터, 파일 등을 서로 연결하는 것을 의미한다.
2. 타임리프
- HTML 기반의 템플릿 엔진으로, 정해진 문법으로 작성하면 페이지를 동적으로 구현할 수 있다.
- HTML을 기반으로 하기 때문에 최종 출력을 생각하면서 뷰를 생성할 수 있다.
즉, 타임리프를 사용하면 디자이너와 쉽게 분업할 수 있다.
타임리프에 대해 학습하기 전에 반드시 기억해야 할 것은 모델이다.
스프링 MVC가 요청을 받고 응답을 보낼 때까지의 흐름에 대해 다시 한 번 복습하고 진행한다.
- DispatcherServlet이 클라이언트로부터 요청을 받는다.(프런트 컨트롤러 패턴)
- DispatcherServlet이 컨트롤러의 요청 핸들러 메서드를 호출한다.
- 컨트롤러는 비즈니스 로직 처리를 실행하여 처리 결과를 취득한다.
- 처리 결과를 Model로 설정하고 뷰 이름을 반환한다.
- DispatcherServlet은 뷰 이름에 대응하는 뷰에 대해서 화면 표시 처리를 의뢰한다.
- 클라이언트가 응답을 받고 브라우저에 화면이 표시된다.
3. Model 인터페이스
Model 인터페이스의 특징에 대해 간단하게 설명하면 다음과 같다.
- 처리한 데이터를 뷰에 표시하고 싶을 경우 데이터를 전달하는 역할을 수행한다.
- 스프링 MVC에 의해 관리되며, 수동 혹은 자동으로 객체를 저장하고 관리하는 기능을 제공한다.
- Model을 이용하고 싶은 경우 요청 핸들러 메서드의 인수에 Model 타입을 전달한다.
그러면 스프링 MVC가 자동으로 Model 타입 인스턴스를 설정한다.
3.1 addAttribute
Model 객체를 저장하기 위한 메서드는 여러 가지가 존재한다. 다음의 메서드 시그니처는 반드시 기억해야 한다.
Model addAttribute(String name, Ojbect value);
- name: 이름(별명)
- value: 값(저장하고 싶은 객체)
addAttribute는 스프링 MVC에서 모델에 데이터를 추가하는 메서드이다. 모델은 컨트롤러에서 뷰로 데이터를 전달하는 데 사용되는 객체이며, addAttribute를 통해 모델에 속성(attribute)을 추가할 수 있다. 일반적으로 컨트롤러에서 처리한 결과나 화면에 표시할 데이터를 모델에 추가한 후, 이 모델은 뷰에 전달되어 클라이언트에게 화면이나 정보를 보여주게 된다.
3.2 addAttribute 예제 1
아래는 간단한 예제를 통해 addAttribute의 사용을 설명한다.
여기서는 스프링 MVC의 컨트롤러 메서드 내에서 사용되는 경우를 살펴본다.
@Controller
@RequestMapping("/example")
public class ExampleController {
@GetMapping("/showData")
public String showData(Model model) {
// 모델에 데이터 추가
model.addAttribute("message", "Hello, World!");
model.addAttribute("count", 42);
// 뷰 이름 반환
return "dataView";
}
}
위 코드에서 showData라는 메서드는 "/example/showData" 경로에 대한 GET 요청을 처리하는 메서드이다. Model 객체를 메서드 매개변수로 받아와서, 이 모델에 addAttribute를 사용하여 두 개의 속성("message"와 "count")을 추가하고 있다.
이후 메서드는 문자열 "dataView"를 반환한다. 이 문자열은 실제로 뷰의 이름을 나타낸다. 스프링 MVC는 이 뷰의 이름을 기반으로 실제 뷰를 찾아 렌더링하고, 그 과정에서 모델에 추가된 데이터를 사용한다.
따라서 "dataView"라는 뷰에서는 "message"와 "count"라는 속성을 사용하여 화면에 정보를 표시할 수 있다. 예를 들어, Thymeleaf와 같은 템플릿 엔진을 사용한다면 해당 속성을 템플릿에서 가져와 동적으로 화면을 구성할 수 있다.
3.3 addAttribute 예제 2
@controller
@RequestMapping("hello")
public class HelloModelController {
@GetMapping("model")
public String helloView(Model model) {
// Model에 데이터를 저장
model.addAttribute("msg", "타임리프!!!");
// 반환값으로 뷰 이름을 반환
return "helloThymeleaf";
}
}
타임 리프를 사용하는 경우, 컨트롤러는 뷰에서 표시할 데이터를 준비해야 한다. 그때 Model 인터페이스를 통해 뷰에 표시할 데이터를 준비할 수 있다. Model 인터페이스를 사용하려면 요청 핸들러 메서드의 인수에 Model 타입을 전달한다. 전달되면 스프링 MVC가 자동으로 Model 타입의 인스턴스를 설정하므로 Model의 addAttribute 메서드를 사용할 수 있다.
HelloModelController 클래스에서는 클래스에 @RequestMapping("hello") 어노테이션을 부여하고, 요청 핸들러 메서드에 @GetMapping("model") 어노테이션을 부여한다. 클라이언트로부터 URL(http://localhost:8080/hello/model)이 GET 메서드로 송신되면 HelloModelController 클래스의 helloView 메서드가 호출되어 반환값으로 뷰 이름을 돌려주는 것으로 그에 대응되는 뷰가 표시된다.
4. 뷰 생성
helloView 메서드의 반환값(뷰 이름:helloThymeleaf)에 대응하는 helloThymeleaf.html을 생성한다.
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello Thymeleaf</title>
</head>
<body>
<h1 th:text="${msg}">표시되는 부분</h1>
</body>
</html>
타임리프의 기능은 th:xxx속성명 형식으로 입력한다.
속성:기능 | 개요 |
th:text | 속성값에 지정된 값을 새니타이즈(Sanitize)하여 출력 |
th:utext | 속성값에 지정된 값을 출력(새니타이즈 안 함) |
새니타이즈(Sanitize)는 위험한 코드나 변환 또는 제거하여 무력화하는 것이다. 간단히 설명하면 '특별한 의미를 가진 문자의 특별함을 무효화하고 의도하지 않은 움직임을 봉쇄'하는 것이다. 새니타이즈는 웹사이트의 입력 폼을 통해 악의적인 코드가 입력되었을 때 등에 유용하게 사용된다.
4.1 처리 흐름
요청 핸들러 메서드에서 뷰로 표시하고 싶은 데이터를 Model의 addAttribute(이름, 값) 메서드를 사용해서 저장하고, 타임리프에서 데이터를 표시할 위치를 ${이름} 형식으로 설정한다. 주의할 점은 뷰에서 사용할 수 있는 것은 addAttribute(이름, 값)의 이름 부분이라는 것이다.
5. 타임리프 사용법
타임리프의 특징을 간단하게 설명하면 다음과 같다.
- 스프링 부트에서 추천하는 템플릿 엔진이다.
- HTML로 템플릿을 작성하기 때문에 웹 브라우저로 파일의 내용을 표시하고 확인하면서 뷰를 생성할 수 있다.
- 스프링 부트의 기본 설정으로 templates 폴더 아래에 '요청 핸들러 메서드의 반환값 + .html' 파일이 참조된다.
5.1 직접 문자를 삽입
직접 설정한 문자를 출력할 때는 th:text="출력 문자"로 문자를 출력할 수 있다.
속성값의 값 설정에 큰따옴표" "를 사용하므로 문자를 설정할 때는 작은따옴표' '로 둘러싼다.
<!-- 01: 직접 문자를 삽입 -->
<h1 th:text="hello world">표시하는 부분</h1>
5.2 인라인 처리
[[ ${ } ]] 를 사용하면 태그를 속성에 추가하는 대신 본문에 변수를 포함할 수 있다.
고정값과 변수를 조합하고 싶은 경우에는 이 방법이 편리하다.
<!-- 02: 인라인 처리 -->
<h1>안녕하세요! [[$(name)]] 씨 </h1>
5.3 값 결합
+를 이용해서 값을 결합할 수 있다.
<!-- 03: 값 결합 -->
<h1 th:text="'오늘의 날씨는 ' + '맑음 ' + '입니다'">표시하는 부분</h1>
5.4 값 결합(리터럴 치환)
값 결합은 리터럴 치환을 사용하는 것으로 | 문자 |로 기술할 수 있다. 문자 안에서 ${} 표현식도 함께 사용이 가능하다다.
<!-- 04: 값 결합(리터럴 치환)-->
<h1 th:text="|안녕하세요 ${name}씨|">표시하는 부분</h1>
5.5 지역 변수
th:with = "변수 이름 = 값"으로 변수에 값을 할당할 수 있다. 변수의 범위는 정의된 태그 내부에서만 사용할 수 있다.
또한 산술 연산자인 +, -, /, *, %를 사용할 수 있다.
<!-- 05: 지역 변수 -->
<div th:with="a=1, b=2">
<span th:text="|${a} + ${b} = ${a+b}|"></span>
</div>
5.6 비교와 등가
비교 등가 연산자인 <, >, <=, >=, ==, !=를 사용할 수 있다. 문자열 비교도 가능하다.
<!-- 06: 비교와 등가 -->
<span th:text="1 > 10"></span>
<span th:text="1 < 10"></span>
<span th:text="1 >= 10"></span>
<span th:text="1 <= 10"></span>
<span th:text="1 == 10"></span>
<span th:text="1 != 10"></span>
<span th:text="타임 == 타임"></span>
<span th:text="타임 != 리프"></span>
5.7 조건 연산자
삼항 연산자를 이용할 수 있다.
<!-- 07: 조건 연산자 -->
<p th:text="${name} == '길동' ? '길동입니다!' : '길동이가 아닙니다'"></p>
5.8 조건 분기(true)
th:if="조건"에서 참(ture)인 경우 th:if에서 작성한 태그와 자식 요소를 표시한다.
<!-- 08: 조건 분기(true) -->
<div th:if="${name} == '길동'">
<p>길동 씨입니다!</p>
</div>
5.9 조건 분기(false)
th:unless="조건"에서 거짓(false)인 경우 th:unless에서 작성한 태그와 자식 요소를 표시한다.
<!-- 09: 조건 분기(false) -->
<div th:unless="${name} == '영희'">
<p>영희 씨가 아닙니다</p>
</div>
5.10 switch
부모 요소의 th:switch 값과 자식 요소에 작성하는 th:case의 값이 동일한 경우, HTML 요소를 출력한다.
어떤 값에도 일치하지 않는 값을 출력하는 경우는 th:case="*"를 지정한다.
<!-- 10: switch -->
<div th:switch="${name}">
<p th:case="길동" th:text="|${name}입니다!|"></p>
<p th:case="영희" th:text="|${name}입니다!|"></p>
<p th:case="철수" th:text="|${name}입니다!|"></p>
<p th:case="*">명부에 없습니다</p>
</div>
5.11 참조(데이터를 결합한 객체)
캡슐화된 필드를 참조하는 경우 public 접근 제한자의 getXXX()라는 게터 메서드를 작성하여 객체명.필드 형식으로 값을 참조할 수 있다. 또 객체명['필드'] 같이 대괄호로도 참조할 수 있다.
<!-- 11. 참조(데이터를 결합한 객체) -->
<p th:text="${mb.id}">ID</p>
<p th:text="${mb.name}">이름</p>
<p th:text="${mb['id']}">ID: []로 접근</p>
<p th:text="${mb['name']}">이름: []로 접근</p>
5.12 참조(th:object)
데이터를 저장한 객체를 th:object에 설정하고, 자식 요소에서 *{필드명}으로 사용한다.
<!-- 12. 참조(th:object) -->
<div th:object="${mb}">
<p th:text="*{id}">id</p>
<p th:text="*{name}">이름</p>
<p th:text="*{['id']}">ID: []로 접근</p>
<p th:text="*{['name']}">이름: []로 접근</p>
</div>
5.13 참조(List)
List나 배열의 요소를 참조할 떄는 인덱스 번호를 입력해서 참조한다.
<!-- 13: 참조(List) -->
<p th:text="${list[0]}">방위</p>
<p th:text="${list[1]}">방위</p>
<p th:text="${list[2]}">방위</p>
<p th:text="${list[3]}">방위</p>
5.14 참조(Map)
Map 요소를 참조하려면 '키'를 이용하여 값을 참조한다.
map.key 형식으로도 참조할 수 있다. 또는 map['키']와 같이 대괄호를 사용하여 참조할 수도 있다.
<!-- 14: 참조(map) -->
<p th:text="${map.kim.name}">이름</p>
<p th:text="${map.lee.name}">이름</p>
<p th:text="${map['kim']['name']}">이름 1 : []로 접근</p>
<p th:text="${map['lee'][name]}">이름 2 : []로 접근</p>
5.15 반복
th:each=변수 : 객체로 반복 처리할 수 있다. 변수는 반복 처리 중에만 유효하다. Iterable 인터페이스를 구현한 클래스라면 th:each로 반복 처리할 수 있다. 자바의 확장 for문과 동일한 방식이다.
<!-- 15: 반복 -->
<div th:each="member : ${members}">
<p>[[${member.id}]] : [[${member.name}]]</p>
</div>
5.16 반복 상태
th:each=변수, 상태 변수 : 객체와 같이 변수의 선언 다음에 상태 변수를 선언하여 반복 상태를 유지한 상태 변수를 사용할 수 있다. 다음의 예제에서는 상태 변수로 s를 선언하고 있다. 반복 상태 변수의 목록은 다음 표를 참고하자.
<!-- 16: 반복 상태 -->
<div th:each="member, s : ${members}" th:object="${obejct}">
<p>
index -> [[${s.index}]], count -> [[${s.count}]],
size -> [[${s.size}]], count -> [[${s.current}]],
even -> [[${s.even}]], count -> [[${s.odd}]],
first -> [[${s.first}]], count -> [[${s.last}]],
[[*{id}]] : [[*{name}]]
</p>
</div>
반복 상태 변수
상태변수 속성 | 개요 |
index | 0부터 시작하는 인덱스. 현재 인덱스를 표시 |
count | 1부터 시작하는 인덱스. 현재 인덱스를 표시 |
size | 반복 처리하는 객체 사이즈를 표시 |
current | 현재 반복 요소의 객체를 표시 |
even | 현재 요소가 짝수 번째인지 여부를 결정 짝수이면 true, 아니면 false를 표시 |
odd | 현재 요소가 홀수 번째인지 여부를 결정 홀수이면 true, 아니면 false를 표시 |
first | 현재 요소가 첫 번째 요소인지 여부를 결정 첫 번쨰이면 true, 아니면 false를 표시 |
last | 현재 요소가 마지막 요소인지 여부를 결정 마지막이면 true, 아니면 false를 표시 |
5.17 유틸리티 객체
타임리프에서는 자주 사용되는 클래스를 #name이라는 상수로 정의하고 있어서 그대로 변수 표현식에서 사용할 수 있다. 데이터를 출력할 때 자주 사용되는 것이 '수치, 일시, 문자열'의 포맷 변환이다.
유틸리티 객체 목록
유틸리티 객체기능 | 개요 |
#strings | 문자 관련 편의 기능 |
#numbers | 숫자 서식 지원 |
#bools | 불리언(boolean) 관련 기능 |
#dates | java.util.Date 서식 지원 |
#objects | 객체 관련 기능 |
arrays | 배열 관련 기능 |
lists | List 관련 기능 |
sets | Set 관련 기능 |
maps | Map 관련 기능 |
유틸리티 객체(숫자)
- 정수값 형식 변환: #numbers.formatInteger
- 부동 소수점 형식 변환: #numbers.formatDecimal
- 쉼표를 사용하는 경우: COMMA
- 소수점을 사용하는 경우: POINT
<!-- 17:유틸리티 객체(숫자) -->
<div th:with="x=100000, y=123456.789">
정수 형식:
<span th:text="${#numbers.formatInteger(x, 3, 'COMMA')}"></span>
<br/>
부동 소수점 형식:
<span th:text="${#numbers.formatDecimal(y, 3, 'COMMA', 2, 'POINT')}"></span>
</div>
유틸리티 객체(날짜 및 시간)
- 현재 날짜와 시간을 반환: createNow()
- 날짜가 담긴 변수와 포맷 문자열 지정: format()
- 년, 월 일을 반환: year(), month(), day()
- 요일을 반환: dayOfWeek()
<!-- 17: 유틸리티 객체(날짜 및 시간) -->
<div th:with="today=${#dates.createNow()}">
yyyy/mm/dd 형식: <span th:text="${#dates.format(today, 'yyyy/MM/dd')}"></span> <br/>
yyyy년 mm월 dd일 형식: <span th:text="${#dates.format(today, 'yyyy년 MM월 dd일')}"></span> <br/>
yyyy년: <span th:text="${#dates.year(today)}"></span><br/>
MM월: <span th:text="${#dates.month(today)}"></span><br/>
dd일: <span th:text="${#dates.day(today)}"></span><br/>
요일: <span th:text="${#dates.dayOfWeek(today)}"></span><br/>
</div>
유틸리티 객체(문자열)
<!-- 17. 유틸리티 객체(문자열)-->
<div th:with="str1='abcdef'">
대문자 변환: <span th:text="${#strings.toUpperCase(str1)}"></span><br/>
빈 문자열 판정: <span th:text="${#strings.isEmpty(str1)}"></span><br/>
길이: <span th:text="${#strings.length(str1)}"></span><br/>
</div>
5.18 다른 템플릿 삽입하기
th:fragment는 Thymeleaf 템플릿 엔진에서 사용되는 속성으로, 템플릿의 일부분을 정의하여 재사용할 수 있게 해준다. 특히, 여러 페이지나 레이아웃에서 공통적으로 사용되는 부분을 분리하고, 이를 템플릿으로 정의하여 th:fragment를 이용해 재사용할 수 있다.
5.19 th:fragment 예제 1
th:fragment를 사용하는 일반적인 예제를 살펴보자. 아래는 헤더를 나타내는 header.html 템플릿 파일이라고 가정한다:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>My Website</title>
<link rel="stylesheet" th:href="@{/css/styles.css}" />
</head>
<body>
<div th:fragment="header">
<header>
<h1>Welcome to My Website</h1>
<nav>
<ul>
<li><a th:href="@{/home}">Home</a></li>
<li><a th:href="@{/about}">About</a></li>
<li><a th:href="@{/contact}">Contact</a></li>
</ul>
</nav>
</header>
</div>
</body>
</html>
위 코드에서 <div th:fragment="header">는 헤더 부분을 나타내는 템플릿이다. 이를 다른 페이지에서 재사용하려면 다음과 같이 다른 페이지에서 th:include나 th:replace 등을 사용하여 템플릿을 가져올 수 있다:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>My Home Page</title>
<link rel="stylesheet" th:href="@{/css/styles.css}" />
</head>
<body>
<div th:replace="header :: header"></div>
<div>
<!-- 여기에 페이지의 본문 내용이 들어갑니다 -->
</div>
</body>
</html>
위 코드에서 <div th:replace="header :: header"></div>는 header.html 템플릿의 header fragment를 현재 페이지에 삽입한다.
이렇게 th:fragment를 사용하면 코드의 재사용성이 높아지고, 유지보수가 용이해진다. 페이지의 일부분을 템플릿으로 정의하고, 이를 th:include나 th:replace 등을 통해 다른 페이지에서 가져와 사용할 수 있다.
5. 20 th:fragment 예제 2
프래그먼트를 사용하려면 th:fragment 속성을 이용한다.
th:fragment 속성을 지정한 요소 내의 자식 요소가 프래그먼트 대상이 된다. 속성에는 프래그먼트를 식별할 이름을 지정한다.
<!-- 18: 프래그먼트를 정의 -->
<span th:fragment="one">하나</span>
<span th:fragment="two">둘</span>
<span th:fragment="three">셋</span>
프래그먼트를 삽입할 때는 th:insert 속성을 이용한다.
파일명::이름의 왼쪽에 프래그먼트의 파일명을, 오른쪽에 th:fragment 속성에 앞에서 정의한 프래그먼트 이름을 지정한다.
전체 내용을 프래그먼트로 완전히 변경하려면 th:replace 속성을 이용한다.
<!-- 18: 프래그먼트 사용하기 -->
<h1>Fragment를 아래에 삽입하기</h1>
<div id="one" th:insert="fragment::one"></div>
<div id="two" th:insert="fragment::two"></div>
th:insert의 경우는 th:fragment를 부여한 태그의 자식 요소가 삽입된다.
th:replace의 경우에는 th:fragment를 부여한 태그 전체로 대체된다.
5.21 레이아웃
레이아웃화란 여러 템플릿에서 같은 디자인 레이아웃을 사용하는 경우 공통 레이아웃을 만들고 공유하는 것을 의미한다. 레이아웃을 사용하려면 전용 라이브러리인 thymeleaf-layout-dialect가 필요하다.
Thymeleaf Layout Dialect에서는 공통 레이아웃을 Decorator라 하고 공통 레이아웃을 사용하는 측을 Fragment라고 정의한다. Thymeleaf Layout Dialect를 이용할 때의 단점으로는 전체 화면을 타임리프의 변환 처리 후에 확인할 수 있다는 것이다.
타임리프의 템플릿 레이아웃 기능을 사용하면 코드의 중복도 줄어들고 공통 부분에 대한 변경도 간단하게 할 수 있다.
'스프링 > 스프링 프레임워크 첫걸음' 카테고리의 다른 글
8. 유효성 검사 기능 알아보기 (0) | 2025.01.12 |
---|---|
7. 요청 파라미터 취득하기 (0) | 2025.01.12 |
5. MVC 모델 알아보기 (0) | 2025.01.12 |
4. 데이터베이스 작업 (0) | 2025.01.12 |
3. 스프링 프레임워크의 핵심 기능 (0) | 2025.01.11 |