[Spring] MVC 패턴이 등장하게 된 이유 !! (feat. 서블릿, JSP)

728x90

 

서블릿

회원 추가 서블릿

@WebServlet(name = "memberFormServlet", urlPatterns = "/servlet/members/newform")
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" +
            ...
            "</html>\n");
  }
}

회원 저장 서블릿

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

    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("<!DOCTYPE html>\n" +
            "<html>\n" +
            ...
            "</html>\n");
  }
}

회원 조회 서블릿

@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("<!DOCTYPE html>\n");
    w.write("<html>\n");
    ...
    for (Member member : members) {
      w.write("   <tr>\n");
      w.write("       <td>" + member.getId() + "</td>\n");
      w.write("       <td>" + member.getUsername() + "</td>\n");
      w.write("       <td>" + member.getAge() + "</td>\n");
      w.write("   </tr>");
    }
    ...
    w.write("</html>\n");
  }
}

간단한 회원 추가, 회원 저장, 회원 조회 기능이다.

화면에 보이는 html 을 전부 자바 코드로 작성하여 해당 클래스에서 관리된다.

 

서블릿의 단점

- 자바 코드로 HTML 을 작성해야 해서 복잡하다.

 

템플릿 엔진의 등장

이처럼 자바 코드로 HTML 을 작성하는 것이 아닌

HTML 문서에 동적으로 변경하는 부분에만 자바 코드를 넣을 수 있도록 하는 `템플릿 엔진`이 등장했다.

ex. `JSP`, `Thymeleaf`, `Freemarker`, `Velocity` 등

 

JSP

JSP 라이브러리 추가

// build.gradle
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'jakarta.servlet:jakarta.servlet-api'
implementation 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api'
implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl'

회원 추가 JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
...
</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" %>
<%
    MemberRepository memberRepository = MemberRepository.getInstance();

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

    Member member = new Member(username, age);
    memberRepository.save(member);
%>
<html>
...
<ul>
 <li>id=<%=member.getId()%></li>
 <li>username=<%=member.getUsername()%></li>
 <li>age=<%=member.getAge()%></li>
</ul>
...
</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>
...
<%
    for (Member member : members) {
        out.write("    <tr>\n");
        out.write("        <td>" + member.getId() + "</td>\n");
        out.write("        <td>" + member.getUsername() + "</td>\n");
        out.write("        <td>" + member.getAge() + "</td>\n");
        out.write("    </tr>");
    }
%>
...
</html>

 

`<%@ page contentType="text/html;charset=UTF-8" language="java" %>` : JSP 문서라는 뜻

`<%@ page import="hello.servlet.domain.member.MemberRepository" %>` : 자바의 import 문

`<% ~~ %>` : 자바 코드 입력

`<%= ~~ %>` : 자바 코드 출력

`${ ~~ }` : request 에 담긴 데이터를 조회

 

`<%@ tagLib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>` 추가 후

<c:forEach var="item" items="${members}">
  ...${item.id} 사용...
</c:forEach>

이때, request 에 담긴 데이터에서 "members" 이름을 가진 데이터를 for 문으로 순회하며 조회할 수 있다.

 

JSP 의 특징

- JSP는 최초 요청 시 서블릿으로 변환되며 이후에는 서블릿처럼 동작한다.

- request, response 는 JSP 기본 내장 객체에 포함되어 있어 별도 선언 없이 사용할 수 있다.

- HTML 을 중심으로 하고, 동적으로 변경하는 부분에만 자바 코드를 부분 입력/출력한다.

 

JSP 의 단점

- 비즈니스 로직과, 뷰를 처리하는 로직이 합쳐져 있다.

- JSP 의 역할이 너무 많다.

 

MVC 패턴의 등장

비즈니스 로직과 뷰를 처리하는 로직을 한 곳에서 처리하는 것이 아닌,

비즈니스 로직은 서블릿처럼 다른 곳에서 처리하고 JSP 는 HTML 로 뷰를 처리하는 역할만 담당하도록 하는 MVC 패턴이 등장했다.

 

MVC 패턴

클라이언트 ① 호출
→ → → →
컨트롤러
(컨트롤러 로직)

→ → → →
서비스, 리포지토리
(비즈니스 로직)
(데이터 접근)
 



  이터 전달 ↘   
  Model  
    데이터 참조  
← ← ← ←
⑥ 응답

(뷰 로직)
   

 

회원 추가 컨트롤러

@WebServlet(name = "mvcMemberFormServlet", urlPatterns = "/servlet-mvc/members/newform")
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);
  }
}

회원 저장 컨트롤러

@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);
    request.setAttribute("member", member);
    
    String viewPath = "/WEB-INF/views/save-result.jsp";
    RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
    dispatcher.forward(request, response);
  }
}

회원 조회 컨트롤러

@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";
    RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
    dispatcher.forward(request, response);
  }
}

 

`dispatcher.forward()` : 다른 서블릿이나 JSP 로 이동할 수 있는 기능. 서버 내부에서 다시 호출이 발생

 

HttpServletRequest 가 제공하는 임시 저장소 기능가 Model 역할을 대신할 수 있다.

`request.setAttribute()` : request 객체에 데이터를 보관하여 뷰에 전달

`request.getAttribute()` : 뷰가 request 객체에 저장된 데이터를 조회

 

MVC 패턴의 특징

- 항상 컨트롤러를 거쳐 뷰로 넘어간다.

- 컨트롤러와 뷰가 분리되었다


> 참고 <

`/WEB-INF` 경로 안에 JSP 가 있으면, 외부에서 이 JSP 를 직접 호출할 수 없다.

컨트롤러에서만 JSP 를 호출하기 위해, 해당 경로 안에 JSP 파일을 생성한다.

 

MVC 패턴의 단점

- `dispatcher.forward()` 중복, `viewPath` 중복

- 사용하지 않는 `request`, `rsponse` 파라미터

- 요청을 처리하는 서블릿마다 중복 코드가 발생하기 때문에 공통처리가 어려움

 

> 다음 글에서 위의 MVC 패턴을 점진적으로 리팩토링 하여 Spring MVC 의 모습으로 변환할 것이다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

출처 | 스프링 MVC 1(김영한) - 인프런

728x90