본문 바로가기

[Spring] 회원관리 웹 애플리케이션 요구 사항 및 Servlet 구현 (A.K.A 타자연습)

@xuv22025. 6. 21. 23:20

이번에는 회원 관리 웹 애플리케이션을 간단하게 구현 해볼 것이다.

서블릿, JSP , MVC 패턴으로 차차 리팩토링 할 예정인데, 이번 포스팅에선 먼저 웹 애플리케이션 요구 사항에 대한 코드를 작성하고 서블릿까지만 먼저 구현해본다.

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

회원 정보 -> 이름(username), 나이(age)

기능 요구 사항 -> 회원 저장, 회원 목록 조회


회원 도메인 모델 작성

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

id 필드는 멤버 객체 생성시 저장소에서 자동으로 할당 시킬 예정이다.

 

회원 저장소

import java.util.*;

public class MemberRepository {

    // static 변수는 JVM 로드시 한번만 생성 -> 이후 인스턴스들이 이 값을 공유
    private static Map<Long, Member> store = new HashMap<Long, Member>();
    private static long sequence = 0L;

    private static final MemberRepository instance = new MemberRepository();

    @SuppressWarnings("lombok")
    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();
    }

}

최대한 간단하게 구현 하였다.

주의 해서 봐야할 점은 최초에 회원 저장소 객체를 미리 하나 만들어두고, 생성자를 막아 둔다. -> 싱글톤 적용

그리고 참고로 static 변수는 모든 인스턴스들이 값을 공유하기 때문에 다른 코드에서 호출 해도 같은 값을 유지한다.

 

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

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.*;

public 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", 10);
        Member member2 = new Member("member2", 20);

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

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

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

JUnit으로 테스트가 잘 통과한 것을 볼 수 있다.


회원 관리 웹 애플리케이션 - 서블릿 적용

스프링 없이 구현을 목적으로 함으로써, 자바 문자열로 일일히 응답 HTML을 입력해야한다(고통의 시간).

회원 등록 폼 HTML

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

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


    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        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" + //POST 메서드로 데이터 전송
                " 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");
    }
}

action 의 method 필드를 post로 설정하여 /servlet/member/save 로 넘어가게끔 설정했다.

아 진짜 자바 IDE에서 이따구로 하면 최악이다. 오타 날 확률도 굉장히 높고 무엇보다 시간이 너무 오래걸린다. 무슨 한컴 타자연습하는 것 같다 ㅋㅋ..

아무튼 이런 서블릿을 실행한 결과는 다음과 같다.

이 화면까지만 뜨면 일단 첫번째 관문은 통과한 것이다.

전송을 눌러도 아무 변화가 없는 이유는 아직 저 데이터를 받아 처리하는 서블릿을 만들지 않았기 때문이다.

 

회원 저장

회원 등록 폼으로 부터 받은 POST HTTP 메서드를 처리하는 서블릿을 구현해야한다.

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.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>");
    }
}

getParamter() 메서드로 전달받은 데이터를 잘 파싱하여 HTML과 자바코드를 섞어 작성할 수 있게 되었다. 결과는 다음과 같다.

이렇게 하니 HTML 안에 자바 코드도 포함하여 작성할 순 있게 되었지만, 여전히 개발자 입장에서는 너무 힘든 고난이다.

 

회원 목록 조회

저장된 모든 회원을 조회하는 서블릿이다.

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.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 {

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

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

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

회원 목록에서 한명씩 꺼내와 출력하는 HTML 이다. 크게 어렵진 않다.

호날두는 제가 추가함ㅋ


자바 코드로 한컴타자연습한 기분이 어때?

스프링, 템플릿 엔진 등을 아예 배제하고 자바 코드로만 HTML을 직접 쓰는 방식으로 작성해보았다.

서블릿 덕분에 동적인 HTML을 만드는데는 성공했지만 일일히 HTML을 다 적고 있는 것은 굉장히 비효율적인 방식이다.

특히나 한글자만 오타가 나도 전체 응답 메시지에 영향을 준다.

우리는 개발을 해야지 타자연습을 하고 있으면 안된다..

만약 HTML 문서에서 동적으로 변하는 부분에만 자바 코드를 넣을 수 있는 방법이 있다면 굉장히 편할텐데, 이를 해결하기 위한 방법으로 등장한 대안이 바로 템플릿 엔진(JSP, Thymleaf, Freemarker 등)이다.

백엔드 개발자는 반드시 하나 이상의 템플릿 엔진을 자유자재로 다룰 수 있어야하고, 이를 통해 효율적으로 개발해야 한다 !

 

다음 포스팅에선 지금은 거의 상장 되었지만, 근본 기술인 JSP로 현재 코드를 리팩토링 해보겠다 !

xuv2
@xuv2 :: xuvlog

폭싹 늙었수다

목차