스프링 MVC2

Single Ko·2023년 6월 2일
0

Spring 강의 정리

목록 보기
12/31

타임리프

서버 사이드 렌더링(SSR) - 백엔드 서버에서 HTML을 동적으로 렌더링하는 용도로 사용한다.

네츄럴 템플릿이다. 즉 순수한 HTML을 최대한 유지한다. 웹 브라우저에서 HTML 파일을 직접 열어도 내용을 확인 할 수 있고, 서버를 통해 뷰 템플릿을 거치면 동적으로 변환된 결과를 확인 할 수 있다.

JSP를 포함한 다른 뷰 템플릿들은 해당 파일을 열면 웹 브라우저에서 정상적인 HTML 결과를 확인 할 수 없다. 오직 서버를 통해서 템플릿이 렌더링 되고 HTML 응답 결과를 받아야 화면을 확인.

스프링 통합 지원
타임 리프는 스프링에서 추천하는 뷰 템플릿이다. 따라서 스프링의 다양한 기능을 편리하게 사용 할 수 있게 지원한다.

타임리프 선언
<html xmlns:th="http://www.thymeleaf.org">
타임리프롤 사용하려면 html에 위와 같은 타임리프 사용 선언을 해줘야 한다.

텍스트 - text, utext

<span th:text="${data}">data로변환</span>
<span>컨텐츠 안에서 직접 출력 = [[${data}]]</span>

  • th:text="${data}" : data쪽에 model로 받아온 데이터가 들어오고, text value가 변환된다.
  • [[${data}]] : 직접출력

이스케이프
HTML 문서는 <,> 같은 특수문자 사용에 조심해야된다.
<, >&lt, &gt로 변환된다.

<b>강조</b>를 사용한다면 의도한것은 강조라는 부분이 볼드가 되어 나오기를 원했을것이다. 하지만 실제 HTML 화면에는 그대로 나온다.

HTML에서 사용하는 특수 문자를 HTML 엔티티로 변경하는 것을 이스케이프라고 한다. 타임리프가 제공하는 th:text, [[...]] 는 기본적으로 이스케이프(escape)를 제공 한다.

왜 이스케이프를 제공할까?
만약 이스케이프를 제공하지 않고, 그냥 태그상태로 들어오면 큰 문제가 생길 수 있다. HTML 태그로 인식되서 화면에 어떤 것이 생길지모른다.

th:utext , [(...)]는 기본적으로 이스케이프를 제공하지 않는다.

기본적으로 th:text를 사용하면 되고, 꼭 필요할때만 unescape를 사용하라.

변수 - SpringEL

타임리프에서 변수를 사용할 때는 변수 표현식을 사용한다. SpringEL이라는 스프링이 제공하는 변수식도 있다.

Model로 보내온 데이터를 가져오는 다양한 방법

  1. Object
//model에서 user를 그냥 보내줬을경우
<span th:text="${user.username}"></span>
<span th:text="${user['username']}"></span>
<span th:text="${user.getUsername()}"></span>
  1. List
//리스트로 보내줬을 경우 인덱스로 지정가능.
<span th:text="${users[0].username}"></span>
<span th:text="${users[0]['username']}"></span>
<span th:text="${users[0].getUsername()}"></span>
  1. Map
//맵으로 보내줬을 경우 키로 꺼내야된다.
<span th:text="${userMap['userA'].username}"></span>
<span th:text="${userMap['userA']['username']}"></span>
<span th:text="${userMap['userA'].getUsername()}"></span>

위의 출력 결과는 저부 같음. 보통 첫번째 방법이 제일 편하고 쓰기 쉬운 편이다.

지역 변수 선언

지역 변수 - th:with

<div th:with="first=${users[0]}">
	<span th:text="${first.usernname}"></span>
</div>

선언한 Scope안에서만 활용할 수 있다. 여기선 div의 scope에서만 사용 가능

기본 객체들

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

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

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

스프링 부트 3.0 미만에서 사용법

 <span th:text="${#request}"></span>
 <span th:text="${#response}"></span>
 <span th:text="${#session}"></span>
 <span th:text="${#servletContext}"></span>
 <span th:text="${#locale}"></span>

편의객체

// java class
@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;
 	}
}

//html
편의 객체

//요청 파라미터 접근: param , ?paramData=data를 바로 꺼내씀
<span th:text="${param.paramData}"></span>  

//HTTP 세션 접근 : 클래스에 sessionData를 받아오는 것.
<span th:text="${session.sessionData}"></span>

//Spring Bean에 접근 : @helloBean의 빈에 hello 메서드에 'Spring'을 data로 넣어서 리턴
<span th:text="${@helloBean.hello('Spring!')}"></span>

스프링 부트 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";
}

위 처럼 request, response, servletContext등을 전부 넣어서 보내줘야 된다.(더 불편해졌네..)

유틸리티 객체와 날짜

  • 실제 타임리프 공식 문서에 유틸리티 객체가 잘 설명되어 있다. 여기서는 간단하게 집고 넘어가고 사용때 필요하면 찾아서 보자.
  • Document의 Appendix B Expression Utility Objects 부분
  1. Messages 2. URIs/URLs 3. Dates 4. Calander 5. Number 6. String 7. Object 8. Booleans 9. Arrays 10. Lists 11. Set 12. Map 13.Temporals

