MVC 패턴
MVC 패턴이란?
Model View Controller의 약자로, 디자인 패턴 중 하나입니다. MVC 패턴이 있기 전에는 하나의 서블릿이나 JSP에서 어떤 애플리케이션의 모든 기능들을 구현하고 처리하였습니다. 이렇게 분리할 수 있는 기능들도 다 같은 곳에서 처리하다 보니, 유지보수 하기에 어려움이 있었습니다. 이렇게 서블릿이나 JSP로 처리하던 것을 모델(Model), 뷰(View), 컨트롤러(Controller)라는 영역으로 서로 역할을 나눈 것이 MVC 패턴입니다. 웹 애플리케이션은 보통 이 MVC 패턴을 주로 사용합니다.
- Controller : HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행 혹은 호출합ㄴ다. 그리고 뷰에 전달할 결과 데이터를 조회해서 모델에 담습니다.
- Model: 뷰에 출력할 데이터를 담아둡니다. 뷰가 필요한 데이터를 모두 모델에 담아서 전달해 주는 덕분에 뷰는 비즈니스 로 직이나 데이터 접근을 몰라도 되고, 화면을 렌더링 하는 일에 집중할 수 있습니다.
- View: 모델에 담겨있는 데이터를 사용해서 화면을 그리는 일에 집중합니다. 아래 예제에서는 HTML을 생성하는 부분을 의미합니다.
참고로, Controller에 비즈니스 로직을 작성해도 되지만, 이렇게 되면 Controller가 너무 많은 역할을 담당하게 됩니다. 그래서 일반적으로 비즈니스 로직은 Service라는 계층을 별도로 생성해서 처리하도록 합니다. 그리고 Controller가 Service를 호출하여 비즈니스 로직을 실행하도록 구성합니다.
MVC 패턴 예시
아래 코드는 서블릿을 Controller로 사용하고, JSP를 View로 사용한 MVC 패턴입니다. Model의 경우, HttpSrvletRequest 객체를 이용하여 데이터를 보관하고 조회할 수 있습니다.
Controller - Servlet
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@WebServlet(name = "mvcServlet", urlPatterns = "/servlet-mvc/books")
public class MyServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Book book1 = new Book(1, "my book1");
Book book2 = new Book(2, "my book2");
Book book3 = new Book(3, "my book3");
List<Book> bookList = new ArrayList<>();
bookList.add(book1);
bookList.add(book2);
bookList.add(book3);
request.setAttribute("books", bookList); // Model: 데이터 저장
String viewPath = "/WEB-INF/views/books.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response); // 다른 서블릿이나 JSP로 이동할 수 있는 기능
}
}
- /WEB-INF: 이 경로안에 JSP가 있으면 외부에서 직접 JSP를 호출할 수 없습니다. 이렇게 설정한 이유는, 항상 Controller를 통해서 JSP를 호출하기 위해서 외부에서 호출 불가능하도록 하였습니다.
- redirect vs forward
- redirect: 실제 클라이언트(웹 브라우저)에 응답이 나갔다가, 클라이언트가 redirect 경로로 다시 요청합니다. 따라서 클라이언트가 인지할 수 있고, URL 경로도 실제로 변경됩니다.
- Forward: 서버 내부에서 일어나는 호출이기 때문에 클라이언트가 전혀 인지하지 못합니다. 즉, 클라이언트 모르게 서버 내부에서 서블릿에서 JSP로 호출하게 되는 것입니다.
View - jsp
아래 코드는 /WEB-INF/views 경로에 있는 books.jsp 파일입니다.
<%@ 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>
<th>Books</th>
<table>
<thead>
<th>id</th>
<th>title</th>
</thead>
<tbody>
<c:forEach var="item" items="${books}">
<tr>
<td>${item.id}</td>
<td>${item.title}</td>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
Book 클래스
import lombok.Getter;
@Getter
public class Book {
private Integer id;
private String title;
public Book(Integer id, String title) {
this.id = id;
this.title = title;
}
}
Main 클래스
@SpringBootApplication
@ServletComponentScan //서블릿 직접 등록
public class ActuatorApplication {
public static void main(String[] args) {
SpringApplication.run(ActuatorApplication.class, args);
}
}
Gradle Dependency
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
//JSP 추가 시작
implementation 'org.apache.tomcat.embed:tomcat-embed-jasper'
implementation 'jakarta.servlet:jakarta.servlet-api' //스프링부트 3.0 이상
implementation 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api' //스프링부트 3.0 이상
implementation 'org.glassfish.web:jakarta.servlet.jsp.jstl' //스프링부트 3.0 이상
//JSP 추가 끝
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
실행 결과
MVC 패턴의 한계
MVC 패턴을 적용하니까 Controller 역할과, View 역할을 명확하게 구분할 수 있게 되었습니다. 하지만 MVC의 Controller에서 여러 단점을 발견할 수 있습니다.
1. 중복 코드
Controller가 여러 개 일 경우 View로 이동하는 코드를 항상 중복하여 작성하고 호출해야 합니다.
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
또한 ViewPath 를 직접 설정하는 부분도 있고, 이 코드 또한 중복되어 작성해야 할 것입니다.
String viewPath = "/WEB-INF/views/books.jsp"
만약, jsp가 아닌 다른 뷰로 변경해야 한다면 전체 코드를 다 변경해야 하는 번거로움이 있습니다.
2. 사용하지 않는 코드
위 Servlet 코드에서, HttpServletResponse는 사용하지 않습니다. 또 이런 HttpServletRequest, HttpServletResponse를 사용하는 코드는 test case를 작성하기도 어렵습니다.
이러한 단점들을 극복하려면, Controller 호출 전에 먼저 공통 기능을 뽑아 미리 처리해야 합니다. 이렇게 공통 기능을 선처리하는 역할을 Front Controller 패턴을 도입하여 도입할 수 있습니다. 다음 포스팅은 이 Front Controller 패턴을 도입한 MVC 패턴에 대해서 작성해 보겠습니다.
[참고자료]
김영한, " 스프링 MVC 1편", 인프런