서블릿
서블릿을 배웠으니 본격적으로 만들어 봅시다!
서블릿의 핵심은 가지고 있는 데이터를 바탕으로 자바 코드에 HTML을 입력하는 것입니다!
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
List<Item> items = itemRepository.findAll();
// 바디 입력이기 때문에 타입, 인코딩 설정
resp.setContentType("text/html");
resp.setCharacterEncoding("utf-8");
PrintWriter w = resp.getWriter();
// HTML 입력
w.write("<html>");
w.write("<head>");
w.write(" <meta charset=\"UTF-8\">");
w.write(" <title>Title</title>");
w.write("</head>");
w.write("<body>");
w.write("<a href=\"/index.html\">메인</a>");
w.write("<table>");
w.write(" <thead>");
w.write(" <th>id</th>");
w.write(" <th>username</th>");
w.write(" <th>age</th>");
w.write(" </thead>");
w.write(" <tbody>");
for (Item item : items) {
w.write(" <tr>");
w.write(" <td>" + item.getId() + "</td>");
w.write(" <td>" + item.getName() + "</td>");
w.write(" <td>" + item.getStock() + "</td>");
w.write(" </tr>");
}
w.write(" </tbody>");
w.write("</table>");
w.write("</body>");
w.write("</html>");
}
작성을 하자마자 드는 생각은....
물론 반복문을 보면서 객체를 효율적으로 입력할 수 있구나라는 좋은 점도 있긴 합니다만, 문제점이 많습니다.
- 일일이 입력하는 것 자체가 매우 비효율적이다.
- 컴파일러가 오타를 잡아내지 못한다.
이러한 방식보다는 동적으로 변해야 하는 부분만 HTML 문서에 넣는게 더 쉬울 것 같습니다.
이 생각으로 만들어진 것이 바로 템플릿 엔진이고 지금 작성해볼 JSP 역시 이 템플릿 엔진 중 하나입니다.
템플릿 엔진(JSP)
JSP, Thymeleaf, Freemarker, Velocity 등등 많은 템플릿 엔진이 있으나 여기서는 JSP를 다루겠습니다.
템플릿 엔진은 자바 코드에서 어떠한 작업을 하는 것 보다는, HTML 문서에 자바 코드를 넣는 작업을 실시합니다.
<%@ page import="java.util.List" %>
<%@ page import="hello.servlet.domain.item.ItemRepository" %>
<%@ page import="hello.servlet.domain.item.Item" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
ItemRepository itemRepository = ItemRepository.getInstance();
List<Item> items = itemRepository.findAll();
%>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="/index.html">메인</a>
<table>
<thead>
<th>id</th>
<th>상품명</th>
<th>재고</th>
</thead>
<tbody>
<%for (Member member : members) {%>
<tr>
<td><%=member.getId()%></td>
<td><%=member.getUsername()%></td>
<td><%=member.getAge()%></td>
</tr>
<%}%>
</tbody>
</table>
</body>
</html>
- 첫 번째로 우리가 서블릿에 @WebServlet 어노테이션을 입력해 서블릿이라고 명시했던 것처럼 JSP 역시 이 작업이 필요합니다. 이게 바로 <%@ page contentType="text/html;charset=UTF-8" language="java" %>입니다.
- 그다음 필요한 데이터들을 가져 올 필요가 있는 경우 import 역시 할 수 있습니다. '<%@ page import='
- 자바 코드 입력.
- <% 자바 코드 %>를 통해 자바 코드를 입력합니다.
- 자바 코드 출력
- <%= 자바 코드%>를 통해 자바 코드를 출력합니다.
MVC 패턴
템플릿 엔진에 대한 고민
JSP까지 만들어 봤는데. 그래도 몇 가지 문제가 있습니다. 가장 큰 문제 하나만 짚어 보자면 바로 캡슐화입니다.
캡슐화
JSP에는 요청 데이터들을 처리해주는 로직과, 이를 화면에 출력(렌더링)해주는 뷰 로직이 한 군데에 모두 들어가 있습니다. 이는 재사용성과 유지 보수 능력을 저해하는 문제를 만듭니다.
추가적으로 JSP 자체가 너무 길어지는 문제점도 있습니다. 만약 JSP가 몇만 줄이라면?? 수정할 엄두도 안 날 것입니다.
수정
캡슐화와 비슷한 문제입니다. 만약 뷰에 대한 내용을 고쳐야 할 때와 로직을 수정해야 할 때 모두 JSP를 수정해야 하기 때문에 상당히 불편한 일이 발생합니다.
그래서...
가장 필요한 작업은 데이터를 처리해주는 비즈니스 로직과, 뷰를 나누어야 합니다!
이게 바로 MVC의 패턴의 시작입니다.
MVC 패턴 Start
뷰
JSP파일을 비즈니스 로직과 뷰를 나누어 봅시다. 먼저 JSP의 로직(자바 코드) 부분을 모두 지워 일반적인 뷰의 기능만 하도록 만듭시다.
컨트롤러
그다음 로직 부분을 모두 컨트롤러에 옮길 차례입니다. 어렵지 않습니다. 이전의 배웠던 작업들과 조금의 기능이 더 필요할 뿐입니다!
@WebServlet(name = "mvcItemSaveServlet", urlPatterns = "지정할 URL 위치")
public class MvcItemSaveServlet extends HttpServlet {
private ItemRepository itemRepository = ItemRepository.getInstance();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String itemName = req.getParameter("itemName");
int stock = Integer.parseInt(req.getParameter("stock"));
Item item = new Item(itemName, stock);
itemRepository.save(item);
// 내부 저장소에 저장
req.setAttribute("member", member);
String viewPath = "jsp위치.jsp";
RequestDispatcher requestDispatcher = req.getRequestDispatcher(viewPath);
requestDispatcher.forward(req, resp);
}
}
- RequestDispatcher : 클라이언트로부터 요청을 수신하고 이를 서버의 모든 리소스(예: 서블릿, HTML 파일 또는
JSP 파일)로 보내는 객체를 정의합니다. - forward(HttpServletRequest, HttpServletResponse) : 해당 리소스로 포워딩을 명령합니다.
- 깨알 지식! 포워딩 vs 리다이렉트
- 포워딩 : 서버 내부적으로 동일 웹 컨테이너가 관리하는 다른 페이지로 이동. Request, Response 객체를
공유하며 URL이 변경되지 않기 때문에 클라이언트(브라우저)측에서는 이 사실을 모른다. - 리다이렉트 : 서버가 클라이언트(브라우저)에게 제시하는 URL로 이동하라고 명령
새롭게 요청을 하는 것이기 때문에 Request, Response 객체는 공유되지 않는다!
다른 웹 컨 테일러가 관리하는 페이지로 이동할 수 있다.
- 포워딩 : 서버 내부적으로 동일 웹 컨테이너가 관리하는 다른 페이지로 이동. Request, Response 객체를
- 깨알 지식! 포워딩 vs 리다이렉트
- Model은 HttpServletRequest의 내부 저장소를 이용하고 있습니다. => setAttribute( )
- 만약 jsp를 url을 통해 실행하고 싶지 않다면 jsp파일을 WEB-INF 디렉터리에 넣는 것을 권장합니다.
이렇게 뷰는 렌더링의 역할만, 컨트롤러는 로직을 실행하는 역할로 관심사를 분리(캡슐화)해서 더욱 진보되었습니다.
MVC 패턴 리팩터링
MVC 패턴을 만들다 보니 캡슐화가 어느 정도 완성되었고 깔끔해 보입니다. 그런데 여기서 더 발전할 여지가 많습니다.
개선할 점을 나열해보자면...
1. RequestDispatcher를 통해 forward( ) 사용의 중복
2. ViewPath에서 '위치' + '. jsp' 중복
3. HttpServletRequest, HttpServletResponse의 선택적인 사용
- Request, Response 객체가 사용되는 컨트롤러가 있고 그렇지 않은 컨트롤러가 존재합니다.
- 단적으로 Request의 저장소를 사용하지 않는 경우에는 이를 호출할 필요가 없습니다.
- (RequestDispatcher은 리팩터링을 하기 때문에 사용 범위를 배제하겠습니다.)
결국 또다시 추가 작업이 필요하다.
1, 2, 3의 부분들을 공통 처리로 묶어버린다면 정말 순수한 로직만 가지는 컨트롤러가 탄생하게 될 것입니다.
그런데 의문이 드실 겁니다
이 작업을 왜 해야 할까요?
쉽게 설명해봅시다.
A고등학교에서 전자출결 서비스를 도입했다고 합시다. 학생들은 등교와 하교를 할 때마다 자신이 들고 있는
학생증을 전자출결 모듈에 인식을 시켜야 합니다!
그래서 각 반에 전자출결 모듈을 설치를 해 봅시다.
이쁘게 설치가 잘 되었군요! 이렇게만 흘러가면 성공적인 변화입니다
그런데...
1년 만에 IC에서 NFC 방식을 도입한다고 전자출결 모듈을 모조리 바꾸어야 한다네요!
그래서 눈물을 머금고 바꿉니다.
또???
1년 뒤 주변의 고등학교가 통폐합이 되면서 반을 늘려야 된다고 합니다. 그런데 아뿔싸! 모듈의 수요가 너무 많아 가격이 올라가버렸습니다!
예산 책정을 하는데.. 괜히 눈물이 납니다.
으악! 차라리 이럴걸!
이를 현명하게 처리할 수 있는 방법이 무엇이 있을까요?
네 맞습니다. 차라리 교문에 이 출결 시스템을 도입하는 것입니다!
이렇게 되면 얻을 수 있는 장점이 많아집니다.
- 불필요한 출결 모듈의 구매를 줄일 수 있다!
- 반을 추가하거나, 시스템을 변경해야 하는 상황이 발생했을 때 교문의 시스템만 변경하면 된다!
자 이제 각 반을 컨트롤러에, 출결 모듈을 중복되는 코드(forward( ) 등..)에 대입을 해 봅시다.
이것이 바로 리팩터링을 해야 하는 이유입니다!
이렇게 결합되는 코드들이 없어지면(의존관계가 느슨해진다) 새로운 컨트롤러를 추가하기도 쉬워지고, 컨트롤러를 표현하는 방식 (JSP에서 다른 템플릿 엔진을 사용)을 변경하는 것에 대한 부담이 확 줄어듭니다!
이러한 기법을 FrontController 패턴이라고 합니다!
스프링은 이 핵심 패턴을 코어(DispatcherServlet)로 삼아 Spring MVC를 만들었습니다.
다음 포스트부터 어떤 방식으로 Spring MVC를 만들었는지 알아가 봅시다!
'SPRING > Spring MVC' 카테고리의 다른 글
HTTP 요청 데이터와 응답 데이터 입력 (0) | 2021.11.01 |
---|---|
MVC 사용 전 서블릿 (0) | 2021.11.01 |