이것 말고도 더 있음. 다만 주요 사용되는 것들은 이런것들.

URL 링크

<ul>
	/hello
	<li><a th:href="@{/hello}">basic url</a></li>
    /hello?param1=${param1}&pram2=${param2}
	<li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query param</a></li>
    /hello/{param1}/{param2}
	<li><a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=$ {param2})}">path variable</a></li>
    /hello/{param1}?param2={param2}
	<li><a th:href="@{/hello/{param1}(param1=${param1}, param2=$ {param2})}">path variable + query parameter</a></li>
</ul>

리터럴

Literals - 리터럴은 소스 코드상에 고정된 값을 말하는 용어이다.

타임리프는 다음과 같은 리터럴이 있다.

  • 문자 : 'hello' -> 공백없이 쭉 이어진다면 하나의 의미있는 토큰으로 인지해서 작은 따옴표 생략 가능
    ex) th:text="'hello'"(o) , th:text="hello"(o) , th:text="hello world!"(x) , th:text="'hello world'"(o)
  • 숫자 :
  • 불린 :
  • null :
  • 리터럴 대체 : th:text = "|hello ${data}|"

연산

타임리프의 연산은 자바와 크게 다르지 않다. 다만 HTML안에 사용하는 것이기에 HTML 태그를 주의하자

산술 연산 
<span th:text="10 + 2"></span>
<span th:text="10 % 2 == 0"></span>

비교 연산
<span th:text="1 &gt; 10"></span> > &gt
<span th:text="1 gt 10"></span>   > gt
<span th:text="1 >= 10"></span>   
<span th:text="1 ge 10"></span>	  >= ge
<span th:text="1 == 10"></span>
<span th:text="1 != 10"></span>

조건식
<span th:text="(10 % 2 == 0)? '짝수':'홀수'"></span>

Elvis 연산자
<span th:text="${data}?: '데이터가 없습니다.'"></span> ${data}가 있을시, 없을시 데이터가 없습니다
<span th:text="${nullData}?:'데이터가 없습니다.'"></span>
<span th:text="${data}?: _">데이터가 없습니다.</span> ${data}가 없을 시 아무것도 안함.(기존의 text출력) 
<span th:text="${nullData}?: _">데이터가 없습니다.</span>

속성값 설정

타임태그는 주로 HTML태그에 th:*속성을 지정하는 방식으로 동작한다. th:*로 속성을 지정하면 기존 속성을 대체한다. 기존 속성이 없으면 새로 만든다.

속성값 대체
<input type="text" name="mock" th:name="userA">

속성 추가
//띄어쓰기 신경써야됨 append 뒤에 붙임
<input type="text" class="text" th:attrappend="class=' large'" />  
//띄어쓰기 신경 prepend 앞에 붙임
<input type="text" class="text" th:attrprepend="class='large '" />
//class 이름의 경우 띄어쓰기 신경쓰기 귀찮음. classappend를 쓰면 자동으로 클래스 이름 구분해서 붙여줌
<input type="text" class="text" th:classappend="large" />

체크 처리
- checked o <input type="checkbox" name="active" th:checked="true" />
- checked x <input type="checkbox" name="active" th:checked="false" />
- checked=false <input type="checkbox" name="active" checked="false" />
// checked="false"라고 해도 checked 속성이 있는것으로 판단됨. 
// 타임리프의 경우 th:checked="false" 의경우 checked 속성이 없다. 

반복

타임리프에서의 반복은 th:each를 사용하면 된다.

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

th:each는 List 뿐만 아니라 배열, java.util.Iterable , java.util.Enumeration 을 구현한 모든 객체를 반복에 사용할 수 있습니다. Map 도 사용할 수 있는데 이 경우 변수에 담기는 값은 Map.Entry 입니다.

반복 상태 유지

<tr th:each="user, userStat : ${users}">
	<td th:text="${userStat.count}">username</td>
	<td th:text="${user.username}">username</td>
	<td th:text="${user.age}">0</td>
<td>
 	index = <span th:text="${userStat.index}"></span>
 	count = <span th:text="${userStat.count}"></span>
 	size = <span th:text="${userStat.size}"></span>
 	even? = <span th:text="${userStat.even}"></span>
 	odd? = <span th:text="${userStat.odd}"></span>
 	first? = <span th:text="${userStat.first}"></span>
 	last? = <span th:text="${userStat.last}"></span>
 	current = <span th:text="${userStat.current}"></span>
	</td>
</tr>

반복의 두번째 파라미터를 설정해 상태를 확인할 수 있습니다.
두번째 파라미터(userStat)는 생략 가능한데, 생략하면 지정한 변수명(user)+(Stat)이 됩니다.
즉 파라미터를 생략해도 userStat으로 사용 가능.

  • index = 현재 인덱스의 값
  • count = 1부터 시작되는 값. 현재 카운트
  • size = 전체 사이즈 크기
  • even , odd = 홀수, 짝수 여부(boolean)
  • first, last = 처음, 마지막 여부(boolean)
  • current = 현재 객체.

