pebblepark
개발계발
pebblepark
전체 방문자
오늘
어제
  • 분류 전체보기 (24)
    • Frontend (7)
    • Backend (7)
    • 인프라 (1)
    • CS (0)
      • Design Pattern (0)
    • 정리용 (9)
    • 회고 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • typescript
  • 스프링
  • 무한스크롤
  • vite
  • redux
  • SpringMVC
  • github
  • useLayoutEffect
  • 리액트쿼리
  • 스프링 의존관계
  • CORS
  • Docker
  • debounce
  • Context API
  • TDZ
  • hoisting
  • wsl
  • 스프링 빈
  • springboot
  • javascript
  • spring
  • 스프링 컨테이너
  • Git
  • 호이스팅
  • react
  • react-query
  • React Query
  • ERR_UNSAFE_PORT
  • @ModelAttribute
  • Github Pages

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
pebblepark

개발계발

[Spring] MVC 패턴의 등장
Backend

[Spring] MVC 패턴의 등장

2022. 1. 15. 01:40

MVC 패턴의 등장을 알아보기 위해 먼저 서블릿과 JSP로 회원 관리 웹 애플리케이션을 구현해보자.

회원관리 웹 애플리케이션

회원정보

  • 이름: username
  • 나이: age

 

기능 요구사항

  • 회원 저장
  • 회원 목록 조회

 

회원 도메인 모델

package hello.servlet.domain.member;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Member {

    private Long id;
    private String username;
    private int age;

    public Member() {
    }

    public Member(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

 

회원 저장소

package hello.servlet.domain.member;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MemberRepository {
    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    private static final MemberRepository instance = new MemberRepository();

    public static MemberRepository getInstance() {
        return instance;
    }

    // 싱글톤 패턴 적용을 위해 생성자 private으로 막아놓기
    private MemberRepository() { }

    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    public Member findById(Long id) {
        return store.get(id);
    }

    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }

    public void clearStore() {
        store.clear();
    }
}
현재는 동시성 문제를 고려되어 있지 않은 회원 저장소이다.
동시성 문제를 해결하려면 ConcurrentHashMap, AtomicLong 사용을 고려하자.

 

회원 저장소 테스트 코드 작성

package hello.servlet.domain;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;

class MemberRepositoryTest {

    MemberRepository memberRepository = MemberRepository.getInstance();

    @AfterEach
    void afterEach() {
        memberRepository.clearStore();
    }

    @Test
    void save() {
        //given
        Member member = new Member("hello", 20);

        //when
        Member savedMember = memberRepository.save(member);

        //then
        Member findMember = memberRepository.findById(savedMember.getId());
        assertThat(findMember).isEqualTo(savedMember);
    }

    @Test
    void findAll() {
        //given
        Member member1 = new Member("member1", 20);
        Member member2 = new Member("member2", 30);

        memberRepository.save(member1);
        memberRepository.save(member2);

        //when
        List<Member> result = memberRepository.findAll();

        //then
        assertThat(result.size()).isEqualTo(2);
        assertThat(result).contains(member1, member2);
    }

}



서블릿으로 만드는 회원 관리 웹 애플리케이션

MemberFormServlet - 회원 등록 폼

package hello.servlet.web.servlet;

import hello.servlet.domain.member.MemberRepository;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "memberFormServlet", urlPatterns = "/servlet/members/new-form")
public class MemberFormServlet extends HttpServlet {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");

        PrintWriter w = resp.getWriter();
        w.write("<!DOCTYPE html>\n" +
                "<html>\n" +
                "<head>\n" +
                "   <meta charset=\"UTF-8\">\n" +
                "       <title>Title</title>\n" +
                "<head>\n" +
                "<body>\n" +
                "<form action=\"/servlet/members/save\" method=\"post\">\n" +
                "   username : <input type=\"text\" name=\"username\" />\n" +
                "   age:       <input type=\"text\" name=\"age\" />\n" +
                "   <button type=\"submit\">전송</button>\n" +
                "</form>\n" +
                "</body>\n" +
                "</html>\n");


    }
}

 

MemberSaveServlet - 회원 저장

package hello.servlet.web.servlet;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet(name = "memberSaveServlet", urlPatterns = "/servlet/members/save")
public class MemberSaveServlet extends HttpServlet {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        System.out.println("MemberSaveServlet.service");
        String username = req.getParameter("username");
        int age = Integer.parseInt(req.getParameter("age"));

        Member member = new Member(username, age);
        System.out.println("member = " + member);
        memberRepository.save(member);

        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");

        PrintWriter w = resp.getWriter();
        w.write("<!DOCTYPE html>\n" +
                "<html>\n" +
                "<head>\n" +
                "   <meta charset=\"UTF-8\">\n" +
                "       <title>Title</title>\n" +
                "<head>\n" +
                "<body>\n" +
                "성공\n" +
                "<ul>\n" +
                "   <li>id=" + member.getId() + "</li>\n" +
                "   <li>username=" + member.getUsername() + "</li>\n" +
                "   <li>age=" + member.getAge() + "</li>\n" +
                "</ul>\n" +
                "<a href=\"/index.html\">메인</a>\n" +
                "</body>\n" +
                "</html>\n");
    }
}

 

MemberListServlet - 회원 목록

package hello.servlet.web.servlet;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

@WebServlet(name = "memberListServlet", urlPatterns = "/servlet/members")
public class MemberListServlet extends HttpServlet {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");

