1. MVC 패턴 개념
MVC 패턴을 적용한다. 서블릿이나 JSP로 작성하던 방식들은 HTML 코드와 자바 코드가 혼재되어 유지보수에 좋지 않은 단점이 있었다. 이 통합된 코드를 컨트롤러(Controller)와 뷰(View)로 분리하는 것이 MVC 패턴의 개념이다. 또 다른 요소인 Model 까지의 기본 개념은 다음과 같다.
- Controller : HTTP 요청을 받고, 비즈니스 로직을 실행하는 부분이다. 비즈니스 로직을 호출해서 처리 결과를 Model에 담고, View 로직을 실행한다. 비즈니스 로직은 Service에 있다. Controller에서는 Service 객체를 호출하는 것이다. 그리고 Service 에서 Database에 접근하는게 일반적이다.
- View : Model에 담긴 데이터를 참고하여 페이지에 표시할 View 정보를 렌더링한다. 그리고 HTTP 응답으로 송출한다.
- Model : Controller에서 처리된 데이터가 담기고, 이것을 View에 전달하는 역할을 한다. Model 부분이 있기 때문에 View에서는 Controller에서의 로직을 모르더라도 페이지(화면)를 그려낼 수 있게 된다.
2. MVC 패턴 적용
HTTP 요청에 대해서 request.setAttribute로 요청에 대한 정보를 저장할 수 있는데, 이렇게 요청 정보를 저장할 수 있는 공간을 Model처럼 사용할 수 있다.
2-1. 입력 페이지 : Controller 작성 - MvcMemberFormServlet
클라이언트 요청이 왔을 때, 요청을 처리하고 view로 넘겨주어서 기존에 하던 방식대로 사용자가 정보를 입력할 수 있는 웹 페이지를 그려주어야 한다. 그런데 페이지를 그리는 역할은 view로 넘겨주기로 했으므로, controller에서는 일단 request만 처리하여 view로 넘겨주면 된다.
여기서는 request.getRequestDispatcher()를 통해서 실제 view인 jsp 파일로 이동할 수 있는 dispatcher를 생성하고, 여기에 request, response를 파라미터로해서 dispatcher.forward()를 통해 view로 이동하도록 한다. dispatcher.forward()는 다른 서블릿이나 jsp로 이동할 수 있고, 서버 내부에서 다시 경로를 호출하는 방식으로 작동한다.
MvcMemberFormServlet
1
2
3
4
5
6
7
8
9
10
|
@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);
}
}
|
cs |
WEB_INF
이번 jsp 파일은 WEB-INF 디렉토리 내부에 작성한다. 원래는 jsp 파일을 URL 상에 입력하여 localhost:8080/jsp/members/new-form 으로 해당 페이지에 접근할 수 있었다. 그러나 WEB-INF 디렉토리에 들어있는 페이지는 URL을 통한 직접적인 접근이 제한되어 있다. 이 페이지들은 반드시 controller를 거쳐서만 접근이 가능하다. WAS에서 정해놓은 규칙이다. (WEB-INF, 대문자로 작성해야한다.)
redirect vs forward
dispatcher.forward() 방식으로 페이지로 이동하면, 서버 내부에서 경로를 다시 호출하는 방식으로 작동한다. 그러나 나중에 다룰 redirect 방식으로 처리하면, 페이지 이동 전 클라이언트로 응답이 갔다가, 클라이언트에서 새로운 페이지로 다시 요청을 하는 방식을 적용하게 된다.
2-2. 입력 페이지 : View 작성 - new-form.jsp
컨트롤러에서 작성한 경로대로 view용 jsp 파일을 작성한다. form의 action 속성에서 "save"만 작성해주었는데, 이렇게 해주면 상대경로로 지정된다. 다시말해, 이 페이지의 URL은 localhost:8080/servlet-mvc/members/new-form 이고, form 양식을 제출하면 localhost:8080/servlet-mvc/members/save 로 이동한다는 뜻이다.(상위경로/save)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<%@ 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>
|
cs |
2-3. 저장 페이지 : Controller 작성 - MvcMemberSaveServlet
이제 new-form으로 forward시 회원 가입을 하고, 결과화면을 보여주는 SaveServlet controller를 작성한다.
요청정보를 보관할 수 있는 Model 객체를 이용한다. request.setAttribute(파라미터 이름, 파라미터)를 통해서 request내에 Model 객체로 정보를 저장하고, 이것을 save-result.jsp 페이지로 forward 해주게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@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);
}
}
|
cs |
2-4. 저장 페이지 : View 작성 - save-result.jsp
request에 담긴 정보들을 화면에 나타내주면 된다. 원래대로라면 10~12번 줄에 해당하는 request.getAttribute(속성이름) 구문을 통해서 정보를 가져와서 표현해주어야 하지만, 아래 13~15번 줄 코드와 같이 ${ ... } 표현식을 사용해주면 request.getAttribute(속성 이름) 대신 바로 속성 이름으로 조회할 수 있게 된다. 이런 방식을 프로퍼티 접근법이라고 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<%@ page import="hello.servlet.domain.member.Member" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
성공
<ul>
<%-- <li>id=<%=((Member)request.getAttribute("member")).getId()%></li>--%>
<%-- <li>username=<%=((Member)request.getAttribute("member")).getUsername()%></li>--%>
<%-- <li>age=<%=((Member)request.getAttribute("member")).getAge()%></li>--%>
<li>id=${member.id}</li>
<li>username=${member.username}</li>
<li>age=${member.age}</li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>
|
cs |
2-5. 조회 페이지 : Controller 작성 - MvcMemberServletList
구조는 다른 controller들과 같다. /servlet-mvc/members url로 요청이 오면, member 정보를 찾는 로직을 실행하고 이 정보를 setAttribute를 통해서 Model 객체로 request에 담는다. 이후 RequestDispatcher를 통해 members.jsp 파일인 view 쪽으로 이동한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@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);
}
}
|
cs |
2-6 조회 페이지 : View 작성 - members.jsp
JSP에서 제공하는 문법을 사용하여 for문을 작성해준다. taglib를 import 하고, c:forEach 구문을 사용한다. JSP 구문은 필요하다면 따로 공부하되, 이후에는 thymeleaf로 진행할 것이기 때문에 굳이 문법을 공부할 필요는 없다.
이전과 같이 Model 객체로 전달받은 member 속성을 바로 변수처럼 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
<%@ 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>
|
cs |
3. MVC 패턴 한계
Controller에서 중복된 코드가 보인다. forward, viewPath의 앞단 경로 부분, HttpServletResponse는 사용하지 않는 등 중복 및 불필요한 코드들을 정리하면 좋을 것이다. 이러한 중복되는 내용들을 공통적으로 처리해버리면, 중복 작업을 하지 않아도 될 뿐만 아니라 유지보수에도 좋을 것이다. Controller의 공통화된 부분을 실제 Controller 전에 먼저 처리하는 부분을 Front Controller라고 한다. 다음 글에서는 Front Controller에 대해서 알아본다.
참조
1. 인프런_스프링 MVC 1편 - 백엔드 웹개발 핵심 기술_김영한 님 강의
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-1
'Programming-[Backend] > Spring' 카테고리의 다른 글
[스프링 웹MVC] 8. 프론트 컨트롤러(Front Controller) - 2. 컨트롤러 개선, MVC 패턴 요약 (3) | 2021.08.01 |
---|---|
[스프링 웹MVC] 7. 프론트 컨트롤러(Front Controller) - 1. 컨트롤러 수정 (0) | 2021.07.31 |
[스프링 웹MVC] 5. 서블릿, JSP 로 회원관리 웹앱 만들기 (0) | 2021.07.24 |
[스프링 웹MVC] 4. HTTP 응답 정보 : 서블릿 - HttpServletResponse (0) | 2021.07.19 |
[스프링 웹MVC] 3. HTTP 요청 정보 : 서블릿 - HttpServletRequest (0) | 2021.07.18 |