티스토리 뷰

반응형

예전에 혼자 구현했던 프로젝트의 코드를 리팩토링 해보겠습니다.

다시 코드를 보니 고쳐야 할부분도 많이 보이네요.

 

이 글에서는 타임리프기능을 리팩토링 해보겠습니다.

Thymeleaf가 스프링에서 사용할 때 편리한 기능들이 많이 있습니다.

처음 프로젝트를 진행할 때 뷰 템플릿에 대한 기능은 프론트 부분이다 라고 생각하여 기능 구현을 하는데 급급하였습니다.

추후에 잘못된 생각이었다고 깨달으면서 타임리프에 대해 공부하였고, 공부한 내용을 바탕으로 저의 프로젝트 코드들을 리팩토링 해보겠습니다.

 

1. 템플릿 레이아웃

HTML로 웹 페이지를 만들다 보면 Head 부분에 많은 중복된 코드가 발생하게 됩니다.

웹 폰트, css, 파비콘 같은 자료들은 매 페이지 마다 고정으로 들어가게 되는데 중복 코드를 줄여주기 위해 타임리프는 템플릿 조각 기능을 제공해줍니다.

먼저 기존 코드입니다.

 

fragments/header

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="header">

    <!-- 모든 장치에서 웹 사이트가 잘 보이도록 뷰포트를 설정하는 에제 -->
    <meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <!-- 문서의 저자 -->
    <meta name="author" content="charile">
    <!-- 웹 페이지 설명 -->
    <meta name="description" content="찰리 웹사이트">
    <!-- 검색 엔진을 위한 키워드 -->
    <meta name="keywords" content="메가박스, 유투브, 영화, 최신영화, 영화관, CGV, 롯데시네마, 웹스토리보이, 웹스, 사이트 만들기, 따라하기">

    <!-- CSS -->
    <link rel="stylesheet" type="text/css" href="/css/reset.css">
    <link rel="stylesheet" type="text/css" href="/css/swiper.css">
    <link rel="stylesheet" type="text/css" href="/css/font-awesome.css">

    <!-- 파비콘 -->
    <link rel="shortcut icon" type="image/ico" href="/favicon/favicon.ico">
    <link rel="apple-touch-icon-precomposed" type="image/png" href="/favicon/favicon_72.png" />
    <link rel="apple-touch-icon-precomposed" sizes="96x96" type="image/png" href="/favicon/favicon_96.png" />
    <link rel="apple-touch-icon-precomposed" sizes="144x144" type="image/png" href="/favicon/favicon_144.png" />
    <link rel="apple-touch-icon-precomposed" sizes="192x192" type="image/png" href="/favicon/favicon_192.png" />

    <!-- 자바스크립트 라이브러리  -->
    <script type="text/javascript" src="/js/jquery-3.5.1.min.js"></script>
    <script type="text/javascript" src="/js/modernizr-custom.js"></script>
    <script type="text/javascript" src="/js/ie-checker.js"></script>
    <script type="text/javascript" src="/js/swiper.min.js"></script>
    <script type="text/javascript" src="/js/iframe_api.js"></script>
    <script type="text/javascript" src="/js/slick.min.js"></script>

    <!-- 웹 폰트 -->
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap" rel="stylesheet">
</head>

<head th:fragment="header"> "header" 라는 템플릿 조각 생성

 

 

index.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
    <div th:replace="fragments/header :: header"></div>
    <title>찰리집</title>
    <link rel="stylesheet" href="css/style.css">
    <link rel="stylesheet" href="css/slick.css">
</head>

<div th:replace="fragments/header :: header"></div>

th:replace 를 이용하면 미리 지정해 놓은 "header" 템플릿 조각으로 대체됩니다.

 

하지만 head 부분을 보면 공통된 부분이외에도 각 페이지마다 title, link 처럼 개별적으로 들어가는 코드가 발생하게 됩니다. 그래서 추가적인 코드를 작성하게 되는데

이런 문제를 해결해 주기 위해 좀 더 개념이 확장된 템플릿 레이아웃 기능을 타임리프는 제공해줍니다.

 

 

바로 코드를 보고 설명하겠습니다.

template/layout/head.html

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="common_header(title,links)">

    <title th:replace="${title}">레이아웃 타이틀</title>

    ...

    <!-- 추가 -->
    <th:block th:replace="${links}" />

        ...

</head>

위의 fragments/header 파일에

<title th:replace="${title}">레이아웃 타이틀</title>

<th:block th:replace="${links}" />

