2.1 뷰 템플릿과 MVC 패턴
뷰 템플릿
화면을 담당하는 기술, 웹 페이지(View)를 하나의 틀(Template)로 만들고 여기에 변수를 삽입해 서로 다른 페이지로 보여줌
1장에서 스프링 부트 프로젝트를 만들 때 추가한 Mustache 도구가 뷰 템플릿을 만드는 도구임
MVC 패턴
화면을 담당하는 뷰 템플릿을 '뷰'라고도 부름
컨트롤러(Controller)는 클라이언트의 요청에 따라 서버에서 처리하는 역할
모델(Model)은 데이터를 관리하는 역할
웹 페이지를 화면에 보여주고(View), 클라이언트의 요청을 받아 처리하고(Controller), 데이터를 관리하는(Model) 역할을 나누는 기법을 MVC 패턴(Model-View-Controller Pattern)이라고 함
2.2 MVC 패턴을 활용해 뷰 템플릿 페이지 만들기
뷰 템플릿 페이지 만들기
1. templates 디렉터리에서 New → File 을 눌러 greetings.mustache 파일 생성
확장자인 mustache는 뷰 템플릿을 만드는 도구, 뷰 템플릿 엔진
mustache의 기본 위치는 src > main > resources > templates
템플릿 엔진으로는 mustache 외에 Thymeleaf, JSP 등이 있음
2. mustache 파일을지원하는 플러그인을 발견했다고 뜨면 Install Handlebars/Mustache plugin 을 클릭하여 설치
메뉴에서 선택해 머스테치 플러그인 설치하는 방법
메뉴에서 File → Settings → plugins → [Marketplace] → mustache 검색 → Handlebars/Mustache 선택 → [install]
3. 제일 윗줄에 doc 입력한 후 Tab 키를 누르면 기본 HTML 코드가 자동으로 작성됨
본문에 <h1>홍팍님, 반갑습니다!</h1> 추가
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Document</title>
</head>
<body>
<h1>홍팍님, 반갑습니다!</h1>
</body>
</html>
뷰 템플릿 작성 완료
이 페이지를 웹 브라우저에서 보기 위해 컨트롤러와 모델 이용
컨트롤러 만들고 실행하기
컨트롤러 만드는 위치:
src > main > java 디렉터리에 존재하는 기본 패키지 com.example.firstproject에 controller 패키지 생성
1. com.example.firstproject.controller 패키지 생성
2. controller 패키지에서 FirstController 클래스 생성
일반적으로 컨트롤러 이름은 ' ~ Controller ' 로 지음
3. 아래와 같이 작성
- 이 클래스가 컨트롤러임을 선언하는 @Controller 어노테이션 작성
→ Controller 클래스 패키지(org.springframework.stereotype.Controller)가 자동 생성됨 - 반환형이 문자열인 niceToMeetYou() 메서드 선언, 공백 문자열("")을 반환하도록 return ""; 추가
package com.example.firstproject.controller;
import org.springframework.stereotype.Controller;
@Controller
public class FirstController {
public String niceToMeetYou(){
return "";
}
}
클래스 패키지 자동으로 임포트하기
File → Settings 클릭 후 왼쪽 목록에서 Editor > General > Auto import 선택
Insert imports on paste 옵션을 Always로 선택, 아래 체크 박스 2개 모두 선택 후 [Apply], [OK] 클릭
- insert imports on paste
- Always : 코드 붙여 넣기 시 자동으로 import
- Never : 코드 붙여 넣기 시 자동으로 import 하지 않음
- Ask : 코드 붙여 넣기 시 확인한 후 선택해 import - Add unambiguous imports on the fly : 코드 변경 시 필요한 패키지 자동으로 삽입
- Optimize imports on the fly : 코드 변경 시 불필요한 패키지 자동으로 삭제
4. niceToMeetYou() 메서드로 greetings.mustache 페이지를 반환하려면 파일 이름인 greetings만 반환값으로 적어주면 됨
return "greetings"; 로 작성
서버가 알아서 templates 디렉터리에서 greetings.mustache 파일을 찾아 웹 브라우저로 전송
package com.example.firstproject.controller;
import org.springframework.stereotype.Controller;
@Controller
public class FirstController {
public String niceToMeetYou(){
return "greetings";
}
}
5. 서버 실행 후, localhost:8080/greetings.mustache 로 접속 → 404 Not Found 에러
6. FirstController에 페이지(greetings.mustache)를 반환해 달라는 URL 요청을 접수하는 부분 추가
- niceToMeetYou() 메서드 앞에 @GetMapping() 추가
→ 자동으로 org.springframework.web.bind.annotation.GetMapping 패키지 임포드 됨 - @GetMapping 괄호 안에 URL 주소인 "/hi" 넣어줌
→ localhost:8080/hi 로 접속하면 geetings.mustache 파일을 찾아 반환하라는 의미
package com.example.firstproject.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class FirstController {
@GetMapping("/hi")
public String niceToMeetYou(){
return "greetings";
}
}
7. 서버 재시작 후, localhost:8080/hi 접속
한글 깨짐 현상(???, ????!)이 발생하는 경우
src > main > resources > application.properties 파일을 열어
server.servlet.encoding.force=true 코드 추가 후 서버 재시작
모델 추가하기
1. "홍팍님, 반갑습니다!" 에서 다른 이름으로 바꾸기 위해 머스테치 문법을 사용하여 뷰 템플릿 페이지에 변수 삽입
{{변수명}}
greetings.mustache에서 홍팍님을 {{username}}님 이라고 수정
2. 서버 재시작 후, localhost:8080/hi 에 접속 → username 변수를 찾을 수 없어 에러 발생
에러를 해결하기 위해 모델 사용
3. 모델은 컨트롤러의 메서드에서 매개변수로 받아옴
FirstController로 가서 niceToMeetYou() 메서드에 Model 타입의 model 매개 변수 추가
Model 클래스 패키지 임포트
package com.example.firstproject.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class FirstController {
@GetMapping("/hi")
public String niceToMeetYou(Model model){
return "greetings";
}
}
4. 모델을 통해 변수 등록 → addAttribute() 메서드 사용
model.addAttribute("변수명", 변숫값) //변숫값을 "변수명"이라는 이름으로 추가
niceToMeetYou() 메서드 내부에 model.addAttribute("username", "홍팍"); 코드 추가
package com.example.firstproject.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class FirstController {
@GetMapping("/hi")
public String niceToMeetYou(Model model){
model.addAttribute("username", "홍팍");
return "greetings";
}
}
5. 서버 재시작 후 localhost:8080/hi에 접속
변숫값을 바꿔서도 출력해 봄
2.3 MVC의 역할과 실행 흐름 이해하기
/hi 페이지의 실행 흐름
FirstController.java 코드를 보며 컨트롤러 동작 이해해보기
package com.example.firstproject.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller // 1
public class FirstController {
@GetMapping("/hi") // 2
public String niceToMeetYou(Model model){ // 3, 4
model.addAttribute("username", "dlng23"); // 5
return "greetings"; // 6
}
}
1. 이 파일이 컨트롤러 임을 선언
2. 클라이언트로부터 "/hi" 라는 요청을 받아 접수
3. "/hi" 라는 요청을 받음과 동시에 niceToMeetYou() 메서드 수행
4. 뷰 템플릿 페이지에서 사용할 변수를 등록하기 위해 모델 객체를 매개변수로 가져옴
5. 모델에서 사용할 변수 등록
6. 메서드 수행 겨과로 greetings.mustache 파일 반환
→ 서버가 알아서 templates 디렉터리에 있는 해당 뷰 템플릿 페이지 찾아 웹 브라우저로 전송
컨트롤러가 @GetMapping("/hi")로 클라이언트의 요청을 받으면 niceToMeetYou()메서드 수행
뷰 템플릿 페이지에서 사용할 변수는 모델을 통해 등록하고 메서드의 반환값으로 greetings.mustache 파일 반환
/bye 페이지의 실행 흐름
localhost:8080/bye 로 요청 받았을 때 "xx님, 다음에 또 만나요!" 출력
FirstController 그대로 사용
FirstController
package com.example.firstproject.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class FirstController {
@GetMapping("/hi")
public String niceToMeetYou(Model model){
model.addAttribute("username", "dlng23");
return "greetings";
}
@GetMapping("/bye") // 1
public String seeYouNext(Model model){ // 2, 3
model.addAttribute("nickname", "dlng23"); // 4
return "goodbye"; // 5
}
}
1. @GetMapping("/bye") 어노테이션 추가
2. /bye 요청을 처리할 seeYouNext() 메서드 생성
3. 모델 객체를 매개변수로 가져옴
4. 모델 변수 등록
5. 반환값에 보여 줄 뷰 템플릿 페이지 적음
goodbye.mustache
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Document</title>
</head>
<body>
<h1>{{nickname}}님, 다음에 또 만나요!</h1>
</body>
</html>
templates 디렉터리에서 goodbye.mustache 파일 생성 후 코드 작성
서버 재시작 후 localhost:8080/bye 접속
컨트롤러는 클라이언트의 요청을 @GetMapping("/bye") 로 받음
return 값으로 goodbye.mustache 반환함
"nickname" 이라는 변수 등록, "dlng23" 이라는 값을 연결해 goodbye.mustache 파일에서 사용할 수 있게 함
2.4 뷰 템플릿 페이지에 레이아웃 적용하기
레이아웃(layout) : 화면에 요소를 배치하는 일
웹 페이지는 같은 요소로도 레이아웃에 따라 다른 느낌을 줄 수 있음
헤더 - 푸터 레이아웃(header-footer layout)
가장 기본이 되는 레이아웃, 샌드위치 구조
상단의 헤더(header) 영역에는 사이트 안내를 위한 내비게이션을 넣음
헤더와 푸터 영역 사이에는 사용자가 볼 핵심 내용인 content를 배치
하단의 푸터(footer) 영역에는 사이트 정보를 넣음
/hi 페이지에 헤더-푸터 레이아웃 적용하기
부트 스트랩(Bootstrap) : 웹 페이지를 쉽게 만들 수 있도록 작성해 놓은 코드 모음
코드를 가져와 사용하기만 하면 됨
1. 부트스트랩 홈페이지 ( https://getbootstrap.com ) 에 접속 (v5.0.2 사용)
2. 스크롤을 내려 스타터 템플릿에서 [copy] 버튼을 눌러 코드 복사
3. greetings.mustache 파일로 와 파일 내용을 모두 지운 후 복사한 내용 붙여 넣음
4. greetings.mustahe 페이지의 본문 영역을 <!-- --> 주석 표시를 이용하여 3개의 레이아웃으로 나눔
content 부분을 {{username}}님, 반갑습니다! 로 수정
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body>
<!-- navigation -->
<!-- content -->
<h1>{{username}}님, 반갑습니다!</h1>
<!-- site info -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>
5. localhost:8080/hi 로 접속하여 잘 출력되는지 확인
6. 부트 스트랩 검색창에서 'navbar' 를 입력한 후 목록이 뜨면 Navbar 선택
7. 코드 복사 후, greetings.mustache 파일로 와서 <!-- navigation --> 주석 아래에 붙여 넣기
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body>
<!-- navigation -->
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Dropdown
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
</li>
</ul>
<form class="d-flex">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>
<!-- content -->
<h1>{{username}}님, 반갑습니다!</h1>
<!-- site info -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>
8. 서버 재시작 후 localhost:8080/hi 에 접속하여 확인
9. 푸터 영역에 사이트 정보 추가
<!-- site info -->
<div class="mb-5 container-fluid">
<hr>
<p>ⓒ Cloudstudying | <a href="#">Privacy</a> | <a href="#">Terms</a></p>
</div>
10. ctrol + F9 (망치 아이콘)으로 서버를 재시작하지 않고 프로젝트를 빌드해 수정된 HTML 코드 빠르게 반영
11. localhost:8080/hi 새로고침
12. 콘텐트 영역 간격을 넓히기 위해 <div></div> 태그로 콘텐트 부분을 감싸고 부트스트랩에서 제공하는 class 속성 추가
배경 색상 어둡게 (bg-dark), 텍스트 색상 하얗게(text-white), 상하좌우 여백 5배만큼(p-5)
<!-- content -->
<div class="bg-dark text-white p-5">
<h1>{{username}}님, 반갑습니다!</h1>
</div>
/bye 페이지에 헤더-푸터 레이아웃 적용하기
/hi페이지를 템플릿화(코드를 하나의 틀로 만들어 변수화)하여 사용
템플릿 파일 만들고 적용하기
1. templates 디렉터리에서 새로운 디렉터리 layouts 생성
layouts 디렉터리에 header.mustache 파일, footer.mustache 파일 생성
2. greetings.mustache 코드에서 상단 내비게이션 바 부분 발췌하여 header.mustache 파일로 만듦
header.mustache
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body>
<!-- navigation -->
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Dropdown
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
</li>
</ul>
<form class="d-flex">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>
3. greetings.mustache 파일에서 잘라낸 부분을 템플릿으로 대체
{{>파일명}} 으로 작성
layouts 디렉터리 안에 header.mustache 파일을 만들었기때문에 {{>layouts/header}} 로 작성
greetings.mustache
{{>layouts/header}}
<!-- content -->
<div class="bg-dark text-white p-5">
<h1>{{username}}님, 반갑습니다!</h1>
</div>
<!-- site info -->
<div class="mb-5 container-fluid">
<hr>
<p>ⓒ Cloudstudying | <a href="#">Privacy</a> | <a href="#">Terms</a></p>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>
4. 같은 방식으로 푸터 영역부터 잘라내어 footer.mustache 파일에 붙여넣고 greetings.mustache에 {{>layouts/footer}} 작성
footer.mustache
<!-- site info -->
<div class="mb-5 container-fluid">
<hr>
<p>ⓒ Cloudstudying | <a href="#">Privacy</a> | <a href="#">Terms</a></p>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>
greetings.mustache
{{>layouts/header}}
<!-- content -->
<div class="bg-dark text-white p-5">
<h1>{{username}}님, 반갑습니다!</h1>
</div>
{{>layouts/footer}}
핵심 부분만 보이기 때문에 어떤 페이지인지 명확하게 인식하기 쉬움
5. 망치 아이콘 누르고 localhost:8080/hi 새로고침
문제 없이 잘 나옴
템플릿 적용하기(goodbye.mustache)
1. goodbye.mustache 파일
- 콘텐트 영역을 제외하고 기존 코드를 지운 후 헤더 템플릿과 푸터 템플릿 삽입
- 콘텐트 영역을 <div>로 감싸고 부트스트랩의 class 속성을 적용
2. 망치 아이콘 누르고 localhost:8080/bye 접속
greetings.mustache와 구분하기 위해 Controller에서 변수값을 dlng23→홍길동으로 수정하였음
셀프 체크
quote.mustache
{{>layouts/header}}
<div class="bg-dark text-white p-5">
<h1>{{randomQuote}}</h1>
</div>
{{>layouts/footer}}
SecondController.java
package com.example.firstproject.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class SecondController {
@GetMapping("random-quote")
public String randomQuote(Model model){
String[] quotes = {
"행복은 습관이다. 그것을 몸에 지니라. " +
"-하버드-",
"고개 숙이지 마십시오. 세상을 똑바로 정면으로 " +
"바라보십시오. -헬렌켈러-",
"고난의 시기에 동요하지 않는 것, 이것은 진정 " +
"칭찬받을 만한 뜅난 인물의 증거다. -베토벤-",
"당신이 할 수 있다고 믿든 할 수 없다고 믿든 " +
"믿는 대로 될 것이다. -헨리포드-",
"작은 기회로부터 종종 위대한 업적이 시작된다. " +
"-데모스테네스-"
};
int randInt = (int) (Math.random() * quotes.length);
model.addAttribute("randomQuote", quotes[randInt]);
return "quote";
}
}
localhost:8080/random-quote
'백엔드 > 코딩 자율학습 스프링 부트 3 자바 백엔드 개발 입문 1~2장' 카테고리의 다른 글
5장. 게시글 읽기: Read (0) | 2024.12.21 |
---|---|
4장. 롬복과 리팩터링 (0) | 2024.12.21 |
3장. 게시판 만들고 새 그 작성하기: Create (0) | 2024.12.21 |
1장. 스프링 부트 시작하기 (0) | 2024.11.29 |