MVC 1 - MVC 패턴 적용

Jan 16, 2023

7 mins read

앞에서 만든 서블릿과 JSP를 사용하여 MVC 패턴을 적용해보자. 서블릿이 컨트롤러가 되고 JSP가 뷰가 된다.
모델은 HttpServletRequest객체의 request 저장소로 보면 된다.

1. 컨트롤러 뷰 분리하기.

  • 클라이언트의 요청은 무조건 컨트롤러를 거치도록 한다.
  • hello.servlet.web 아래 servletmvc 패키지를 만들고 회원 등록용 컨트롤러 MvcMemberFormServlet를 만든다.
  • 클라이언트가 /servlet-mvc/members/new-form로 요청을 하면 new-form.jsp로 포워드 된다.
package hello.servlet.web.servletmvc;

import java.io.IOException;

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;

//컨트롤러 기능
@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";
		
		//getRequestDispatcher는 컨트롤러에서 뷰로 이동할 때 사용된다.
		RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
		//jsp를 찾아서 호출해준다.
		dispatcher.forward(request, response);
	}
	
}
  • 회원등록 용 뷰는 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">
	 username: <input type="text" name="username" />
	 age: <input type="text" name="age" />
 <button type="submit">전송</button>
</form>
</body>
</html>
  • hello.servlet.web.servletmvc 패키지에 회원 저장용 컨트롤러 MvcMemberSaveServlet을 만든다.
  • 위 상대경로/save로 action이 동작했기때문에 /servlet-mvc/members/save 컨트롤러로 오게되고 비지니스 로직 수행 후
    save-result.jsp 뷰로 이동된다.
package hello.servlet.web.servletmvc;

import java.io.IOException;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
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;

@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);
		
		String viewPath = "/WEB-INF/views/save-result.jsp";
		RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
		dispatcher.forward(request, response);
		
	}
	
}
  • 회원등록 결과 뷰 webapp/WEB-INF/views 폴더에 save-result.jsp 이다.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
 <meta charset="UTF-8">
</head>
<body>
성공
<ul>
	 <li>id=${member.id}</li>
	 <li>username=${member.username}</li>
	 <li>age=${member.age}</li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>
  • 회원조회 용 컨트롤러 hello.servlet.web.servletmvc 패키지에 MvcMemberListServlet을 만든다.
package hello.servlet.web.servletmvc;

import java.io.IOException;
import java.util.List;

import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
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;

@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);
	}
	
}
  • 회원목록 조회 뷰 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"%>
<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>
			<td>${item.username}</td>
			<td>${item.age}</td>
		</tr>
	</c:forEach>
 </tbody>
</table>
</body>
</html>

2. MVC 패턴의 한계

  • 위와 같이 MVC 패턴을 적용해도 여전히 불편한 점 들이 있다.

1. 뷰 이동 코드 중복 : 컨트롤러에서 뷰로 이동하는 코드가 중복된다.

RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);

2. ViewPath 중복 : prifix인 /WEB-INF/views/ 부분과 suffix인 .jsp는 계속 중복으로 명시되며 한꺼번에 수정하기 어렵다.
3. 공통처리가 어렵다 : 프로젝트가 커지고 기능이 많아질수록 공통 코드가 많아질 텐데 따로 메서드로 뽑아도
컨트롤러 마다 계속 그 메소드를 호출해야 한다.

3. Spring MVC의 핵심

  • 위와 같은 문제를 해결하기 위해 공통 기능을 처리하는 프론트 컨트롤러(Front Controller) 패턴을 도입하여 문제를 해결한다.