개요
부끄러운 얘기지만 저 같은 경우 웹 프레임워크를 배울 때 처음부터 Front Controller 패턴으로 접했기 때문에 요청을 처리하는 과정을 완벽하게 이해하지 못한 상태로 개발을 시작했습니다.
따라서, 조금 옛날 기술이지만 서블릿과 JSP를 통해 MVC 패턴이 탄생하게 된 기원을 이해하고 어떠한 한계점이 있어 Front Controller 패턴이라는 개념이 등장했는지 알아보고자 합니다.
MVC Pattern 이전
- 사용자의 요청을 서블릿이나 JSP가 전부 다 처리 (Controller의 부재)
- 웹브라우저 사용자의 요청을 받은 서블릿이나 JSP는 비즈니스 로직을 처리하고 뷰 렌더링까지 처리
- 뷰 화면을 위한 HTML 만드는 작업과 비즈니스 로직을 모두 서블릿 내에서 처리할 경우 HTML 코드를 자바 코드로 작성해야 하기 때문에 오타 발생하기 쉬움
- JSP를 사용함으로서 HTML 작업을 깔끔하게 가져가고 동적으로 변경이 필요한 부분을 자바 코드로 적용함으로써 조금 단순화시켰지만, 역시 로직과 뷰가 같이 있다는 점이 큰 문제점
- JSP 내 렌더링 될 HTML 코드, 자바 코드, 데이터를 조회하는 레포지토리 등 너무나 많은 역할을 하기 때문에 유지보수하기 힘듦
- HTML 코드를 수정해야 할 때도 해당 파일을 열어야하고, 비즈니스 로직을 수정해야할 때도 해당 파일을 열어야 함
- 분리가 안되어있기 때문에 유지 보수하기가 매우 힘듦

뷰와 로직이 섞여있는 Servlet 예시
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%@ page import="com.tistory.jaimemin.servlet.domain.example.repository.ExampleRepository" %> | |
<%@ page import="com.tistory.jaimemin.servlet.domain.example.Example" %> | |
<%@ page contentType="text/html;charset=UTF-8" language="java" %> | |
<% | |
// request, response 사용 가능 | |
ExampleRepository exampleRepository = ExampleRepository.getInstance(); | |
String username = request.getParameter("username"); | |
String blog = request.getParameter("blog"); | |
Example example = Example.builder() | |
.username(username) | |
.blog(blog) | |
.build(); | |
exampleRepository.save(example); | |
%> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
</head> | |
<body> | |
성공 | |
<ul> | |
<li>username=<%=example.getUsername()%></li> | |
<li>blog=<%=example.getBlog()%></li> | |
</ul> | |
</body> | |
</html> |
뷰와 로직이 섞여있는 JSP 예시
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%@ page import="com.tistory.jaimemin.servlet.domain.example.repository.ExampleRepository" %>
<%@ page import="com.tistory.jaimemin.servlet.domain.example.Example" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
// request, response 사용 가능
ExampleRepository exampleRepository = ExampleRepository.getInstance();
String username = request.getParameter("username");
String blog = request.getParameter("blog");
Example example = Example.builder()
.username(username)
.blog(blog)
.build();
exampleRepository.save(example);
%>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
성공
<ul>
<li>username=<%=example.getUsername()%></li>
<li>blog=<%=example.getBlog()%></li>
</ul>
</body>
</html>
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%@ page import="com.tistory.jaimemin.servlet.domain.example.repository.ExampleRepository" %> | |
<%@ page import="com.tistory.jaimemin.servlet.domain.example.Example" %> | |
<%@ page contentType="text/html;charset=UTF-8" language="java" %> | |
<% | |
// request, response 사용 가능 | |
ExampleRepository exampleRepository = ExampleRepository.getInstance(); | |
String username = request.getParameter("username"); | |
String blog = request.getParameter("blog"); | |
Example example = Example.builder() | |
.username(username) | |
.blog(blog) | |
.build(); | |
exampleRepository.save(example); | |
%> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
</head> | |
<body> | |
성공 | |
<ul> | |
<li>username=<%=example.getUsername()%></li> | |
<li>blog=<%=example.getBlog()%></li> | |
</ul> | |
</body> | |
</html> |
MVC Pattern (Model 2 방식)
- Model, View, Controller 모두 존재하는 MVC Pattern
- Controller: HTTP 요청을 받아 파라미터를 검증하고, 비즈니스 로직을 실행. 그리고 뷰에 전달할 데이터를 모델에 담음
- 비즈니스 로직을 Controller 내에서 실행하는 방식이 Model 1 방식
- 비즈니스 로직을 별도 계층인 Service 계층에서 실행하고 Controller가 Service 계층을 호출하는 방식이 Model 2 방식
- Model: Controller로부터 데이터를 전달받은 데이터를 모델에 전달하는 역할
- View: 모델에 담겨있는 데이터를 사용해서 화면을 렌더링, 모델 덕분에 비즈니스 로직을 몰라도 데이터를 활용 가능 (역할 분리)
- 기존의 Model 1 방식은 클라이언트의 요청을 JSP가 모두 처리했다면, Model 2 방식은 요청을 컨트롤러와 뷰라는 영역으로 서로 역할을 나눔
- 역할을 분리함에 따라 유지보수 기존 방식보다 수월

Servlet, JSP를 통한 MVC Pattern의 한계점
- 뷰 렌더링과 컨트롤러 역할을 분리한 건 좋지만 페이지가 늘어남에 따라 컨트롤러 내 중복 코드 다량 발생
- View로 이동하는 Forward 코드 중복
- View 주소 즉, ViewPath 설정하는 코드 중복
- 별도 응답을 보낼 필요가 없는 경우 서블릿 내 response 코드가 사용되지 않음
- HttpServletRequest, HttpServletResponse를 사용하는 테스트 코드 작성하기 쉽지 않음
- 정리하자면, 공통 처리가 어려운 것이 문제 (이를 해결하기 위해 Front Controller 패턴 도입)
중복되는 컨트롤러 코드 예시
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@WebServlet(name = "exampleServlet", urlPatterns = "/example") | |
public class ExampleServlet extends HttpServlet { | |
@Override | |
protected void service(HttpServletRequest request, HttpServletResponse response) | |
throws ServletException, IOException { | |
// 매 컨트롤러마다 중복되는 코드 | |
String viewPath = "/WEB-INF/views/example.jsp"; | |
RequestDispatcher requestDispatcher = request.getRequestDispatcher(viewPath); | |
requestDispatcher.forward(request, response); | |
} | |
} |
비고
forward vs redirect
- forward는 서버 내부에서 일어나는 호출이기 때문에 URL이 바뀌지 않고 클라이언트가 인지 못함
- 반면, redirect의 경우 클라이언트에 응답이 나갔다가, 클라이언트가 redirect 경로로 다시 요청 (따라서, URL 경로도 변경됨)
WEB-INF의 역할
- 해당 경로 내 JSP가 있을 경우 외부에서 직접 호출 못함
- 컨트롤러를 통해서만 JSP를 호출할 수 있게 해주는 역할
출처
인프런 스프링 MVC 1편 (김영한 강사님)
반응형
'면접 준비' 카테고리의 다른 글
PRG 패턴 (Post/Redirect/Get) (2) | 2021.06.14 |
---|---|
Front Controller 패턴 (0) | 2021.06.07 |
서블릿 (Servlet) 간단 정리 (0) | 2021.05.28 |
웹 서버 vs WAS 비교 (0) | 2021.05.28 |
싱글톤(Singleton) 패턴 (0) | 2021.05.16 |