delpho

JSP MVC Pattern (등장배경, Model 1 / Model 2, 한계) 본문

CS/Spring

JSP MVC Pattern (등장배경, Model 1 / Model 2, 한계)

delpho 2024. 4. 8. 22:37

1. MVC Pattern 등장 배경


  • MVC 패턴은 JSP의 등장 이후 제안된 패턴이 아니라, 1970년대 후반에 제안된 패턴입니다. Smalltalk 언어를 고도화하는데에 사용되었다고 알고 있습니다. (이전 포스팅 참고)
  • 프레젠테이션 로직과 비즈니스 로직이 분리되어있지 않은 Servlet의 단점을 해결하기 위해 JSP가 등장하였고, 이 JSP를 더욱 효과적으로 사용하기 위해 MVC 패턴을 적용하기 시작했습니다.

 

[ Servlet과 JSP ]

 

Servlet 코드

@WebServlet(name = "memberSaveServlet", urlPatterns = "/servlet/members/save")
public class MemberSaveServlet extends HttpServlet {

    private final MemberRepository memberRepository = MemberRepository.getInstance();

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

        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");
        final PrintWriter printWriter = response.getWriter();
        printWriter.write("<html>\\n" +
            "<head>\\n" +
            " <meta charset=\\"UTF-8\\">\\n" +
            "</head>\\n" +
            "<body>\\n" +
            " <li>id=" + member.getId() + "</li>\\n" +
            " <li>username=" + member.getUsername() + "</li>\\n" +
            " <li>age=" + member.getAge() + "</li>\\n" +
            "</body>\\n" +
            "</html>");
    }
}

 

 

JSP 코드

<%@ page import="hello.servlet.basic.domain.member.Member" %>
<%@ page import="hello.servlet.basic.domain.member.MemberRepository" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    final MemberRepository memberRepository = MemberRepository.getInstance();
    final String username = request.getParameter("username");
    final int age = Integer.parseInt(request.getParameter("age"));
    final Member member = new Member(username, age);
    memberRepository.save(member);
%>
<html>
<head>
    <title>Title</title>
</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 모두, 뷰와 비즈니스 로직이 결합되어있다.

⇒ 변경 생명 주기가 다른 두 가지 요소가 결합되어있다!!

 

비즈니스 로직은 Servlet이, 프레젠테이션 로직은 JSP가 담당하도록 하기 위해 MVC 패턴 적용(Model, View, Controller)

 

 

 

 

2. MVC Pattern


 

하나의 애플리케이션을 Model, View, Controller 세 가지 구성요소로 구분한 패턴

 

[ Model ]

데이터와 비즈니스 로직, 그리고 데이터의 처리 방법을 관리하는 핵심 구성 요소

  • 역할: 데이터의 저장, 조회, 수정, 삭제와 같은 데이터 관리와 비즈니스 로직의 실행을 담당합니다.
  • 특징: 모델은 다른 구성 요소(View, Controller)와 독립적으로 작동하여, 데이터 변경이 있을 경우 이를 View에 반영할 수 있도록 설계됩니다.

 

[ View ]

사용자에게 정보를 표시하는 부분, HTML, JSP 등을 사용하여 구현

  • 역할: 모델에서 처리한 데이터를 사용자에게 시각적으로 표현합니다. 사용자의 요청에 따라 다른 데이터를 표시하는 역할도 합니다.
  • 특징: View는 Model에 직접적으로 데이터를 요청하지 않고, Controller를 통해 필요한 데이터를 전달받아 사용자에게 표시합니다.

 

[ Controller ]

사용자의 입력을 받아 비즈니스 로직을 처리하는 부분입니다. 사용자의 요청을 분석하여 Model을 조작하고, 그 결과를 View에 전달합니다.

  • 역할: 사용자의 요청을 받아들여, 해당 요청에 맞는 Model의 데이터 처리를 요청하고, 처리 결과를 바탕으로 적절한 View를 선택하여 사용자에게 응답합니다.
  • 특징: Controller는 Model과 View 사이의 중재자 역할을 하며, Model과 View의 직접적인 의존성을 제거함으로써 애플리케이션의 유연성과 확장성을 증가시킵니다.

 

