공부 내용을 정리하고 앞으로의 학습에 이해를 돕기 위해 작성합니다.
MVC 패턴 - 적용
서블릿을 컨트롤러로, JSP를 뷰로 사용하여 MVC 패턴을 적용한다. 데이터는 HttpServletRequest 객체를 통해 모델 역할을 수행하며, request.setAttribute()로 데이터를 저장하고 request.getAttribute()로 조회할 수 있다.
회원 등록
회원 등록 폼 - 컨트롤러
hello.servlet.web.servletmvc.MvcMemberFormServlet
package hello.servlet.web.servletmvc;
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);
}
}
MvcMemberFormServlet은 회원 등록 폼을 보여주기 위해 JSP로 이동한다. 이때, dispatcher.forward() 메서드를 사용하여 지정된 JSP로 서버 내부에서 요청을 포워드 한다. 이 방식은 클라이언트가 인지하지 못한 채 서버 내부에서 다른 서블릿이나 JSP로 이동할 수 있게 해 준다.
또한, JSP 파일이 /WEB-INF 경로 안에 위치해 있기 때문에, 외부에서 직접 접근할 수 없고 항상 컨트롤러를 통해서만 호출될 수 있다. 이는 뷰 파일에 대한 보안을 강화하고, 명확하게 컨트롤러를 거쳐 뷰로 연결되도록 하기 위한 설계이다.
redirect vs forward
- 리다이렉트: 클라이언트가 서버의 응답을 받고 새로운 요청을 보내는 방식으로, 클라이언트가 URL의 변화를 인식할 수 있다.
- 포워드: 서버 내부에서 이루어지는 요청 이동으로, 클라이언트는 이동 사실을 인지하지 못하며 URL 변경도 없다.
회원 등록 폼 - 뷰
main/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>
new-form.jsp는 회원 정보를 입력받는 폼을 제공한다. 폼의 action 경로는 상대경로를 사용하여, 현재 경로를 기준으로 폼을 전송할 수 있도록 한다. 이는 폼 전송 시 save로 호출되도록 설정한다.
회원 저장
회원 저장 - 컨트롤러
MvcMemberSaveServlet
package hello.servlet.web.servletmvc;
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);
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
MvcMemberSaveServlet은 회원 정보를 받아 MemberRepository에 저장하고, 저장된 정보를 모델(request 객체)에 담아 뷰로 전달한다. 이때, request.setAttribute()로 member 객체를 저장하고, dispatcher.forward()로 결과 JSP 페이지를 호출한다.
회원 저장 - 뷰
main/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>
save-result.jsp는 저장된 회원 정보를 화면에 표시한다. JSP에서 ${} 문법을 사용하여 request의 attribute 데이터를 쉽게 조회할 수 있다. 이로 인해, 뷰가 데이터를 출력하는 것이 간결해진다.
회원 목록 조회
회원 목록 조회 - 컨트롤러
MvcMemberListServlet
package hello.servlet.web.servletmvc;
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 {
System.out.println("MvcMemberListServlet.service");
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);
}
}
MvcMemberListServlet은 저장된 모든 회원 목록을 조회하고, 이를 request.setAttribute()로 모델에 저장하여 JSP로 전달한다. List<Member> 데이터를 모델에 보관하여 뷰가 이를 사용해 화면에 표시할 수 있도록 한다.
회원 목록 조회 - 뷰
main/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>
<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>
members.jsp는 c:forEach 태그를 사용하여 members 리스트의 데이터를 순회하면서 화면에 출력한다. taglib 기능을 통해 코드가 간결해지고, 반복 처리와 같은 뷰 렌더링 작업이 용이해진다.
실행
저장된 결과 목록을 확인할 수 있다.
MVC 패턴의 장점
MVC 패턴을 통해 컨트롤러와 뷰의 역할을 분리하여, 비즈니스 로직과 뷰 로직을 독립적으로 관리할 수 있게 된다. 덕분에 화면 변경 시 뷰 로직만 수정하면 되고, 비즈니스 로직 변경 시 컨트롤러 로직만 수정하면 되므로 유지보수가 용이해진다.
MVC 패턴 - 한계
MVC 패턴 덕분에 컨트롤러와 뷰의 역할을 명확히 구분할 수 있었다. 특히 뷰는 화면 렌더링에만 집중하게 되어 코드가 깔끔하고 직관적이며, 모델에서 필요한 데이터를 가져와 화면을 구성하는 작업만 하면 된다. 하지만 컨트롤러에는 중복된 코드와 필요하지 않은 코드가 존재하여 몇 가지 한계점이 드러난다.
MVC 컨트롤러의 단점
1. 포워드 중복
- 컨트롤러에서 뷰로 이동하기 위해 항상 forward 코드를 호출해야 한다. 이를 메서드로 공통화하더라도, 해당 메서드를 컨트롤러마다 직접 호출해야 한다는 점에서 중복이 발생한다.
예시 코드
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath); dispatcher.forward(request, response);
2. ViewPath에 중복
- 뷰 경로 설정 시 반복적으로 prefix와 suffix가 사용된다.
- 예: String viewPath = "/WEB-INF/views/new-form.jsp";
- 만약 뷰를 JSP에서 Thymeleaf와 같은 다른 템플릿 엔진으로 변경해야 할 경우, 모든 경로를 수정해야 한다.
- 일반적으로 사용되는 구조는 다음과 같다:
- prefix: /WEB-INF/views/
- suffix: .jsp
3. 사용하지 않는 코드
- 컨트롤러 메서드에는 항상 HttpServletRequest와 HttpServletResponse가 필요하지만, 실제로 모든 코드에서 이를 사용하는 것은 아니다. 특히 response 객체는 필요하지 않은 경우가 많다.
- 이러한 HttpServletRequest와 HttpServletResponse 객체는 테스트 코드 작성 시에도 불편함을 유발한다.
4. 공통 처리의 어려움
- 기능이 복잡해질수록 컨트롤러마다 공통으로 처리해야 할 작업이 많아진다. 이를 메서드로 분리하더라도, 모든 컨트롤러에서 해당 메서드를 항상 호출해야 하며, 실수로 호출을 빠뜨리면 문제가 발생할 수 있다. 호출 자체도 중복을 유발한다.
- 공통 처리가 어렵다는 점이 큰 문제이며, 이를 해결하기 위해서는 컨트롤러 호출 전에 공통 기능을 처리할 수 있는 구조가 필요하다. 이러한 구조는 소위 수문장 역할을 담당한다.
프론트 컨트롤러(Front Controller) 패턴
위 문제를 해결하기 위해 프론트 컨트롤러(Front Controller) 패턴을 도입할 수 있다. 이 패턴은 모든 요청을 단일 진입점에서 처리하여 공통 기능을 일관되게 수행하고, 필요한 컨트롤러로 분배하는 구조다. 스프링 MVC의 핵심 또한 이 프론트 컨트롤러 패턴에 있으며, 이를 통해 MVC 패턴의 단점을 효과적으로 해결할 수 있다.
'Spring MVC' 카테고리의 다른 글
[MVC] MVC 프레임워크 만들기(2) (1) | 2024.12.08 |
---|---|
[MVC] MVC 프레임워크 만들기(1) (0) | 2024.11.13 |
[MVC] 서블릿, JSP, MVC 패턴(3) (0) | 2024.10.29 |
[MVC] 서블릿, JSP, MVC 패턴(2) (0) | 2024.10.29 |
[MVC] 서블릿, JSP, MVC 패턴(1) (0) | 2024.10.29 |