        List<Member> members = memberRepository.findAll();

        PrintWriter w = resp.getWriter();
        w.write("<html>");
        w.write("<head>");
        w.write("   <meta charset=\"UTF-8\">");
        w.write("   <title>Title</title>");
        w.write("</head>");
        w.write("<body>");
        w.write("<a href=\"/index.html\">메인</a>");
        w.write("<table>");
        w.write("   <thead>");
        w.write("   <th>id</th>");
        w.write("   <th>username</th>");
        w.write("   <th>age</th>");
        w.write("   </thead>");
        w.write("   <tbody>");

        for (Member member : members) {
            w.write("   <tr>");
            w.write("       <td>" + member.getId() + "</td>");
            w.write("       <td>" + member.getUsername() + "</td>");
            w.write("       <td>" + member.getAge() + "</td>");
            w.write("   </tr>");
        }

        w.write("   </tbody>");
        w.write("</body>");
        w.write("</html>");
    }
}

 

서블릿으로 동적 HTML을 만들어 반환할 수 있지만, 자바 코드로 HTML을 제공하는 것은 매우 복잡하고 비효율적이다. 차라리 자바 코드로 HTML을 만드는 것보다 HTML 문서에 동적으로 변경해야 하는 부분에 자바코드를 넣는 것이 더 편리할 것이다. 이로 인해 템플릿 엔진이 등장했고, 템플릿 엔진을 사용하면 HTML 문서에 필요한 부분만 코드를 통해 동적으로 변경이 가능하다. 템플릿 엔진에는 JSP, Thymeleaf, Freemarker, Velocity 등이 있다.



JSP로 만드는 회원 관리 웹 애플리케이션

JSP 라이브러리 추가
build.gradle

// JSP 추가 시작
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'javax.servlet:jstl'
// JSP 추가 끝

 

회원 등록 폼 JSP
main/webapp/jsp/members/save.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<form action="/jsp/members/save.jsp" method="post">
    username:   <input type="text" name="username"/>
    age:        <input type="text" name="age" />
    <button type="submit">전송</button>
</form>
</body>
</html>

 

회원 저장 JSP
main/webapp/jsp/members/save.jsp

<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    // request, response 사용 가능

    MemberRepository memberRepository = MemberRepository.getInstance();

    System.out.println("save.jsp");
    String username = request.getParameter("username");
    int age = Integer.parseInt(request.getParameter("age"));

    Member member = new Member(username, age);
    System.out.println("member = " + member);
    memberRepository.save(member);

%>
<html>
<head>
    <title>Title</title>
</head>
<body>
성공
<ul>
    <li>id=<%=member.getId()%></li>
    <li>username=<%=member.getUsername()%></li>
    <li>age=<%=member.getAge()%></li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>

 

회원 목록 JSP
main/webapp/jsp/members.jsp

<%@ page import="java.util.List" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    MemberRepository memberRepository = MemberRepository.getInstance();
    List<Member> members = memberRepository.findAll();
%>
<html>
<head>
    <title>Title</title>
</head>
<body>
<a href="/index.html">메인</a>
<table>
    <thead>
    <th>id</th>
    <th>username</th>
    <th>age</th>
    </thead>
    <tbody>
    <%
        for (Member member : members) {
            out.write(" <tr>");
            out.write("     <td>" + member.getId() + "</td>");
            out.write("     <td>" + member.getUsername() + "</td>");
            out.write("     <td>" + member.getAge() + "</td>");
            out.write(" </tr>");
        }
    %>
    </tbody>
</table>
/body>
</body>
</html>

 

서블릿은 View화면을 위한 HTML을 만드는 코드가 자바 코드에 섞여있어 지저분하고 복잡했다. JSP를 사용하면서 뷰를 사용하는 HTML과 동적으로 변경이 필요한 부분에만 추가하는 자바 코드로 분리할 수 있었다. 하지만 하나의 JSP에서 코드 상위 절반은 비즈니스 로직이고, 하위 절반은 HTML을 보여주기 위한 뷰 영역이다. 하나의 JSP에서 JAVA 코드, 데이터를 조회하는 리포지토리 등 너무 많은 역할을 하고 있다. 따라서 비즈니스 로직과 뷰를 분리하기 위해 MVC 패턴이 등장하였다.

 

MVC 패턴의 등장
비즈니스 로직은 서블릿처럼 다른 곳에서 처리하고, JSP는 목적에 맞게 HTML로 화면을 그리는 역할만 주도록 하자.

 



저작자표시 (새창열림)

'Backend' 카테고리의 다른 글

[Spring] 스프링 컨테이너와 스프링 빈  (0) 2022.01.25
[Spring] HTTP 요청 파라미터 사용 - @RequestParam과 @ModelAttribue  (0) 2022.01.18
[Spring] MVC 패턴  (0) 2022.01.16
[Spring] HttpServletRequest와 HttpServletResponse 다루기  (0) 2022.01.11
[Spring] Spring Boot에서 Servlet 사용하기  (0) 2022.01.11
    'Backend' 카테고리의 다른 글
    • [Spring] HTTP 요청 파라미터 사용 - @RequestParam과 @ModelAttribue
    • [Spring] MVC 패턴
    • [Spring] HttpServletRequest와 HttpServletResponse 다루기
    • [Spring] Spring Boot에서 Servlet 사용하기
    pebblepark
    pebblepark
    프론트엔드 개발자입니다. 피드백은 언제나 환영입니다:)

    티스토리툴바