스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술[서블릿, JSP, MVC패턴1]

윤현우·2023년 2월 21일
0
post-thumbnail

목차

  1. 회원 관리 웹 애플리케이션 요구사항
  2. 서블릿으로 회원 관리 웹 애플리케이션 만들기
  3. JSP로 회원 관리 웹 애플리케이션 만들기
  4. MVC패턴 - 개요
  5. MVC패턴 - 적용
  6. MVC패턴 - 한계

1. 회원 관리 웹 애플리케이션 요구사항

요번에 만드는 웹 애플리케이션은 간단하다. 서블릿과 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;

/**
 * 동시성 문제가 고려되어 있지 않음, 실무에서는 ConcurrentHashMap, AtomicLong 사용고려
 */
public class MemberRepository {

    private 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 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();
    }
}

간단한 메모리 레파지토리를 만들었다.

public static MemberRepository getInstance() { return instance; }

  • 해당 레파지토리 생성자를 private로 만들었기 때문에 다른 클래스에서 레파지토리 사용시 쓸 메서드 --> DIP OCP원칙

member.setId(++sequence);

  • 회원 정보 저장시 id에 1추가 메서드

store.put(member.getId(), member);

  • 회원 정보 메모리 레파지토리에 추가하는 메서드

1-1. 회원 레파지토리 테스트 코드

package hello.servlet.domain.member;

import org.assertj.core.api.Assertions;
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.*;


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);
    }

}

@AfterEach

  • 각각의 테스트가 끝나고 실행되는 메서드
  • memberRepository.clearStore(); 을 실행하면 해당 테스트가 끝나면 메모리 레파지토리에 저장된 객체들을 삭제시킴 --> 다음 테스트에 영향을 주지 않도록 초기화

2. 서블릿으로 회원 관리 웹 애플리케이션 만들기

이제 본격적으로 서블릿을 사용하여 만들어보자.

2-1. 회원 등록 서블릿

가장 처음으로 회원 정보를 작성하고 제출할 회원 등록 form을 서블릿으로 만든다.

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 request, HttpServletResponse response) throws ServletException, IOException {


        // 응답은 html 코드로 보내야 하기 때문에
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");

        PrintWriter w = response.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");
    }
}

private MemberRepository memberRepository = MemberRepository.getInstance();

  • MemberRepository 생성자 초기화

이 코드들은 단순하게 회원 정보를 입력할 수 있는 HTML Form을 만들어서 응답한다.

자바 코드로 HTML을 제공해야 하므로 쉽지 않은 작업이다.

자그마한 웹페이지인데도 작성하기 어려운데 정식 웹페이지를 만드려면 얼마나의 노력이 필요할까라는 생각이 든다.

2-2. 회원 저장 서블릿

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 request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("MemberSaveServlet.service");
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

        Member member = new Member(username, age);
        memberRepository.save(member);

        //응답
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");
        PrintWriter w = response.getWriter();
        w.write("<html>\n" +
                "<head>\n" +
                " <meta charset=\"UTF-8\">\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>");
    }
}

String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));

  • 회원 등록 폼에서 받아온 파라미터들을 객체로 만든다.
  • 해당 파라미터들은 기본 String 객체 이므로 Integer타입으로 형식 변환이 필요하다.

Member member = new Member(username, age);
memberRepository.save(member);

  • 해당 파라미터의 객체들을 멤버객체로 만든다.
  • 그 후, 멤버 레파지토리에 저장한다.

2-3. 회원 목록 서블릿

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 request, HttpServletResponse response) throws ServletException, IOException {

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

        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");
        PrintWriter w = response.getWriter();
        // /n이 없어서 웹 브라우저에서 소스 보기를 하면 엉망으로 보인다 너무 불편하다.
        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>");

        /*
        w.write(" <tr>");
        w.write(" <td>1</td>");
        w.write(" <td>userA</td>");
        w.write(" <td>10</td>");
        w.write(" </tr>");
        */

        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("</table>");
        w.write("</body>");
        w.write("</html>");
    }
}

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

  • 멤버 레파지토리에 미리 작성한 회원 정보 리스트로 반환하는 메서드