두가지를 추가해주고

 

<head th:fragment="common_header(title,links)">

템플릿 레이아웃을 새롭게 정의해주었습니다.

이렇게 괄호안에 파라미터를 넣어주면, 동적으로 title과 link의 값을 받을 수 있습니다.

 

 

index.html

<head th:replace="template/layout/head :: common_header(~{::title}, ~{::link})">

    <title>찰리집</title>

    <link rel="stylesheet" th:href="@{css/style.css}">
    <link rel="stylesheet" th:href="@{css/slick.css}">

</head>

이처럼 사용하면 모든 페이지마다 title을 다르게 정의해줄 수 있고, link 태그도 추가 해 줄 수 있습니다.

템플릿 조각과 템플릿 레이아웃이 둘다 추가적인 코드를 작성하게 되니 비슷해보이지만 템플릿 레이아웃은 좀 더 확장하여 head 뿐만 아니라 html 전체의 틀을 정해서도 사용할 수 있습니다.

개인적으로는 코드의 추가 없이 재사용에 목적이 더 크다면 템플릿 조각을

구조는 일정한데 부분적인 코드 추가가 필요하면 템플릿 레이아웃을 사용하는걸 권장합니다.

 

 

2. 폼 분리

기존에는 회원을 등록하는 것과, 수정하는것을 분리하지 않고 하나의 Form.html 로 관리하였습니다.

등록과 수정은 거의 구조와 내용이 비슷하기 때문에 하나의 Form 으로 관리하는게 코드도 줄어들어 좋아보여 합쳐놓았습니다.

하지만 실무에서는 등록과 수정의 요구사항이 다른 경우가 많고, 분리를 하는것이 추후 확장이나 유지 보수 측면에서 유리합니다. 네이밍은 통일성 있게 등록은 addForm 수정은 editForm 으로 분리해주었습니다.

 

 

3. 폼 객체 가져오기

요구사항

  • 성별
    • 남자, 여자
    • 셀렉트 박스로 하나만 선택할 수 있다.
    • 성별은 ENUM을 사용

 

Gender - 성별

import lombok.Getter;

@Getter
public enum  Gender {
    MAN("남자"), FEMALE("여자");

    private final String description;

    Gender(String description) {
        this.description = description;
    }
}

 

form.html

<div class="box">
    <select th:field="*{gender}" name="gender" id="gender" class="sel" aria-label="성별">
    <option value="">성별</option>
    <option th:each="sex : ${T(study.charlieZip.entity.Gender).values()}"
    th:value="${sex}"
    th:text="${sex}">
    </option>
    </select>
    <span th:text="${valid_gender}"></span>
</div>

기존에는 타임리프의 스프링EL 문법으로 ENUM을 직접 사용하도록 구현하였습니다.
컨트롤러에서 객체를 넘겨줄 필요가 없으니 조금 더 깔끔하다고 생각했었습니다.

 

그러나 위의 코드는 몇가지 단점이 있습니다..
먼저 Gender ENUM의 패키지 위치가 변경되면 코드가 작동하지 않습니다. 코드가 유연하지 못한 특성이 있게됩니다. 그리고 엔티티에 직접적으로 의존하게 됩니다.

또한 자바 컴파일러가 타임리프까지 컴파일 오류를 잡을 수 없어 프로그램을 실행하지 않고서는 오류를 알기가 어렵습니다.

 

그래서 컨트롤러에서 객체를 생성해 Model을 통해 넘겨주도록 변경하였습니다.

Controller

@GetMapping(value = "/members/new")
public String createForm(Model model) {
    model.addAttribute("memberForm", new MemberForm());
    Gender[] genders = Gender.values();
    model.addAttribute("genders", genders);
    return "members/addForm";
}

values()를 사용하면 ENUM의 모든 정보를 배열로 반환합니다.
model.addAttribute("genders", genders); 배열을 model을 통해 넘겨줍니다.

 

 

form.html

<div class="box">
    <select th:field="*{gender}" name="gender" id="gender" class="sel" aria-label="성별">
        <option value="">성별</option>
        <option th:each="sex : ${genders}"
                th:value="${sex.name()}"
                th:text="${sex.description}">
        </option>
    </select>
    <span th:text="${valid_gender}"></span>
</div>

 

아시는 분들이 보시면 너무 당연한 것일 수도 있지만 만약 저와 비슷한 경우라면 각 경우에 장단점을 알고 상황에 맞게 사용하시는데 도움이 되셨으면 좋겠습니다.

반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함