[ JSP MVC 패턴의 흐름 ]

  1. 사용자 요청: 사용자는 웹 브라우저를 통해 특정 작업을 요청합니다.
  2. Controller 동작: 요청을 받은 Controller는 요청의 성격을 분석합니다.
  3. Model 조작: Controller는 분석 결과에 따라 적절한 Model에 데이터 처리를 요청합니다.
  4. 데이터 반환: Model은 요청받은 데이터 처리 작업을 수행한 후 결과를 Controller에 반환합니다.
  5. View 선택: Controller는 Model로부터 반환받은 데이터를 바탕으로 적절한 View를 선택합니다.
  6. 응답 생성: 선택된 View는 Controller로부터 받은 데이터를 사용하여 사용자에게 보여줄 응답을 생성합니다.
  7. 사용자에게 응답 전송: 생성된 응답은 사용자에게 전송되어 사용자의 웹 브라우저에 표시됩니다.

 

 

 

 

 

3. Model 1 / Model 2 Architecture


[ Model 1 Architecture ]

JSP 페이지가 직접 요청을 처리하고, 비즈니스 로직을 실행하며, 응답을 생성하는 구조입니다. (구성 요소에 Servlet이 따로 없음)

  • 이 아키텍처에서는 JSP 페이지 내에서 JavaBeans 컴포넌트를 사용하여 데이터를 처리하고, 비즈니스 로직을 구현할 수 있습니다.
  • 장점: 구현이 간단하고 직관적이며, 작은 프로젝트나 단순한 웹 애플리케이션에 적합합니다.
  • 단점: JSP 페이지 내에 프레젠테이션 로직과 비즈니스 로직이 혼재되어 있어 유지보수와 확장이 어렵습니다. 이로 인해 애플리케이션이 복잡해질수록 관리가 힘들어지며, 재사용성이 낮아집니다.

 

JSP 예시

<%@ page import="java.util.*, myapp.models.UserDAO" %>
<%
    UserDAO userDAO = new UserDAO();
    List users = userDAO.getAllUsers();
    request.setAttribute("users", users);
%>
<html>
<body>
    <h2>User List</h2>
    <ul>
        <% for(int i=0; i<users.size(); i++) { %>
            <li><%= users.get(i).getName() %></li>
        <% } %>
    </ul>
</body>
</html>

 

 

 

 

[ Model 2 Architecture = 일반적으로 말하는 MVC 패턴 ]

MVC 패턴을 기반으로 한 구조로, 애플리케이션을 모델(Model), 뷰(View), 컨트롤러(Controller)의 세 부분으로 나눕니다.

  • 이 아키텍처에서는 컨트롤러 역할을 하는 서블릿이 사용자 요청을 받아 처리하고, 적절한 비즈니스 로직을 모델을 통해 실행한 후 결과를 뷰에 전달하여 사용자에게 응답을 보여줍니다.
  • 장점: 프레젠테이션 로직과 비즈니스 로직이 분리되어 있어 유지보수와 확장이 용이합니다. 애플리케이션의 규모가 커질수록 이 아키텍처의 장점이 더욱 부각됩니다.
  • 단점: 모델 1 아키텍처에 비해 초기 구축이 복잡할 수 있으며, 작은 프로젝트에서는 과도한 설계로 인식될 수 있습니다.

 

 

 

Servlet 예시 2개

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
import myapp.models.UserDAO;

public class UserListServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        UserDAO userDAO = new UserDAO();
        List users = userDAO.getAllUsers();
        request.setAttribute("users", users);
        RequestDispatcher dispatcher = request.getRequestDispatcher("/userList.jsp");
        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 requestDispatcher = request.getRequestDispatcher(viewPath);
        requestDispatcher.forward(request, response);
    }
}

 

 

JSP 예시 2개

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <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>

 

 

 

5. JSP MVC 패턴의 한계


1. 복잡한 구성

  • 구성의 복잡성: 모델 2 아키텍처는 분리된 컨트롤러, 모델, 뷰의 구성으로 인해 초기 설정이 복잡하고, 프로젝트의 구조를 이해하고 관리하기가 더 어려울 수 있습니다. 특히 큰 프로젝트에서는 다수의 컨트롤러와 뷰가 상호작용하면서 이해와 관리의 복잡도가 증가합니다.

2. 뷰와 로직의 혼재

  • JSP 내의 로직 혼재: JSP 페이지 내에서 Java 코드를 사용하여 비즈니스 로직이나 흐름 제어를 처리하는 것은 권장되지 않습니다. 이는 뷰와 로직의 명확한 분리를 어렵게 만들며, 코드의 유지보수와 가독성을 저하시킵니다.