조건부 평가

if, unless 라는 조건문을 제공한다. switch - case문도 제공

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

<td th:switch="${user.age}">
	<span th:case="10">10살</span>
	<span th:case="20">20살</span>
	<span th:case="*">기타</span>
</td>
  • if, unless
    타임리프는 해당 조건이 맞지 않으면 태그 자체를 렌더링하지 않는다.
    만약 다음 조건이 false 인 경우 <span>...<span>부분 자체가 렌더링 되지 않고 사라진다.
    <span th:text="'미성년자'" th:if="${user.age lt 20}"></span>
  • switch
    *은 만족하는 조건이 없을 때 사용하는 디폴트이다.

주석

<!-- 표준 HTML 주석 --> 

<!--/* Tag */-->

<!--/*-->
	Tag
<!--*/-->

타임리프 프로토타입 주석
HTML 파일을 그대로 열면 주석으로 보이는데, 타임리프 랜더링을 거치면 주석이 사라지고 안에 내용이 보임.
<!--/*/
	Tag
/*/-->

블록

<th:block>은 HTML이 아닌 타임리프가 제공하는 태그

<th:block th:each="user : ${users}">
	<div>
		사용자 이름1 <span th:text="${user.username}"></span>
		사용자 나이1 <span th:text="${user.age}"></span>
	</div>
	<div>
		요약 <span th:text="${user.username} + ' / ' + ${user.age}"></span>
	</div>
</th:block>

블록 내부의 것을 반복. 타임리프의 특성상 HTML 태그안에 속성으로 기능을 정의해서 사용하는데, 그렇게 사용하기 애매한 경우에 사용하면 된다. th:block은 렌더링시 제거된다.

자바스크립트 인라인

자바스크립트에서 타임리프를 편리하게 사용할 수 있는 자바스크립트 인라인 기능을 제공한다.
<script th:inline="javascript">

<!-- 자바스크립트 인라인 사용 전 -->
<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>
  • 변수의 경우
    인라인 사용 전 var username = userA; 변수명으로 들어감
    인라인 사용 후 var username = "userA"; 문자열로 들어감

  • 내추럴 템플릿
    인라인 사용 전 var username2 = /*[[${user.username}]]*/ "test username"; 그대로 나옴
    인라인 사용 후 var username2 = /*[[${user.username}]]*/ "test username"; user.useranme이 나옴.

  • 객체
    인라인 사용 전 var user = [[${user}]]; - toString() 호출
    인라인 사용 후 var user = [[${user}]]; - JSON으로 변환해줌.

자바스크립트 인라인 each

[# th:each="user, stat : ${users}"]
var user[[${stat.count}]] = [[${user}]];
[/]

타임리프의 방식을 그대로 사용 가능해진다.

템플릿 조각

웹 페이지를 개발할 때는 공통 영역이 많이 있다. 상단 영역, 하단 영역, 좌측 카테고리 등등 여러 페이지에서 함께 사용하는 영역들이 있다. 이런 부분들을 코드 복사해서 사용한다면 변경시 여러 페이지를 다 수정해야 하므로 비효율적이다. 이런 문제를 해결하기 위해 템플릿 조각과 레이아웃 기능을 지원한다.

우선 템플릿 조각을 사용해보자

footer.html
같은 하단 영역을 만들어둔다. 

<footer th:fragment="copy">
	푸터 자리 입니다.
</footer>

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

fragment는 사용되는 조각이라 보면 된다.

<div> 태그 안에 들어가는 insert
<div th:insert="~{template/fragment/footer :: copy}"></div>

아예 태그 자체가 교체되버리는 replcae
<div th:replace="~{template/fragment/footer :: copy}"></div>

replace 단순 표현식(복잡해지면 사용 어려움)
<div th:replace="template/fragment/footer :: copy"></div>

파라미터 사용
<div th:replace="~{template/fragment/footer :: copyParam ('데이터1', '데이터2')}"></div>

~{경로 :: fragment 이름(pram..)}

레이아웃

템플릿 조각은 일부 코드를 조각해서 가지고왔다면, 코드 조각을 레이아웃에 넘겨서 사용하는 방법을 알아보자.

큰 모양이 있고 내 코드를 조각에 맞춰 넣는다고 생각하면 된다.

base.html

<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는 하나의 거대한 레이아웃이다.
  • head부분에 "common_header(title,links)" 이 핵심이다.
layout.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>
  • ~{::title}, ~{::link} title과 link는 base.html에 보내져 replace 된다.
  • 즉 이 layout.html은 부품을 보내주고, base.html이라는 큰 틀에 보내져 동적으로 교체된다.

위의 개념을 더 확장해 Header 뿐만 아니라 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>

타이틀과 컨텐츠 쪽만 바꾸도록 해놓음.

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

html에 th:replace가 붙어 있는 것이 보일것이다. 전부 교체된다는 뜻이다. 그때, title 과section은 파라미터로 넘겨준다는 뜻이다.

참조 : 김영한님의 스프링 강의를 공부하며 정리한 것입니다.

profile
공부 정리 블로그

0개의 댓글