[Spring] 회원 관리 웹 만들기(1)

JJoSuk·2023년 6월 3일
0

본 프로젝트 자료는 김영한님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술을 참고 제작됐음을 알립니다.

서블릿, JSP, MVC 패턴을 활용해 회원 관리 웹 애플리케이션 을 만들어 보고자 한다.

회원 정보

* 이름 : username

* 나이 : age

기능 요구사항

* 회원 저장

* 회원 목록 조회

위의 요구사항에 맞춰 기본적인 순수 자바 코드만 활용해서 회원 도메인 모델을 생성해보자.


회원 도메인 모델

@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는 Member가 회원 저장소에 저장하면 회원 저장소가 할당된다.

회원 저장소

/*
* 동시성 문제가 고려되어 있지 않음, 실무에서는 ConcurrentHashMap, AtomicLong 사용 고려
* */
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 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();
    }
}
  • 회원 저장소는 싱클톤 패턴 적용
  • 순수 자바만 사용해서 작성
  • 싱글톤은 객체 하나만 생성해야 하므로 private 으로 접근 근지

서블릿으로 회원 관리 웹 애플리케이션 생성

이제부터 서블릿패턴을 적용하고자 한다.

회원 등록 폼

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

        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");
    }
}
  • 서블릿으로 회원 등록 HTML 폼 적용
  • MemberFormServlet 은 단순 회원 정보 입력할 수 있는 HTML Form 만들어서 응답

데이터 전송 관련 코드를 만들었으니 전달 받는 서블릿인 회원 데이터 저장소를 만들어보자.

회원 저장

@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>");
    }
}
  1. 파라미터를 조회해서 Member 객체를 만든다.
  2. Member 객체를 MemberRepository를 통해서 저장한다.
  3. Member 객체를 사용해서 결과 화면용 HTML을 동적으로 만들어서 응답한다.

이제 마지막으로 회원 저장소에 저장되는 데이터들이 실제 보이게 조회할 수 있는 기능을 만들어보자.

회원 리스트

@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();
        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>");
    }
}
  1. memberRepository.findAll() 을 통해 모든 회원을 조회한다.
  2. 회원 목록 HTML을 for 루프를 통해서 회원 수 만큼 동적으로 생성하고 응답한다.

정리

현재 서블릿과 자바 코드로만 만들었을 때, 코드는 매우 복잡하고 비효율적이다. 그래서 조금 더 효율적으로 만들 수 있는 템플릿 엔진이라는게 있다.

템플릿 엔진 중에 지금은 거의 안쓰는 추세인 JSP 라는 녀석이 있는데, 그 녀석을 이용해 현재 코드를 업글해볼 생각이다.


JSP으로 회원 관리 웹 애플리케이션 생성

회원 등록 폼 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

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

<%
    // request, request 사용 가능
    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>

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

회원 목록 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>
    <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>

리스트 화면까지

생성 방식만 다를 뿐 저장소는 공유

정리

장점

  1. JSP 를 사용한 덕에 뷰를 생성하는 HTML 작업을 보다 쉽게 가져갈 수 있다.
  2. 중간중간 동적으로 변경이 필요한 부분에만 자바 코드를 적용

단점

  1. JAVA 코드, 데이터를 조회하는 리포지토리 등등 다양한 코드가 모두 JSP에 노출되어 있다.
  2. JSP 에 할당된 역할이 너무 많다.

이런 나쁜 환경을 개선하고자 나온게 MVC패턴이다.

MVC 패턴 이후



MVC 패턴을 활용해 회원 관리 웹 애플리케이션 생성

  1. 서블릿을 컨트롤러로 사용하고, JSP를 뷰로 사용해서 MVC 패턴을 적용해보자.
  2. Model 은 HttpServletRequest 객체를 사용한다.
  3. request 는 내부에 데이터 저장소를 가지고 있는데, request.setAttribute() , request.getAttribute() 를 사용하면 데이터를 보관하고, 조회할 수 있다.

