[스프링 MVC 1편] 서블릿, JSP, MVC 패턴

enxnong·2023년 6월 20일
0

Spring

목록 보기
26/26

김영한님의 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술(유료강의)을 보면서 공부한 내용입니다.

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

  • 회원 정보
    username, age
  • 기능
    회원 저장, 회원 목록 조회

회원 도메인 모델

package hello.servlet.domain.member

회원 도메인에 필요한 id(식별자), username, age를 만들고
기본 생성자와 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;
    }
}

회원 저장소

hello.servlet.domain.member

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 {

    // 데이터 저장하는 키 : id, 값 : member
    private static Map<Long, Member> store = new HashMap<>();
    private static long sequence = 0L;

    // 싱글톤으로 생성
    // 객체의 인스턴스가 오직 1개만 생성 => 외부에서 호출 못하게 private으로 지정
    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;
    }

    // id로 회원 조회
    public Member findById(Long id){
        return store.get(id);
    }

    // 전체 회원 조회
    // 저장소에 있는 값들을 찾아서 새로운 ArrayList에 넣어줌
    // 왜? new ArrayList에 값을 넣거나 조작해도 store에 있는 값들을 건드리고 싶지 않아서
    public List<Member> findAll(){
        return new ArrayList<>(store.values());
    }

    // 저장소 데이터 삭제
    public void clearStore(){
        store.clear();
    }

}

회원 저장소 테스트 코드

회원 저장소 기능이 제대로 작동하는지 테스트 코드(Ctrl+Shift+T)를 만들어서 확인한다.

package hello.servlet.domain.member

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.*;

public class MemberRepositoryTest {

    MemberRepository memberRepository = MemberRepository.getInstance();

    // 테스트 끝난 후 초기화
    @AfterEach
    void afterEach(){
        memberRepository.clearStore();
    }

    @Test // 저장 테스트
    void save(){
        // given : 이런게 주어졌을 때
        ...

        // when : 이런걸 실행했을 때
        ...

        // then : 결과가 이거여야돼
        ...
    }

    @Test // 조회 테스트
    void findAll(){
        // given
        ...

        // when
        ...

        // then
        ...
    }

}

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

회원 등록 폼

package hello.servlet.web.servlet;

import hello.servlet.domain.member.MemberRepository;
import org.springframework.validation.annotation.Validated;

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 {
        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");

        PrintWriter w = response.getWriter();
        w.write(...);
    }
}

http://localhost:9090/servlet/members/new-form에 들어가 보면 HTML 폼의 화면을 확인할 수 있다.

회원 저장

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="memberSaveServlce", 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"));
        // request.getParameter의 응답타입은 항상 문자이므로 숫자타임으로 변환시켜야된다.

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

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

        PrintWriter w = response.getWriter();
        w.write(...);

    }
}

회원 목록 조회

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();
        w.write(...);
    }
}

http://localhost:9090/servlet/members/new-form에 들어가 보면 저장된 회원 목록을 조회할 수 있다.

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

JSP를 하기 전에 build.gradle에 있는 dependencies안에 추가적으로 코드를 생성해야된다.

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

회원 등록 폼 JSP

src/main/webapp/jsp/members/new-form.jsp

JSP폼 안에는 반드시 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 문구가 있어야 한다.

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

src/main/webapp/jsp/members/save.jsp

<%@ page import="~~~" %> // 자바의 import 문
<% ~~ %> // 자바 코드 입력, request, response 사용 가능
<%= ~~ %> // 자바 코드 출력

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

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

회원 목록 조회 JSP

src/main/webapp/jsp/members.jsp

<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page import="java.util.List" %>
<%@ page import="hello.servlet.domain.member.MemberRepository" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
 	// MemberListServlet 와 동일
%>
<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(" </tr>");
    }
  %>
  </tbody>
</table>
</body>
</html>

💻 MVC 패턴

개요

Model View Controller(MVC)
서블릿이나, JSP로 처리하던 것을 컨트롤러(Controller)와 뷰(View) 라는 영역으로 역할을 나눈 것

  • 컨트롤러 (Controller)
    HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다. 그리고 뷰에 전달할 결과 데이터를 조회해서 모델에 담는다.

  • 모델 (Model)
    뷰에 출력할 데이터를 담아 전달한다.

  • 뷰 (View)
    모델에 담겨있는 데이터를 사용해서 화면을 생성한다. ex) HTML 생성

적용

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

회원 등록 폼

  • controller

hello.servlet.web.servletmvc.MvcMemberFormServlet

// 컨트롤러
@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 dispatcher = request.getRequestDispatcher(viewPath);// 컨트롤러에서 뷰로 이동할 때 사용하는 것
        dispatcher.forward(request,response); // 서블릿(컨트롤러)에서 jsp를 호출

    }
}

WEB-INF : 이 경로안에 JSP가 있으면 외부에서 직접 JSP를 호출할 수 없다. 항상 컨트롤러를 통해서 JSP를 호출해야 한다.

  • View

main/webapp/WEB-INF/views/new-form.jsp

<%@ 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">
 	...
</form>
</body>
</html>

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

  • 현재 계층 경로: /servlet-mvc/members/
  • 결과: /servlet-mvc/members/save

회원 저장 폼

  • Controller

src/main/java/hello/servlet/web/servletmcv/MvcMemberSaveServlet

@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); // 내부에 member 저장

        String viewPath = "/WEB-INF/views/save-result.jsp";
        ...
    }
}
  • View
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
성공
<ul>
    <%--
    <li>id=<%=((Member)request.getAttribute("member")).getId()%></li>
    ...
    --%>
        <li>id=${member.id}</li>
        <li>...</li>
        <li>...</li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>

회원 목록 조회 폼

  • Controller

src/main/java/hello/servlet/web/servletmcv/MvcMemberListServlet

@WebServlet(name="mvcMemberListServlet", urlPatterns = "/servlet-mvc/members")
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";
        ...
    }
}
  • View

src/main/webapp/WEB-INF/views/members.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!-- jsp에서 반복문을 사용할 수 있는 구문 -->
<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>
	  ...
    </tr>
  </c:forEach>
  </tbody>
</table>
</body>
</html>

profile
높은 곳을 향해서

0개의 댓글