서블릿과 JSP를 통해 MVC를 구현 해볼 예정이다. 구현은 다음과 같다.
서블릿 : 컨트롤러
JSP : 뷰
HttpServletRequest 가 제공하는 내부 저장소 (Attribute) : 모델
/WEB-INF 경로
참고 사항으로, /WEB-INF 라는 폴더를 만들고 그 안에 JSP 코드를 작성하면, 외부에서 호출이 불가능한 JSP 코드가 작성 된다. 즉 우리는 항상 컨트롤러로 JSP를 호출 할 수 있도록 해야한다.
회원 등록 폼
회원등록 폼 - 컨트롤러
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;
@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";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);// 컨트롤러에서 뷰로 이동할 때 사용하는 메서드
dispatcher.forward(request, response);
}
}
설정한 경로로 요청이 들어오면, viewPath의 경로로 설정된 DispatcherServlet을 통해 서블릿이나 JSP로 이동할 수 있다.
request와 response를 담아 foward() 해주면 viewPath로 넘어가게 된다.
dispatcher.foward()의 재귀호출
foward() 메서드 호출시에 해당 경로의 서블릿이나 JSP로 이동할 수 있는데, 이때 리다이렉션이 아닌 서버 내부에서 재귀호출이 일어난다.
이게 무슨 말이냐 하면..
리다이렉션을 떠올려보면 클라이언트에게 서버가 3XX 응답 코드를 내려주면, 클라이언트가 이를 인지할 수 있고, 실제 변경된 URL로의 접속이 새롭게 이루어진다.
foward()는 이와 다르게 서버 내부에서 일어나는 호출이기 때문에 클라이언트가 인지할 수 없고, 현재 URL이 속한 계층 + 뷰에서 설정한 action 필드에 따라 재귀호출이 일어난다. 예시는 아래를 보자
회원 등록 폼 - 뷰
// 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>
foward()를 통해 현재 상대 경로인 save를 입력하면 이전 주소에서 파일명을 제거하고 해당 자리에 save를 채워넣게 되어, /servlet-mvc/members/save 경로의 컨트롤러를 재귀호출하게 된다.
아무튼 결과는 포워딩된 결과는 다음과 같아진다.
/servlet-mvc/members/ -> /servlet-mvc/members/save
이후 등장하는 컨트롤러와 뷰도 위와 같은 로직을 반복한다.
회원 저장
회원 저장 - 컨트롤러
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;
import java.io.IOException;
@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);
// model 정보 기반 view로 던지기
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
여기서 Model 개념이 사용되는데, request가 제공하는 저장소를 모델로 사용하여 viewPath 경로의 뷰에 데이터를 전달한다.
데이터를 저장하는데는 setAttribute() 메서드를 사용하여 key-value 형태로 저장하고, 가져올 때는 getAttribute(key) 메서드를 사용하여 데이터를 가져온다 (일종의 Map).
회원 저장 - 뷰
//save-result.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</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>
해당 뷰에서 컨트롤러에서 넘어온 Attribute의 데이터를 통해 데이터를 출력한다.
JSP 문법에서 ${} 문법을 통해 request의 attribute에 담긴 데이터를 편리하게 조회할 수 있다.
회원 목록 조회
회원 목록 조회 - 컨트롤러
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;
import java.io.IOException;
import java.util.List;
@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);
}
}
save 로직과 동일하다. 차이점은 모델에 담는 데이터가 이번엔 리스트라는 점이다.
해당 모델에 모든 회원 정보를 담아 members.jsp로 던진다.
회원 목록 조회 - 뷰
//members.jsp
<%@ 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>
JSP가 제공하는 taglib 기능을 통해 반복문을 사용하여 출력하는 JSP 코드이다.
서블릿과 JSP로만 흉내냈을 때 한계점
이번 포스팅에서 컨트롤러를 서블릿으로, 뷰를 JSP로 거의 비슷하게 흉내를 내보았다.
결론적으로 이렇게 라이프 사이클을 분리한 덕분에 컨트롤러는 HTTP 메시지를 처리하는데만 집중할 수 있게 되었고, 해당 메시지에서 받은 파라미터를 모델에 담아 던지면 해당 모델의 데이터로 JSP는 뷰만 그리는데 집중할 수 있게 되어 서로의 역할 부담이 확실히 줄어들게 되었다.
하지만, 계속해서 Dispatcher을 만들어주고 /WEB-INF 등의 코드를 작성해줘야하는 것 처럼 반복된 작업이 굉장히 많아졌다.
또한 다른 템플릿 엔진을 사용하게 된다면 모든 코드를 다 변경해야하고, 특히 response와 같은 파라미터는 전혀 사용하지 않았다.
결론 적으로는 서블릿과 JSP 만으로는 공통 처리 로직을 구현하기가 굉장히 힘들다.
그럼 만약 이런 공통된 작업을 해주는 컨트롤러가 있다면 좋지 않을까?
이를 해결해주는 방법이 바로 Front Controller 패턴이다. 다음 포스팅에선 프론트 컨트롤러 패턴을 적용해보겠다 !
'Spring > MVC' 카테고리의 다른 글
[Spring] FrontController 도입하기 V1 (0) | 2025.06.23 |
---|---|
[Spring] Front Controller 패턴 - 개요 (0) | 2025.06.22 |
[Spring] MVC 패턴의 개요 - JSP는 만능이지만 만능이 아님 (1) | 2025.06.22 |
[Spring] 회원 관리 웹 애플리케이션 JSP 리팩토링 (0) | 2025.06.22 |
[Spring] 회원관리 웹 애플리케이션 요구 사항 및 Servlet 구현 (A.K.A 타자연습) (0) | 2025.06.21 |