회원 등록 폼 - 컨트롤러

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

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String viewPath = "/WEB-INF/views/new-form.jsp";
        RequestDispatcher dispather = request.getRequestDispatcher(viewPath);
        dispather.forward(request, response);
    }
}
  • /WEB-INF

String viewPath = "/WEB-INF/views/new-form.jsp";
이 경로 안에 JSP 가 있으면 외부에서 직접 JSP 를 호출 할 수 없다. 우리가 원하는건 항상 컨트롤러를 통해서 JSP 를 호출하는 것이다.

리다이렉트를 사용하면 클라이언트가 인지할 수 있고, 그로인해 URL 경로도 실제로 변경되는 반면에 현재 쓰는 포워드는 서버 내부에서 일어나는 호출이기 떄문에 클라이언트가 전혀 인지 못한다. 경로 변경 x

회원 등록 폼 - 뷰

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

<!-- 상대경로 사용, [현재 URL이 속한 계층 경로 + /save] -->
<form action="save" method="post">
    username: <input type="text" name="username" />
    age:      <input type="text" name="age" />
    <button type="submit">전송</button>
</form>

</body>
</html>

여기서 form의 action을 보면 절대 경로( / 로 시작)가 아니라 상대경로( / 로 시작X)인 것을 확인할 수 있다.
이렇게 상대경로를 사용하면 폼 전송시 현재 URL이 속한 계층 경로 + save가 호출된다.

회원 저장 - 컨트롤러

@WebServlet(name = "mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/save")

public class MvcMemberSaveServlet extends HttpServlet {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        int age = Integer.parseInt(request.getParameter("age"));

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

        //Model 에 데이터를 보관
        request.setAttribute("member", member);

        String viewPath = "/WEB-INF/views/save-result.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}
  • HttpServletRequest를 Model로 사용한다.
  • request 가 제공하는 setAttribute() 를 사용하면 request 객체에 데이터를 보관해서 뷰에 전달할 수 있다.
  • 뷰는 request.getAttribute() 를 사용해서 데이터를 꺼내면 된다.

회원 저장 - 뷰

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
성공
<ul>
    <li>id=${member.id}</li>
    <li>username=${member.username}</li>
    <li>age=${member.age}</li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>

<%= request.getAttribute("member")%> 로 모델에 저장한 member 객체를 꺼낼 수 있지만, 너무 복잡해진다.

JSP는 ${} 문법을 제공하는데, 이 문법을 사용하면 request의 attribute에 담긴 데이터를 편리하게 조회할 수 있다.

회원 목록 조회 - 컨트롤러

public class MvcMemberListServlet extends HttpServlet {

    private MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

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

        request.setAttribute("members", members);

        String viewPath = "/WEB-INF/views/members.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
        dispatcher.forward(request, response);
    }
}

request 객체를 사용해서 List members 를 모델에 보관했다.

회원 목록 조회 - 뷰

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<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>
  <c:forEach var="item" items="${members}">
      <tr>
          <td>${item.id}</td>
          <td>${item.username}</td>
          <td>${item.age}</td>
      </tr>
  </c:forEach>
  </tbody>
</table>

</body>
</html>

모델에 담아둔 members 를 JSP 가 제공하는 taglib 기능을 사용해서 반복하면서 출력했다.
members 리스트에서 member 를 순서대로 꺼내서 item 변수에 담고, 출력하는 과정을 반복한다.

현재 만든 3가지 방식 전부 현재로서 비효율적이며 스프링이 들어가지 않은 방식이다.


MVC 패턴 - 한계

MVC 패턴을 활용해서 덕분에 컨트롤러 역할과 뷰를 랜더링 하는 역할이 명확하게 구분할 수 있지만, 아직까지 컨트롤러에 중복이 많이 남아있고, 필요치 않는 코드들이 남아있다.

그리고 기능이 복잡해질 수 록 컨트롤러에 공통으로 처리해야하는 부분이 점점 더 많이 증가할 것인데, 그만큼 메서드를 항상 호츌해야 하고, 호출하는 것 자체가 중복이다.

profile
안녕하세요

0개의 댓글