스프링 MVC 밑바닥부터 만들어보기3 - MVC패턴의 시작
서블릿과 JSP를 단독으로 사용해서 코드를 작성하게 되면 하나의 파일이 너무 많은 일을 수행하게 되버린다. 이렇게 되면 유지보수도 어려워질 뿐더러 작업의 프로그램의 효율도 떨어질 수 있다. 이러한 문제를 극복하기 위해서 MVC패턴이 등장하게 되었다.
MVC패턴은 Model, View, Controller로 역할을 구분해서 사용하는 방식이다.
- Model : View에 출력할 데이터를 담아두고 있는다.
- View : html과 같이 화면을 담당하는 코드이다. 모델에 담겨있는 데이터를 활용할 수 있다.
- Controller : HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다. View에 전달할 데이터를 모델에 담는다.
Controller는 요청을 처리해 주는 역할이 주된 역할이기 때문에 그 안에서 비즈니스 로직을 처리하기에는 너무 많은 일을 하게 될 수 있다. 그렇기 때문에 비즈니스 로직은 Service라는 클래스를 추가하여 구분해서 처리할 수도 있다.
MVC패턴의 전체적인 흐름을 살펴보면
이런 흐름으로 진행된다. 그림만으로는 이해가 힘들수도 있겠지만 그림을 참고하면서 코드를 작성하다보면 이해가 될 것이다.
이제 본격적으로 jsp코드를 MVC패턴으로 리팩토링 해보자.
- 서블릿 - Controller
- jsp - View
- HttpServletRequest - Model
이렇게 역할을 분담해서 만들어보도록 하자.
먼저 컨트롤러 역할을 해줄 서블릿을 만들어야 한다.
@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 path = "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(path);
dispatcher.forward(request, response);
}
}
- 먼저 호출할 jsp의 위치를 문자열로 담는다.
- getRequestDispatcher()를 이용해서 path를 넣어주면 해당 위치로 이동할 수 있는 RequestDispatcher를 반환한다.
- 마지막으로 forward를 이용해서 해당페이지로 이동시킨다.
forward는 특정 위치를 호출하는 기능이다. 특정 위치를 호출하는 기능은 Redirect와 forward 2가지가 있다.
Redirect와 forward의 차이점 자세히 알아보기
기본적으로 클라이언트가 서버에 요청을 보내면 요청이 흘러가는 흐름이
WAS -> 서블릿 -> View -> 서블릿 -> WAS의 흐름이다.
Redirect를 호출하면 다시 클라이언트가 처음부터 호출한 것 처럼 Redirect에 적힌 주소로 호출한다. 이렇게 호출되면 웹에서의 URL도 새로 호출한 URL로 바뀌게 되면서 완전히 새로운 호출이 된다. 실제로 Redirect는 302상태코드를 응답메세지에 넣어서 반환하기 때문에 클라이언트가 이 상태코드를 보고 다시 요청하는 방식이다.
forward는 클라이언트까지 돌아가지않고 서버 내부에서 재호출하는 방식이다. forward를 이용해서 재호출을 해도 서버 내부에서 지정한 위치의 파일을 찾아서 띄어주기 때문에 URL의 주소가 처음 호출한 주소 그대로 남아있게 된다.
이번에는 경로가 WEB-INF로 들어가게 되었다. 이전에 jsp만 이용해서 만들었을 때는 jsp의 경로를 직접 입력해서 접근했었다. 하지만 WEB-INF를 이용하면 경로를 이용한 직접적인 접근은 못하도록 막히게 된다. 반드시 서블릿(컨트롤러)에서 forward를 이용한 접근만 허용하게 된다. 이것은 WAS의 룰이다.
이제 path로 지정한 위치에 새로운 jsp파일을 만들자. 정확한 경로는 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>
이번에는 form태그 안에 action을 상대경로로 지정했다. /save로 지정하면 절대경로로 localhost:8080/save가 되는거지만 상대경로이기 때문에 디렉토리까지의 경로는 유지되고 jsp파일부분의 경로만 변경된다. 위의 코드처럼 요청하면 http://localhost:8080/servlet-mvc/members/save가 요청된다.
다음은 저장요청을 처리하는 서블릿을 만들어보자.
@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);
request.setAttribute("member", member);
String path = "/WEB-INF/views/save.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(path);
dispatcher.forward(request, response);
}
}
- 먼저 서블릿을 만들어줬다. 로직은 앞에서 했던 save.jsp내부에 작성한 비즈니스 로직과 동일하다.
- request.setAttribute() 를 사용했는데 HttpServletReqeust내부에는 시작된 요청이 끝나기 전까지 데이터를 저장할 수 있는 공간이 있다. 그 공간을 활용하는 방법이다.
- key값과 value를 넣어줌으로서 model로서 활용할 수 있다.
이제 save.jsp를 만들어보자.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
성공
<ul>
<li>id=${member.id}</li> // (member)request.getParameter("member").getId()
<li>username=${member.username}</li>
<li>age=${member.age}</li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>
jsp에서는 request를 사용할 수 있다고 했다. request를 이용해서 모델에 담은 데이터를 사용할 수도 있지만 코드가 너무 복잡하게 된다. 이럴 때는 jstl을 사용하여 처리할 수 있다. ${member.id}는 프로퍼티 접근법을 이용해서 손쉽게 모델에 있는 값을 불러올 수 있다.
이제 마지막으로 회원목록을 가져오는 서블릿을 만들어보자.
@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);
}
}
앞선 저장요청을 처리하는 서블릿과 비슷하다.
<%@ 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>
여기서는 forEach를 사용하였다. 자바의 for문과 같은 기능이라고 생각하면된다. items="${members}"의 데이터를 하나씩 item으로 받아서 총 개수만큼 html태그와 값을 출력하면서 만들어준다.
이렇게 해서 MVC패턴을 이용한 리팩토링을 마무리했다. 그런데 Spring을 사용해본사람들은 느끼겠지만 아직도 Spring에서 사용하던 편리함과는 거리가 멀다. 우리가 직접 만든 이 MVC패턴은 무언가 문제가 많아 보인다.
- HttpServletRequest, HttpServletResponse를 반드시 사용하는 것이 아닌데 항상 서블릿에 함께 넘어온다.
- view로 넘기는 코드가 모든 서블릿에서 반복되어진다.
이러한 문제들이 결국은 공통처리가 어렵다는 문제로 다가선다. 이 문제를 해결하기 위해서 요청이 들어왔을 때 가장 앞에서 요청을 받는 수문장역할의 컨트롤러를 만들어서 해결할 수 있다. 이 컨트롤러를 '프론트 컨트롤러'라고 부를 것이다. SpringMVC도 프론트 컨트롤러 패턴으로 짜여져 있다.
다음부터는 더 수월하게 사용할 수 있도록 버전업을 해가면서 발전시켜보도록 하겠다.
모든 내용은 인프런의 김영한님의 강의를 참고하여 작성되었습니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1/dashboard
사용된 전체 코드는 깃허브에서 보실 수 있습니다.
https://github.com/ssung0810/spring_study/tree/main/spring_mvc_study
'Spring > MVC' 카테고리의 다른 글
스프링 MVC 밑바닥부터 만들어보기5 - 서블릿 종속성 제거, Model분리 (0) | 2022.04.20 |
---|---|
스프링 MVC 밑바닥부터 만들어보기4 - 프론트 컨트롤러와 view의 분리 (0) | 2022.04.14 |
스프링 MVC 밑바닥부터 만들어보기2 - 서블릿과 JSP (0) | 2022.04.11 |
스프링 MVC 밑바닥부터 만들어보기1 - 서블릿 탐구하기 (0) | 2022.04.11 |
스프링 MVC 여러 종류의 요청매핑 사용하는 방법 (0) | 2022.02.22 |
댓글
이 글 공유하기
다른 글
-
스프링 MVC 밑바닥부터 만들어보기5 - 서블릿 종속성 제거, Model분리
스프링 MVC 밑바닥부터 만들어보기5 - 서블릿 종속성 제거, Model분리
2022.04.20 -
스프링 MVC 밑바닥부터 만들어보기4 - 프론트 컨트롤러와 view의 분리
스프링 MVC 밑바닥부터 만들어보기4 - 프론트 컨트롤러와 view의 분리
2022.04.14 -
스프링 MVC 밑바닥부터 만들어보기2 - 서블릿과 JSP
스프링 MVC 밑바닥부터 만들어보기2 - 서블릿과 JSP
2022.04.11 -
스프링 MVC 밑바닥부터 만들어보기1 - 서블릿 탐구하기
스프링 MVC 밑바닥부터 만들어보기1 - 서블릿 탐구하기
2022.04.11