JSP & MVC 패턴 적용
1. 서블릿 단독 사용 문제점
회원 관리 웹 애플리케이션을 만들기 위해 관련된 몇가지 객체를 만들었다고 하자.
· Member(username, age) : 회원 이름과 나이를 저장하는 객체
· MemberRepository() : 회원 정보가 저장되는 객체 (싱글톤)
이제 서블릿으로 회원 등록 폼을 만든다고 하자.
[회원 등록 폼 - 서블릿]
@WebServlet(name = "memberFormServlet", urlPatterns = "/servlet/members/new-form")
public class MemberFormServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
PrintWriter w = resp.getWriter();
w.write("<!DOCTYPE html>\n" +
"<html>\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" + " <title>Title</title>\n" +
"</head>\n" +
"<body>\n" +
"<form action=\"/servlet/members/save\" method=\"post\">\n" +
" username: <input type=\"text\" name=\"username\" />\n" +
" age: <input type=\"text\" name=\"age\" />\n" +
" <button type=\"submit\">전송</button>\n" +
"</form>\n" +
"</body>\n" +
"</html>\n");
}
}
회원을 등록하기 위한 페이지를 띄우기위해 서블릿으로 메시지 바디에 HTML문법을 그대로 넣었다.
[회원 저장 폼 - 서블릿]
@WebServlet(name = "memberSaveServlet", urlPatterns = "/servlet/members/save")
public class MemberSaveServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
System.out.println("MemberSaverServlet.service");
String username = req.getParameter("username");
int age = Integer.parseInt(req.getParameter("age"));
Member member = new Member(username, age);
memberRepository.save(member);
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
PrintWriter w = resp.getWriter();
w.write("<html>\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
"</head>\n" +
"<body>\n" +
"성공\n" +
"<ul>\n" +
" <li>id="+member.getId()+"</li>\n" +
" <li>username="+member.getUsername()+"</li>\n" +
" <li>age="+member.getAge()+"</li>\n" +
"</ul>\n" +
"<a href=\"/index.html\">메인</a>\n" +
"</body>\n" +
"</html>");
}
}
회원 저장 폼같은 경우는 HTML문법에 자바 코드의 변수를 넣는다.
위의 예제들과 같이 서블릿으로 동적인 HTML을 마음껏 만들 수 있다. 그러나 코드에서 보듯이 이러한 작업은 매우 복잡하고 비효율 적이다.
차라리 HTML 문서에 동적으로 변경해야 하는 부분만 자바코드를 넣는것이 더 편리하고 보기 좋을 것이다.
이러한 이유로 나온것이 템플릿 엔진이다. 템플릿 엔진을 사용하면 HTML 문서에서 필요한 곳만 코드를 적용해 동적으로 변경할 수 있다.
2. JSP 사용
JSP로 위의 회원 등록 폼을 변경해보자.
[회원 등록 폼 - JSP]
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<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>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>는 해당 문서가 JSP문서라는 뜻이다.
JSP 문서는 반드시 이렇게 시작해야 한다.
[회원 등록 폼 - JSP]
<%@ page import="hellow.servlet.domain.Member" %>
<%@ page import="hellow.servlet.domain.MemberRepository" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
MemberRepository memberRepository = MemberRepository.getInstance();
System.out.println("MemberSaverServlet.service");
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
memberRepository.save(member);
%>
<html>
<head>
<title>Title</title>
</head>
<body>
성공
<ul>
<li>id=<%=member.getId()%></li>
<li>username=<%=member.getUsername()%></li>
<li>age=<%=member.getAge()%></li>
</ul>
<a href="/index.html">메인</a>
</body>
</html>
<%@ page import= ~ %>
- 자바의 import문과 동일하다.
<% ~~ %>
- 해당 부분에는 자바 코드를 입력할 수 있고 적용된다.
<%= ~~ %>
- 자바 코드를 출력할 수 있다.
위 예제들 처럼 JSP를 사용해서, 뷰를 생성하는 HTML 작업을 서블릿에 종속되지 않게 했고 동적으로 변경이 필요한 부분에만 자바 코드를 적용하였다.
그러나 이와 같은 방식도 몇가지 문제점이 존재한다.
JSP로 만든 회원 저장 폼을 보면 코드 상위 절반은 회원을 저장하기 위한 비즈니스로직이고, 나머지 하위 절반만 HTML로 이루어진 뷰영역이다.
한마디로 이번에는 뷰를 담당해야하는 부분에 너무 많은 비즈니스 로직이 들어가있다.
위와같은 문제를 해결하고자 비즈니스 로직과 뷰를 담당하는 작업을 나누기위해 MVC 패턴이라는 개념이 생겨났다.
3. MVC 패턴
비즈니스 로직과 뷰 렌더링 작업은 아래와 같은 이유로 분리되어야 한다.
· 변경의 라이플 사이클
비즈니스 로직과 뷰 렌더링은 근본적으로 변경 라이플 사이클이 다르다. 이 때문에 이 두 작업을 하나의 코드로 관리하는 것은 유지보수하기 좋지 않다.
· 기능 특화
비즈니스 로직은 로직 그 자체만을, 뷰 부분은 화면을 렌더링하는 부분에만 집중해 최적화된 코드를 작성해야 효율이 좋다.
MVC(Model View Controller) 패턴은 지금까지 하나의 서블릿이나 JSP로 처리하던 것을 컨트롤러와 뷰라는 영역으로 역할을 서로 나눈 것을 말한다.
· 컨트롤러 : HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실해한다. 그리고 뷰에 전달할 결과 데이터를 조회해서 모델에 담는다.
· 모델 : 뷰에서 화면을 렌더링하기 위해 필요한 데이터를 담아두는 곳
· 뷰 : 모델에 담겨있는 데이터를 사용해서 화면을 렌더링하는 일에 맡는다.
[MVC 패턴 이전 방식]
MVC 패턴 이전에는 서블릿이나 JSP같은 하나의 영역에서 비즈니스 로직과 뷰 로직을 모두 한번에 처리 하였다.
[MVC 패턴 적용]
MVC 패턴은 컨트롤러를 사용해 비즈니스 로직을 실행시키고 뷰에 필요한 모델을 반환 받는다.
그 뒤, 모델을 뷰에 넘겨서 뷰 로직이 화면을 렌더링하게 한다.
이처럼 MVC 패턴은 컨트롤러를 사용해 비즈니스 로직과 뷰 로직을 분리한다.
■ MVC 패턴 적용
앞의 회원 관리 예제에 MVC 패턴을 적용하여 보자.
[회원 등록 폼 - 컨트롤러]
@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);
}
}
dispatcher.forward() : 다른 서블릿이나 JSP로 이동할 수 있는 기능이다. 서버 내부에서 다시 호출이 발생한다(리다이렉트 아님).
회원 등록 컨트롤러에서 실행되는 비즈니스 로직은 딱히 없고 dispatcher를 통해 뷰로 이동한다.
[회원 등록 폼 - 뷰]
<%@ 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>
이번에는 상대 경로를 사용하였다. 상대 경로 사용시 현재 URL이 속한 계층 경로 +상대 경로가 호출된다.
· 현재 계층 경로 : /servlet-mvc/members/
· 결과 : /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 {
System.out.println("MemberSaverServlet.service");
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 viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
HttpServletRequest를 Model로 사용한다. request가 제공하는 setAttribute()를 사용하면 request 객체에 데이터를 보관해서 뷰에 전달할 수 있다.
[회원 저장 - 뷰]
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</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>
<%= request.getAtrribute("member")%>로 모델(request)에 저장한 member 객체를 꺼낼 수 있지만, 코드가 너무 길어진다.
JSP는 ${}문법을 통해 request의 attribute에 담긴 데이터를 편리하게 조회할 수 있다.
이처럼 MVC 패턴을 적용한 덕분에 비즈니스 로직과 뷰 로직을 명확하게 구분할 수 있다.
특히 뷰 부분은 단순히 모델에서 데이터를 꺼내고 화면을 만들면 되기 때무에 코드가 깔끔해졌다.
그러나 컨트롤러 부분에는 중복된 코드가 많고 필요하지 않은 코드들도 몇 존재한다.
■ MVC 컨트롤러의 단점
□ 포워드 중복
· View로 이동하는 코드가 항상 중복 호출되어야 한다.
[dispatcher 중복]
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
□ ViewPath 중복
[ViewPath - 중복]
String viewPath = "/WEB-INF/views/new-form.jsp";
/WEB-INF/views/ 부분이 계속해서 중복된다.
그리고 만약 jsp가 아닌 다른 템플릿을 사용할 경우 .jsp 부분 때문에 전체 코드를 변경해야 할 수도 있다.
□ 사용하지 않는 코드
· HttpServletRequest와 HttpServletResponse를 사용할 때도 있고 안할 때도 있다.
위의 문제점들을 종합해 볼 때, 결국 공통적으로 처리해야 하는 부분은 많은데 각 컨트롤러가 이 역할을 하나하나 다 수행해야 하기 때문에 비효율이 발생한다는것을 알 수 있다.
이 문제를 해결하기 위해 개별 컨트롤러를 호출하기 전에 먼저 공통기능을 처리하는 프론트 컨트롤러(Front Controller) 패턴을 도입한다.
'Spring > 스프링 MVC 기본' 카테고리의 다른 글
#6 스프링 MVC 기본 기능 (0) | 2021.07.07 |
---|---|
#5 스프링 MVC 구조 이해 (0) | 2021.06.30 |
#4 MVC 프레임워크 (0) | 2021.06.29 |
#2 서블릿 (0) | 2021.06.23 |
#1 스프링 웹 애플리케이션 이해 (0) | 2021.06.23 |
댓글