서블릿을 컨트롤러로 사용하고, JSP를 뷰로 사용해서 MVC 패턴을 적용해봅시다.
Model은 HttpServletRequest 객체를 사용합니다. request는 내부에 데이터 저장소를 가지고 있는데, request.setAttribute() , request.getAttribute() 를 사용하면 데이터를 보관하고, 조회할 수 있습니다.
그러니까 HttpServletRequest 객체를 Model 처럼 쓰는 것입니다. 컨트롤러에서는 setAttribute 로 값을 담고 뷰에서는 getAttribute 로 값을 꺼내는 것입니다.
→ 참고
hello.servlet.web.servletmvc 패키지를 만들고 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"; //...1
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath); //...2
dispatcher.forward(request, response); //...3
}
}
처음에는 멤버 폼을 보여주고 싶습니다. MVC 패턴은 항상 컨트롤러를 거쳐서 뷰로 들어갑니다. 그래서 컨트롤러에 요청이 다 들어와야 합니다.
이 때 MvcMemberFormServlet는 단순하게 JSP로 가주면 됩니다.
dispatcher.forward()를 호출하면 진짜 서블릿에서 JSP를 호출할 수 있습니다.
고객의 요청이 오면 service()가 호출이 되고 forward가 JSP 경로를 다시 호출해줍니다. 서버끼리 내부에서 호출해줍니다.
여기서 서버 내부에서 다시 호출이 발생한다는게 정말 중요합니다.
다시 클라이언트에 갔다오는 리다이렉트가 아닙니다. 컨트롤러에서 요청을 받고 뷰까지 렌더링이 되는데 URL이 변경되지 않습니다.
클라이언트에서 서버로 호출하고 서버 안에서 서블릿이 호출되고 JSP를 호출하고 JSP에서 응답을 내려주는 것입니다. 즉, forward는 마치 메서드를 호출하듯이 호출이 일어납니다.
<aside> ✏️ [참고] redirect vs forward
리다이렉트는 실제 클라이언트(웹 브라우저)에 응답이 나갔다가, 클라이언트가 redirect 경로로 다시 요청합니다. 따라서 클라이언트가 인지할 수 있고, URL 경로도 실제로 변경됩니다.
그래서 리다이렉트는 2번 호출이 되는 것입니다.
반면에 포워드는 서버 내부에서 일어나는 호출이기 때문에 클라이언트가 전혀 인지하지 못합니다.
그래서 포워드는 1번 호출이 됩니다.
</aside>
<aside> ❗ /WEB-INF
이전에는 그냥 .jsp를 붙이면 jsp파일을 호출할 수 있었습니다. 하지만 외부에서 직접적으로 부르지 않고 컨트롤러를 항상 거쳐서 불러오고 싶습니다.
그 때는 /WEB-INF 경로에 넣어주면 됩니다. 이것은 WAS서버의 룰입니다.
/WEB-INF 경로안에 JSP가 있으면 외부에서 직접 JSP를 호출할 수 없습니다.
→ 참고
</aside>