2.0 2일차 학습 목표
'2일차 MVC 패턴 이해와 실습' 에서는
변수에 따라 서로 다르게 출력되는 뷰 템플릿을 만들고,
이를 위한 역할 분담 동작 기법인 MVC 패턴에 대해 알아보자.
1) 뷰 템플릿과 MVC 패턴
2) MVC 패턴을 활용해 뷰 템플릿 페이지 만들기
3) MVC의 역할과 실행 흐름 이해하기
4) 뷰 템플릿 페이지에 레이아웃 적용하기
2.1 뷰 템플릿과 MVC 패턴
뷰 템플릿 (View Template)
- 화면을 담당하는 기술로, 간단히 '뷰'라고도 이야기함
- 웹 페이지를 하나의 틀로 만들고, 변수를 삽입해 서로 다른 페이지를 보여줌
- 머스테치(Mustache) 도구를 사용하여 뷰 템플릿을 만듦
- 예를 들어 "지민"님 반갑습니다. 에서 이름 부분만 변수로 두고, 다른 부분은 동일한 틀을 사용
MVC 패턴
- 뷰, 컨트롤러, 모델로 역할을 나누는 기법
- 모델 (Model) : 데이터 관리
- 뷰 (View) : 앞선 뷰 템플릿과 동일한 개념
- 컨트롤러 (Controller) : 클라이언트의 요청에 따라 서버에서 처리
2.2 MVC 패턴을 활용해 뷰 템플릿 페이지 만들기
앞선 MVC 패턴을 적용하여 웹 브라우저에서 뷰 템플릿을 출력해보자.
1) 뷰 템플릿 페이지 만들기
기본적으로 뷰 템플릿은
src > main > resources > templates 디렉토리에 만든다.
먼저 templates 디렉토리에 greetings.mustache 파일을 만들어주자.
mustache는 뷰 템플릿을 만드는 뷰 템플릿 엔진이다.
(Thymeleaf, JSP와 같은 뷰 템플릿 엔진도 사용 가능)
이후 상단의 'Install Handlebars/Mustache plugin' 을 클릭하여 설치한다.
다음으로 doc 을 입력한 후 Tap 키를 눌러 기본 HTML 코드를 생성하고,
본문(body)에 "지민님, 반갑습니다!" 를 입력하였다.
2) 컨트롤러 만들고 실행하기
앞서 만든 뷰 템플릿을 웹 브라우저에서 보기 위해서는 컨트롤러를 작성해야 한다.
컨트롤러는 기본적으로 src > main > java > com.example.firstproject에
controller 패키지를 추가하여 작성한다.
controller 패키지 안에 FirstController 라는 클래스를 생성한 후,
아래와 같이 코드를 작성한다.
1) @Controller
- 해당 클래스가 컨트롤러임을 선언
- Controller 클래스 패키지가 자동으로 import됨
2) niceToMeetYou()
- 반환형이 문자열인 메서드 선언
- 공백 문자열 ""을 반환하도록 함
[ 추가 ] 어노테이션이란 (@) ?
- 소스 코드에 추가해 사용하는 메타 데이터
- 컴파일 및 실행 과정에서 코드를 어떻게 처리해야 할지 알려줌
다음으로 niceToMeetYou() 메서드로 grettings.mustache 페이지를 반환하기 위해서는
아래와 같이 "gretting" 라는 파일이름을 반환값으로 작성해야 한다.
파일명을 반환값으로 작성하면,
서버가 자동으로 templates 디렉토리에서 파일을 찾아 웹에 전송한다.
이후 서버를 실행하고, localhost:8080/greeting.mustache 페이지로 이동하면
아래와 같이 404 에러화면이 나오는 것을 확인할 수 있다.
이는 greeting.mustache 페이지를 반환하는
URL 요청을 접수하는 부분이 작성되지 않았기 때문이다.
아래와 같이 @GetMapping 어노테이션을 추가하고,
URL 주소인 "/hi" 를 작성한다.
3) @GetMapping
- URL 요청 접수
- localhost:8080/hi 로 접속할 경우, greetings.mustache 파일을 찾음
실제로 서버를 재시작하고 localhost:8080/hi 에 접속하면
아래와 같이 뷰 템플릿 화면이 출력되는 것을 확인할 수 있다.
[ 정리 ] 컨트롤러 만들기
1) @Controller 로 컨트롤러 선언
2) return 값으로 보여줄 뷰 템플릿의 이름을 적음
3) @GetMapping 으로 URL 요청을 접수
[ 참고 ] 한글 깨짐 현상 발생 해결
처음에는 아래의 사진처럼 한글 깨짐 현상이 발생하여,
src > main > resources > application.properties 파일에서 아래의 코드를 추가하였다.
server.servlet.encoding.force=true
3) 모델 추가하기
앞선 뷰 템플릿의 "지민님, 반갑습니다!" 에서 이름을 변수로 지정하여,
다른 이름으로 자유롭게 바꿀 수 있도록 수정해보자.
greeting.mustache 파일에서 이름 부분을 {{username}} 으로 바꾼다.
( mustache 문법에서 변수 삽입은 두 겹의 중괄호로 감싼다 )
"지민" 을 {{username}} 변수로 변경하게 되면,
변수명에 따라 동적으로 출력되는 값을 바꿀 수 있다. (템플릿화)
그렇다면 username 변수는 어디서 찾게 될까?
MVC 패턴에서 모델은 데이터를 관리하는 역할을 맡게 되는데,
이때 컨트롤러의 메서드에서 매개변수 받아 온다.
아래와 같이 FirstController 코드를 수정해보자.
1) Model model 매개변수
- 모델은 컨트롤러의 메서드에서 매개변수로 받아옴
2) addAttribute("변수명", "변수값") 메서드
- 모델을 통해 변수를 등록
서버를 재시작하고 localhost:8080/hi에 뜨면
아래와 같이 username 변수로 지정한 이름이 뜨는 것을 확인할 수 있다.
2.3 MVC의 역할과 실행 흐름 이해하기
1) /hi 페이지의 실행 흐름
앞서 localhost:8080/hi 에 접속했을 때 뷰 템플릿을 출력하기 위해
어떤 과정이 차례대로 수행되는지 정리해보자.
1) @Controller 어노테이션을 통해 컨트롤러 임을 선언
2) 클라이언트로부터 /hi 요청을 받아 접수
3) niceToMeetYou() 메서드를 수행
4) 변수를 등록하기 위해 모델 객체를 매개변수로 가져옴
5) addAttribute()를 통해 모델에서 사용할 변수 등록
6) return 값인 greetings.mustache 파일을 찾아 웹 브라우저로 전송
2) /bye 페이지 만들기
앞선 페이지와 동일하게 localhost:8080/bye 에 접속하면
"{{username}}님, 다음에 또 만나요!" 라는 문구가 출력되도록 아래와 같이 작성해보았다.
Controller의 경우 별도의 파일을 만들지 않고,
기존의 FirstController에 작성하였다.
[First Conroller]
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 {
// /hi URL 요청 접수
@GetMapping("/hi")
// 메서드 작성
public String niceToMeetYou(Model model) { // 모델 객체 받아오기
model.addAttribute("username", "Jimin");
return "greetings";
}
// /bye URL 요청 접수
@GetMapping("/bye")
public String seeYouNext(Model model) {
model.addAttribute("nickname", "Jimin");
return "goodbye";
}
}
[goodbye.mustache]
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>{{nickname}}님, 다음에 또 만나요!</h1>
</body>
</html>
2.4 뷰 템플릿 페이지에 레이아웃 적용하기
앞서 만든 뷰템플릿에 header-footer 레이아웃을 적용시켜보자.
1) /hi 페이지에 헤더-푸터 레이아웃 적용하기
쉽고 따르게 페이지를 꾸미기 위해 부트스트랩(Bootstrap)을 사용하였다.
먼저 아래의 웹 페이지에 방문하여 상단의 v5.0.2를 선택한 후,
하단의 Starter Template 코드를 복사하여 사용하였다.
Bootstrap
Powerful, extensible, and feature-packed frontend toolkit. Build and customize with Sass, utilize prebuilt grid system and components, and bring projects to life with powerful JavaScript plugins.
getbootstrap.com
복사한 코드를 grettings.mustache 파일에 붙여 넣은 후,
아래와 같이 주석으로 nav / content / footer 를 구분해 주었다.
다음으로 navbar를 설정하기 위해 부트스트랩에서 navbar를 검색한 후,
해당 코드를 복사하여 navigation 영역에 붙여넣는다.
Footer의 경우 아래와 같이 간단하게 작성해주었다.
최종 greeting.mustache 코드와 실행 결과는 아래와 같다.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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 -->
<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>
2) /bye 페이지에 헤더-푸터 레이아웃 적용하기
앞선 /hi 페이지와 동일하게 /bye 페이지에도 레이아웃을 적용해보자.
이때 동일한 코드를 반복해서 사용하는 것은 비효율적이므로,
헤더와 푸터 영역을 변수화하여 템플릿 파일로 만들어보자.
templates 디렉토리 > layouts 디렉토리 를 만든 후,
header.mustache 와 footer.mustache 파일을 생성한다.
이후 앞서 작성한 greetings.mustache 파일의 헤더와 푸터 부분을 나누어 복사 붙여넣기 한다.
[header.mustache]
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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>
[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 파일의 헤더와 푸터 부분은
{{>파일명}} 으로 바꾸어 작성해준다.
[greetings.mustache]
{{>layouts/header}}
<!-- content -->
<div class="bg-dark text-white p-5">
<h1>{{username}}님, 반갑습니다!</h1>
</div>
{{>layouts/footer}}
goodbye.mustache 파일도 동일한 방법으로 작성해주면,
헤더와 푸터를 반복하지 않아도, 코드를 간단하게 작성할 수 있다.
또한 content 내용을 보다 명확하게 이해할 수 있다는 장점이 있다.
{{>layouts/header}}
<!-- content -->
<div class="bg-dark text-white p-5">
<h1>{{nickname}}님, 다음에 또 만나요!</h1>
</div>
{{>layouts/footer}}
실제로 /bye 웹 페이지에 들어가보면,
header와 footer가 정상적으로 적용된 것을 확인할 수 있다.
💡2장 셀프 체크
[quote.mustache]
{{>layouts/header}}
<!-- content -->
<div class="bg-dark text-white p-5">
<h1>{{randomQuote}}</h1>
</div>
{{>layouts/footer}}
[SecondController]
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";
}
}
홍팍 '스프링 부트3 자바 백엔드 개발 입문' 책 내용을 기반으로 작성하였습니다.
'[코딩자율학습단 5기] SpringBoot' 카테고리의 다른 글
[코딩자율학습단] 3일차. 게시판 만들고 새 글 작성하기 : Create (2) | 2023.11.26 |
---|---|
[코딩자율학습단] 1일차. 스프링 부트 시작하기 (1) | 2023.11.25 |
[코딩자율학습단] 0일차. 자율학습단을 신청하게 된 이유 / 스터디 소개 (0) | 2023.11.25 |