지난주 스터디..
- JPA 의 개념
- 프로젝트에 Spring Data Jpa 적용
- 등록 / 수정 / 조회 API 생성
- JPA Auditing을 이용한 생성시간 / 수정시간 자동화
제4장 머스테치로 화면 구성하기
- 서버 템플릿 엔진과 머스테치 소개
- 기본 페이지 만들기
- 게시글 등록 화면 만들기
- 전체 조회 화면 만들기
- 게시글 수정, 삭제 화면 만들기
1. 서버 템플릿 엔진과 머스테치 소개
템플릿 엔진
: 지정된 템플릿 양식과 데이터가 합쳐져 HTML 문서를 출력하는 소프트웨어
머스테치
: 수많은 언어를 지원하는 가장 심플한 템플릿 엔진
지정된 템플릿 양식과 데이터가 합쳐져 HTML 문서를 출력하는 소프트웨어
일반적인 템플릿 엔진들의 단점
- JSP, Velocity
: 스프링 부트에서는 권장하지 않는 템플릿 엔진 → 유료 버전에서만 공식 지원
- Freemarker
: 너무 과하게 많은 기능 지원
: 높은 자유도로 인해 숙련도에 따라 잘못된 방향으로 사용하게 될 가능성 높음
- Thymleaf
: 어려운 문법
: HTML 태그에 속성으로 템플릿 기능을 사용하는 방식 → 높은 진입장벽
머스테치의 장점
- 다른 템플릿 엔진보다 심플한 문법
- 로직 코드 사용 x → View의 역할과 서버의 역할의 명확한 분리
- Mustache.js와 Mustache.java 2개 모두 존재
→ 하나의 문법으로 클라이언트 / 서버 템플릿 모두 사용 가능
머스테치 플러그인 설치
: 해당 플러그인을 이용하면 문법 체크, HTML 문법 지원, 자동완성 등이 지원됨
설치 완료 후 인텔리제이 재시작
2. 기본 페이지 만들기
머스테치 스타터 의존성을 build.gradle에 등록
buildscript { ext { springBootVersion = '2.1.18.RELEASE' } repositories { mavenCentral() jcenter() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") } } apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'org.springframework.boot' apply plugin: 'io.spring.dependency-management' group 'com.jojoldu.book' version '1.0.4-SNAPSHOT-'+new Date().format("yyyyMMddHHmmss") sourceCompatibility = 1.8 repositories { mavenCentral() jcenter() } dependencies { compile('org.springframework.boot:spring-boot-starter-web') compile('org.projectlombok:lombok') compile('org.springframework.boot:spring-boot-starter-data-jpa') compile('com.h2database:h2') compile('org.springframework.boot:spring-boot-starter-mustache') testCompile('org.springframework.boot:spring-boot-starter-test') }
index.mustache
머스테치의 기본 파일 위치인 src/main/resources/templates 디렉토리에 생성
<!DOCTYPE HTML> <html> <head> <title>스프링 부트 웹서비스</title> <meta http-equiv="Content-Type" content="text/html"; charset="UTF-8" /> </head> <body> <h1>스프링 부트로 시작하는 웹 서비스</h1> </body> </html>
- h1 크기로 “스프링 부트로 시작하는 웹 서비스” 출력
IndexController.java
src/main/java/com/jojoldu/book/springboot/web 에 생성
package com.jojoldu.book.springboot.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class IndexController { @GetMapping("/") public String index(){ return "index"; } }
- 해당 머스테치에 URL 매핑 (in Controller)
- 머스테치 스타터로 인해 컨트롤러에서 문자열 반환시 앞의 경로와 뒤의 파일 확장자는 자동으로 지정
앞의 경로 : src/main/resources/templates
뒤의 파일 확장자 : .mustache
IndexControllerTest.java
src/test/java/com/jojoldu/book/springboot/web 에 생성
package com.jojoldu.book.springboot.web; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) public class IndexControllerTest { @Autowired private TestRestTemplate restTemplate; @Test public void 메인페이지_로딩() { //when String body = this.restTemplate.getForObject("/", String.class); //then assertThat(body).contains("스프링 부트로 시작하는 웹 서비스"); } }
- 실제로 URL 호출 시 페이지의 내용이 제대로 호출되는지에 대한 테스트 코드
Non-ASCII characters 경고 발생
원인 : 한글 변수명 사용으로 인해 발생
해결책 : Settings > Editor > Inspections > Non-ASCII characters 체크 해제
실제 실행 화면
Application.java의 main 메소드 실행
3. 게시글 등록 화면 만들기
프론트엔드 라이브러리를 사용할 수 있는 방법
- 외부 CDN 사용
- 직접 라이브러리를 받아 사용
2개의 라이브러리 부트스트랩과 제이쿼리를 레이아웃 방식으로 추가
→ 레이아웃 방식 : 공통 영역을 별도의 파일로 분리하여 필요한 곳에서 가져다 쓰는 방식
header.mustache
src/main/resources/templates/layout 디렉토리에 생성
<!DOCTYPE HTML> <html> <head> <title>스프링부트 웹서비스</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"> </head> <body>
footer.mustache
src/main/resources/templates/layout 디렉토리에 생성
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script> </body> </html>
HTML은 head가 다 실행된 후에 body가 실행
→ 페이지 로딩 속도를 높이기 위해 css는 header에, js는 footer에 둠
index.mustache
src/main/resources/templates/index.mustache 에서 수정
{{>layout/header}} <h1>스프링부트로 시작하는 웹 서비스 Ver.2</h1> <div class="col-md-12"> <div class="row"> <div class="col-md-6"> <a href="/posts/save" role="button" class="btn btn-primary">글 등록</a> </div> </div> </div> {{>layout/footer}}
{{> }}
: 현재 머스테치 파일을 기준으로 다른 파일을 가져옴.
<a>
: 태그를 이용해 글 등록 버튼 생성
- 이동할 페이지 주소 : /posts/save
IndexController.java
src/main/java/com/jojoldu/book/springboot/web/IndexController.java 에 추가
package com.jojoldu.book.springboot.web; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class IndexController { @GetMapping("/") public String index(){ return "index"; } // ********** @GetMapping("/posts/save") public String postsSave() { return "posts-save"; } // ********** }
- /posts/save를 호출하면 posts-save.mustache를 호출하는 메소드
posts-save.mustache
src/main/resources/templates 디렉토리에 생성
{{>layout/header}} <h1>게시글 등록</h1> <div class="col-md-12"> <div class="col-md-4"> <form> <div class="form-group"> <label for="title">제목</label> <input type="text" class="form-control" id="title" placeholder="제목을 입력하세요"> </div> <div class="form-group"> <label for="author"> 작성자 </label> <input type="text" class="form-control" id="author" placeholder="작성자를 입력하세요"> </div> <div class="form-group"> <label for="content"> 내용 </label> <textarea class="form-control" id="content" placeholder="내용을 입력하세요"></textarea> </div> </form> <a href="/" role="button" class="btn btn-secondary">취소</a> <button type="button" class="btn btn-primary" id="btn-save">등록</button> </div> </div> {{>layout/footer}}
실제 실행 화면
Application.java의 main 메소드 실행
- 등록 버튼 기능 없음
index.js
src/main/resources 에 static/js/app 디렉토리 생성 후 여기에 index.js 생성
var main = { init : function () { var _this = this; $('#btn-save').on('click', function () { _this.save(); }); }, save : function () { var data = { title: $('#title').val(), author: $('#author').val(), content: $('#content').val() }; $.ajax({ type: 'POST', url: '/api/v1/posts', dataType: 'json', contentType:'application/json; charset=utf-8', data: JSON.stringify(data) }).done(function() { alert('글이 등록되었습니다.'); window.location.href = '/'; }).fail(function (error) { alert(JSON.stringify(error)); }); } }; main.init();
window
.location.href = '/'
: 글 등록이 성공하면 메인페이지(/)로 이동
footer.mustache
src/main/resources/templates/layout/footer.mustache 에 추가
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script> // ********** <!--index.js 추가--> <script src="/js/app/index.js"></script> // ********** </body> </html>
- 생성된 index.js를 머스테치 파일이 쓸 수 있게 footer.mustach에 추가
- index.js 호출 코드에 따라 절대 경로(/)로 바로 시작
실제 실행 화면
Application.java의 main 메소드 실행