3. 재사용성과 유연성의 제한

  • 컴포넌트 재사용의 어려움: JSP와 서블릿만을 사용하여 개발할 때, UI 컴포넌트의 재사용성이 낮고, 화면 간의 데이터 전달 및 재사용 로직을 구현하기 위한 추가적인 노력이 필요합니다.
  • 프론트엔드와 백엔드의 경계: 최근 웹 개발 트렌드는 프론트엔드와 백엔드를 명확히 분리하는 방향으로 이동하고 있습니다. JSP 기반의 애플리케이션은 이러한 구조적 분리를 어렵게 만들 수 있습니다.

4. 반복적인 코드 작성

  • 공통된 요청 처리 로직(예: 권한 검사, 로깅, 요청 데이터 파싱 등)을 각 컨트롤러나 JSP 페이지마다 반복적으로 작성해야 합니다. 이는 개발 과정에서 중복 코드가 많아지고, 유지보수의 어려움을 증가시킵니다.

5. 요청 처리의 일관성 부족

  • 디스패처 서블릿 같은 중앙 집중식 요청 처리 메커니즘이 없을 경우, 각 요청을 처리하는 방식의 일관성을 유지하기 어려워집니다. 개발자가 각각의 컨트롤러나 JSP 페이지에서 요청 처리 로직을 개별적으로 구현해야 하기 때문에, 애플리케이션 전반에 걸친 처리 로직의 표준화가 어렵습니다.

6. 라우팅 및 컨트롤러 관리의 복잡성

  • 각 요청 URL을 처리할 컨트롤러를 직접 매핑해야 하며, 복잡한 웹 애플리케이션에서는 이러한 매핑과 관리가 복잡하고 어려워질 수 있습니다.

 

여기서, 4,5,6번의 한계를 주목해봅시다.

JSP MVC Pattern을 보면, 클라이언트에서 들어오는 호출을 할때마다 각각의 Controller (서블릿)들이 구동됩니다. 하지만, 서블릿이 가지는 공통된 부분을 매번 작성해야되기에 비효율적입니다. 공통된 부분이 있으면 이를 한번에 처리해주는 일종의 수문장 역할을 하는 무언가 있으면 더 좋을 것 같습니다. 위한 같은 한계점을 돌파하기 위해 스프링 MVC는 Dispatcher Servlet라는 존재를 도입하게 됩니다.

 

 

Model과 View는 서로의 정보를 갖고 있지 않는 독립적인 상태라고 하지만 Model과 View사이에는 Controller를 통해 소통을 이루기에 의존성이 완전히 분리될 수 없습니다. 그래서 복잡한 대규모 프로그램의 경우 다수의 View와 Model이 Controller를 통해 연결되기 때문에 컨트롤러가 불필요하게 커지는 현상이 발생하기도 합니다. 이러한 현상을 Massive-View-Controller 현상이라고 하며 이를 보완하기 위해 MVP, MVVM, Flux, Redux등의 다양한 패턴들이 생겨났습니다.

 

 

 

 

6. JSP MVC pattern의 한계뿐만 아니라..


1. EJB(Enterprise JavaBeans)의 복잡성과 성능 문제

  • Spring이 처음 등장했을 때, 많은 엔터프라이즈 자바 애플리케이션은 EJB를 사용하고 있었습니다. EJB는 분산 컴포넌트와 트랜잭션 관리를 제공하는 강력한 기술이었지만, 설정과 사용이 복잡하고, 특히 성능 문제가 종종 발생했습니다. Spring은 이러한 EJB의 복잡성을 대체하고, 더 간단하며 가벼운 방식으로 엔터프라이즈 애플리케이션을 개발할 수 있는 방법을 제공하고자 했습니다.

2. 보일러플레이트 코드의 감소 필요성

  • 기존의 Java EE 개발에서는 반복되는 코드 패턴이 많이 요구되었습니다. 예를 들어, JDBC를 사용한 데이터베이스 작업, 초기 J2EE 애플리케이션에서의 리소스 관리 등은 많은 반복 코드를 필요로 했습니다. Spring은 의존성 주입(DI), 템플릿 메소드 디자인 패턴 등을 통해 이러한 반복되는 코드를 줄이는 방법을 제공함으로써 개발자의 생산성을 향상시키고자 했습니다.

3. 의존성 관리와 결합도 감소

  • 대규모 애플리케이션에서는 클래스 간의 의존성 관리가 중요한 이슈가 됩니다. 기존 방식에서는 컴포넌트 간의 높은 결합도로 인해 변경이 어렵고 테스트가 어려운 문제가 있었습니다. Spring의 의존성 주입 기능은 이러한 문제를 해결하고, 느슨한 결합을 통해 유연성을 높이고자 했습니다.

 

등등으로 인해 다음 포스팅에 나올 Spring Framework가 등장하게 되었습니다.