for (Member member : members) { ... }

  • 리스트로 반환된 데이터들을 반복문을 통해 뷰 페이지에 전달
  • 회원 목록 HTML을 for루프를 통해서 회원 수 만큼 동적으로 생성하고 응답

2-4. 템플릿 엔진으로..

지금까지 서블릿을 이용하여 HTML을 만들어 보았다.

서블릿 덕분에 동적인 웹페이지를 손쉽게 만들 수 있다. 하지만, 너무 복잡하고 비효율 적이다.

차라리 HTML문서에 동적으로 변경해야 할 부분만 자바코드를 넣을 수 있다면 더 편리할 것이다.

이것이 바로 템플릿 엔진(JSP)이 나온 이유이다.

템플릿 엔진에는 JSP, Thymeleaf, Freemarker, Velocity등이 있다.

참고
JSP는 성능과 기능면에서 다른 템플릿 엔진과의 경쟁에서 밀리면서, 점점 사장되어 가는 추세이다. 템플릿 엔진들은 각각 장단점이 있는데, 강의에서는 JSP는 앞부분에서 잠깐 다루고, 스프링과 잘 통합되는 Thymeleaf를 사용한다.


3. JSP로 회원 관리 웹 애플리케이션 만들기

JSP를 사용하려면 JSP 라이브러리가 필요하다.

implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'javax.servlet:jstl'

build.gradle에 해당 코드를 추가하자.

3-1. 회원 등록 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>

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

  • 해당 파일이 JSP라는 것을 알려주는 코드

3-2. 회원 저장 JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%
        //request,response는 없어도 사용가능 (서블릿으로 자동으로 변경)
        MemberRepository memberRepository = MemberRepository.getInstance();

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

        Member member = new Member(username, age);
        memberRepository.save(member);
%>
<html>
<head>
 <meta charset="UTF-8">
</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>

<%@ page import="hello.servlet.domain.member.MemberRepository" %>

  • 자바의 import 문과 같다.

<% ~~ %>

  • 이 부분에는 자바 코드를 입력할 수 있다.

<%= ~~ %>

  • 이 부분에는 자바 코드를 출력할 수 있다.

회원 저장 JSP를 보면, 회원 저장 서블릿 코드와 같다. 다른 점이 있다면, HTML을 중심으로 하고, 자바 코드를 부분부분 입력해주었다.

3-3. 회원 목록 JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page import="java.util.List" %>

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

%>
<html>
<head>
    <meta charset="UTF-8">
    <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>
</html>

3-4. 서블릿과 JSP의 한계

서블릿
서블릿으로 개발할 때는 뷰(View)화면을 위한 HTML을 만드는 작업이 자바 코드에 섞여 지저분하고 복잡했다.

JSP
이후 JSP가 나와 JSP를 사용한 덕분에 뷰를 생성하는 HTML작업을 깔끔하게 가져가고, 중간중간 동적으로 변경이 필요한 부분만 자바코드를 적용했다.

하지만 이렇게 해도 해결되지 않는 부분들이 있다.

회원 저장 JSP를 보면 코드의 상위 절반은 회원을 저장하기 위한 비즈니스 로직이고, 나머지 하위 절반만 결과를 HTML로 보여주기 위한 뷰 영역이다.

또 코드를 잘 보면, JAVA 코드, 데이터를 조회하는 레파지토리 등등 다양한 코드가 JSP에 노출되어 있다.

MVC 패턴의 등장
그래서 MVC패턴이 등장했다.

비즈니스 로직은 서블릿처럼 다른곳에서 처리하고, JSP는 목적에 맞게 HTML로 화면(View)을 그리는 일에 집중하게 되었다.

MVC패턴은 다음 포스트에 이어서 정리하겠다.


References(참고자료)
https://www.inflearn.com/

profile
개발자가 되는 그날까지

0개의 댓글