Spring/Spring Boot

[Spring Boot] 게시물 화면 구성 (1)

ozofweird 2020. 9. 16. 18:19

1. 화면 구성

1) 템플릿 엔진

템플릿 엔진은 지정된 템플릿 양식과 데이터가 합쳐져 HTML 문서를 출력하는 소프트웨어를 뜻한다. 대표적인 예시로 JSP, Freemarker는 서버 템플릿 엔진이라고 하며, React, Vue의 경우 클라이언트 템플릿 엔진이라 불린다. (JSP은 정확히 서버 템플릿 엔진은 아니다. View의 역할만 하도록 구성할 때는 템플릿 엔진으로 사용된다.)

 

서버 템플릿 엔진을 이용한 화면 생성은 서버에서 Java 코드로 만든 문자열을 HTML로 변환하여 브라우저로 전달하지만, 클라이언트 템플릿 엔진은 브라우저 위에서 동작한다. React,js와 Vue.js를 이용한 SPA (Single Page Application)는 이미 서버와 별개로 브라우저에서 화면을 생성한다. 따라서 일반적으로 서버에서는 JSON 혹은 XML 형식의 데이터만 전달하고 클라이언트에서 가공하여 화면에 보여준다.

2) Mastache

Mastache는 현존하는 대부분의 수많은 언어를 지원하는 가장 심플한 템플릿 엔진이다. JSP, Velocity의 경우 스프링 부트에서는 권장하지 않고, Freemarker는 템플릿 엔진으로서는 너무 과한 기능을 지원하기에 숙련하기 힘들고, Thymeleaf의 경우 문법이 어렵다. 반면 Mastache는 다른 템플릿 엔진에 비해 문법이 단순하고 View의 역할과 서버의 역할을 확실하게 분리할 수 있다. Mastache를 사용하기 위해서는 Mastache 플러그인을 설치하고 build.gradle 파일에 의존성을 추가해준다.

compile('org.springframework.boot:spring-boot-starter-mustache')

3) index.mustache

Mustache 파일 위치는 기본적으로 'src/main/resources/templates'로 스프링 부트에서 자동으로 로딩해준다. 먼저 index.mustache 파일을 생성하여 구성하도록 한다. 이 후 첫 화면에 해당 페이지가 출력이 될 수 있도록 web 패키지 하위에 IndexController를 생성하여 URL을 매핑하도록 한다.

<!DOCTYPE HTML>
<html>
<head>
    <title>스프링 부트 웹 서비스</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <h1>스프링 부트 웹 서비스</h1>
</body>
</html>

Mustache 스타터는 컨트롤러에서 반환하는 문자열의 prefix와 suffix를 자동으로 붙어 View Resolver가 처리해준다.

package com.springbook.biz.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {

    @GetMapping("/")
    public String index() {
        return "index";
    }
}

4) 테스트 코드

마찬가지로 web 패키지 하위에 IndexControllerTest 클래스를 생성하여 실제 URL을 호출했을 때 페이지의 내용이 제대로 호출되는지에 테스트를 진행한다. TestRestTemplate를 통해 '/'를 호출했을 때 index.mustache에 포함된 코드들이 존재하는지 확인한다.

package com.springbook.biz.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 indexLoad() {
        // when
        String body = this.restTemplate.getForObject("/", String.class);
        
        // then
        assertThat(body).contains("스프링 부트 웹 서비스입니다.");
    }
}

5) 구현 화면

Application 클래스를 구동하여 실제로 동작하는지 확인한다.

구현 화면


만약 8080 포트가 사용중인 오류가 나온다면 프로세스를 수동으로 삭제하여 해결이 가능하다.

sudo lsof -i :8080
sudo kill -9 "프로세스 번호"

 

 

 

2. 등록 화면

1) Layout

부트스트랩, 제이쿼리 오픈소스를 외부 CDN을 사용하여 화면을 구성한다. 일반적으로는 CDN를 서비스하는 장소에 의존하게 되므로 많이 사용하지 않는다. 공통 영역을 별도의 파일로 분리하여 필요한 곳에서 가져다 사용하는 레이아웃 방식을 사용한다. templates 하위에 layout 디렉토리를 추가로 생성하고 footer.mustache, head.mustache를 생성한다.(github.com/jojoldu/freelec-springboot2-webservice/tree/master/src/main/resources/templates/layout)

 

페이지 로딩속도를 고려하여 css는 header에, js는 footer에 두었다.

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

2) index.mustache

header와 footer로 분리하였기 때문에 기존의 index.mustache 파일을 수정하고 글 등록 버튼을 추가한다. 글 등록으로 이동할 페이지 주소는 '/posts/save'이다. 

<!-- 현재 파일 기준으로 다른 파일을 불러옴 -->
{{>layout/header}}

<h1>스프링 부트 웹 서비스입니다.</h1>
<div class="col-md-12">
    <div class="row">
        <a href="/posts/save" role="button" class="btn btn-primary">글 등록</a>
    </div>
</div>

{{>layout/footer}}

3) 등록 화면

{{>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}}

4) 등록 기능

API를 호출하는 기능을 담당할 JS 파일을 'src/main/resources/static/js/app' 디렉터리 하위에 생성한다. 브라우저 스코프는 공용 공간으로 사용되기에 나중에 로딩된 JS의 init, save가 먼저 로딩된 JS의 function을 덮어쓰게 된다. 이러한 문제점을 고려하여 각 JS만의 유효 범위를 만들어 사용한다. 여기서는 var index 객체를 만들어 해당 객체에서 필요한 모든 function을 선언하여 index 객체 안에서만 function이 유효하도록 한다.

 

여기서 작성된 JS 파일은 footer.mustache 파일 하단에서 불러오도록 설정되어있다.

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 () {
            console.log("tet")
            alert('글이 등록되었습니다.');
            window.location.href = '/';
        }).fail(function (error) {
            alert(JSON.stringify(error));
        });
    }
};

main.init();

※ 기본적으로 'src/main/resources/static'에 위치한 정적 파일들은 URL에서 '/'로 설정된다. (ex. 'src/main/resources/static/test'는 'http://도메인/test'로 설정된다.

5) Controller

페이지에 관련된 컨트롤러는 모두 IndexController에 작성하도록 한다.

package com.springbook.biz.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {

    ... // 추가될 내용
    
    @GetMapping("/posts/save")
    public String postsSave() {
        return "posts-save";
    }
}

6) 구현 화면

실제로 등록이 되는지 h2 콘솔에 접속하여 확인해본다.

구현 화면


[참고] 스프링 부트와 AWS로 혼자 구현하는 웹 서비스

728x90