728x90
Servlet과 JSP로 MVC 패턴 구현하기
- servlet은 컨트롤러 역할을 맡았다.
- jsp는 뷰 역할을 맡았다.
🔍 회원 등록
💡 회원 등록 폼 - 컨트롤러
📌 MvcMemberFormServlet 클래스
package hello.servlet.web.servletmvc;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.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);
// 서블릿에서 jsp를 호출할 수 있다. 서버 내부에서 서버끼리 호출
dispatcher.forward(request, response);
}
}
- /WEB-INF 경로안에 있는 JSP 파일은 외부에서 직접 JSP를 호출할 수 없다.
- 즉, 항상 컨트롤러를 통해서만 JSP를 호출할 수 있다.
- getRequestDispatcher() 메서드의 인자 값으로 이동할 페이지의 경로를 지정해준다.
🧷 RequestDispatcher
- RequestDispatcher는 클라이언트로부터 최초에 들어온 요청을 JSP / Servlet 내에서 원하는 자원으로 요청을 보내는 역할을 수행하거나 특정 자원의 처리를 요청하고 처리 결과를 얻어오는 기능을 수행하는 클래스다.
- 즉, /a.jsp 로 들어온 요청을 /a.jsp 내에서 RequestDispatcher를 사용하여 b.jsp로 요청을 보낼 수 있다.
- 또는 /a.jsp 에서 /b.jsp 로 처리를 요청하고 /b.jsp 에서 처리한 결과 내용을 /a.jsp의 결과에 포함시킬 수 있다.
- 요청을 보내는 방법은 RequestDispatcher 클래스의 메서드인 forward() 와 include()에 의한 두 가지 방법이 있다.
- RequestDispatcher는 현재 처리중인 서블릿이 속해있는 웹 어플리케이션 범위 내에서만 요청을 제어할 수 있다.
✔ RequestDispatcher#forward() vs HttpServletReponse#sendRedirect() 차이점
HttpServletReponse#sendRedirect()
- HttpServletResponse를 사용하면 sendRedirect() 메서드를 이용하여 지정한 경로로 제어를 이동시킬 수 있다.
- 그러나 sendRedirect()는 HTTP 리다이렉션을 이용하기 때문에 하나의 요청 범위 안에서 처리를 하는 것이 아니라 브라우저에게 Response 후 브라우저 측에서 지정받은 요청 경로로 다시 재요청하는 방식이기에 두 번의 HTTP 트랜잭션이 발생한다.
- 서버 측에서는 최초로 받은 요청 중에 처리한 내용을 리다이렉트 된 요청안에서 공유할 수 없다는 문제가 있다.
- 물론, 쿠키나 세션등을 이용해 특정 상태 유지를 공유할 수 있으나, 상황에 따라 제한적이다.
- 리다이렉트는 실제 클라이언트에 응답이 나갔다가 클라이언트가 redirect 경로로 다시 요청한다. 따라서 클라이언트가 인지할 수 있고, URL 경로도 실제로 변경된다.
- forward() 메서드는 페이지 전환에 대해 한 번의 요청일 때 사용한다.
- sendRedirect() 메서드는 페이지든 주소든 요청이 두 번 들어가야할 때 사용한다.
- ex) 글쓰기 요청이 끝난 후에 바로 글 목록 페이지가 떠야할 때
- 글쓰기가 끝난 후 목록 페이지로 띄어질 경우, 글 목록을 띄우기 위해 다시 DB에 접근 해야한다.
- select 문을 돌린 후 insert 된 게시 목록을 포함해 새롭게 목록이 갱신된 게시 목록을 가져와야 한다.
- 1번 째 요청 : insert 요청으로 게시물을 추가
- 2번 째 요청 : select 요청으로 추가된 게시글을 포함해 새로운 목록 갱신하라는 요청
- ex) 글쓰기 요청이 끝난 후에 바로 글 목록 페이지가 떠야할 때
✔ RequestDispatcher#forward()
- forward() 메서드는 대상으로 제어를 넘기는 역할을 한다.
- forward() 메서드를 통해 HttpServeltRequest 와 HttpServletResponse 객체를 같이 넘겨 주기 때문에 호출한 a.jsp 와 호출 당한 b.jsp에서 이 두 객체를 서로 공유한다.
- 브라우저에서 /a.jsp로 요청했을 때, /a.jsp에서 forward()를 실행하여 /b.jsp로 제어를 넘길 수 있다.
- 제어를 넘겨받은 /b.jsp는 처리 결과를 브라우저에게 출력한다.
- 브라우저 입장에서는 /a.jsp를 요청했지만 받은 결과는 /b.jsp의 결과다.
- 이 때 HTTP 리다이렉트 방식과는 달리 하나의 HTTP 요청(Request) 범위안에서 동작이 이뤄진다.
- forward() 메서드는 서버 내부에서 일어나는 호출이기 때문에 클라이언트가 전혀 인지하지 못한다.
✔ forward() 주의사항
- 원래라면 위 사진처럼 버퍼 처리 과정을 거쳐야 하나, forward()는 제어를 넘길 때, 출력 버퍼를 비우고 넘기기 때문에 a.jsp → b.jsp 로 호출시 a.jsp에서 어떤 내용을 버퍼에 출력했더라도 무시되며, 제어가 넘어간 b.jsp의 출력 내용만 브라우저에 전달된다
RequestDispatcher dispatcher = request.getRequestDispatcher("/b.jsp");
dispatcher.forward(request, response);
PrintWriter w = response.getWriter();
w.write("ok");
- 예를 들어서 /a.jsp에서 /b.jsp를 호출하고, forward() 메서드를 통해 제어를 넘겨주고 나서 /a.jsp 에서 w.write() 메서드를 통해 응답 메시지를 출력하려고 하지만 제어를 넘길 때 이미 출력 버퍼가 다 비워져서 "ok" 메시지도 같이 비워졌다.
💡 회원 등록 폼 - 뷰
📌 new-form.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/jsp/members/save.jsp" method="post">
username: <input type="text" name="username" />
age: <input type="text" name="age" />
<button type="submit">전송</button>
</form>
</body>
</html>
🔍 회원 저장
💡 회원 저장 - 컨트롤러
📌 MvcMemberSaveServlet 클래스
package hello.servlet.web.servletmvc;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.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 객체 안에 임시 저장소가 Map 형태(key:value)로 되어있다.)
request.setAttribute("member", member);
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
- HttpServletRequest를 Model로 사용한다.
- request 객체에는 데이터를 보관할 수 있는 임시 저장소 기능을 가지고 있다.
- 임시 저장소는 Map 형태처럼 key, value 처럼 형태로 되어있다.
- "member" 는 key 값, member는 value 값이다.
- request.setAttribute() 메서드를 통해 request 객체에 데이터를 보관해서 뷰에 전달할 수 있다.
- 뷰는 request.getAttribute() 메서드를 통해 데이터를 꺼내면 된다.
💡 회원 저장 - 뷰
📌 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>
- JSP는 ${} 문법을 제공한다. 이 문법을 통해 request의 attribute에 담긴 데이터를 편리하게 조회할 수 있다.
🔍 회원 목록 조회
💡 회원 목록 조회 - 컨트롤러
📌 MvcMemberListServlet 클래스
package hello.servlet.web.servletmvc;
import hello.servlet.domain.member.Member;
import hello.servlet.domain.member.MemberRepository;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@WebServlet(name = "mvcMemberListServlet", urlPatterns = "/servlet-mvc/members")
public class MvcMemberListServlet extends HttpServlet {
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);
}
}
- request 객체를 사용해서 List<Member> members를 모델에 보관했다.
💡 회원 목록 조회 - 뷰
📌 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>
MVC 패턴 - 한계
- Servlet을 컨트롤러 역할을 부여하고, JSP를 뷰로 역할을 부여함으로써, 코드가 깔끔하고 직관적이게 되었다.
- 하지만 컨트롤러에 중복이 많고 필요하지 않는 코드가 많이 보인다.
- forward() 메서드 중복
- ViewPath 중복
- response는 현재 코드에서 사용되지 않는다.
- HttpServletRequest, HttpServletRsponse를 사용하는 코드는 테스트 케이스를 작성하지 어렵다.
- 공통 처리가 어렵다.
💡 해결
- 공통 처리가 어렵다는 문제를 해결하려면 컨트롤러 호출 전에 먼저 공통 기능을 처리해야한다.
- 프론트 컨트롤러(Front Controller) 패턴을 도입하면 이 문제를 해결할 수 있다.
프론트 컨트롤러 패턴 도입 전
프론트 컨트롤러 패턴 도입 후
👀 참고자료
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
https://dololak.tistory.com/502
https://mainichibenkyo.tistory.com/188
728x90
'[ Spring ] > SpringMVC 1편' 카테고리의 다른 글
[Spring] MVC 구조 (0) | 2022.02.22 |
---|---|
[Spring] MVC 프레임 워크 만들기 (0) | 2022.02.20 |
[Spring] MVC 패턴 (Model - View - Controller) (0) | 2022.02.18 |
[Spring] JSP로 회원 관리 웹 애플리케이션 만들기 (0) | 2022.02.18 |
[Spring] 서블릿으로 회원 관리 웹 애플리케이션 만들기 (0) | 2022.02.17 |