Spring - 스프링 부트 이전의 WAS(Servlet, JSP)에 관하여

2 분 소요

해당 글은 김영한님의 인프런 강의 “스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술”의 내용을 학습 후 정리하기 위해 작성하는 글입니다.


스프링MVC 프레임워크가 나오기 이전에는 Servlet과 JSP를 이용하여 MVC 형태를 직접 구현하였다. 공부하면서 알게된 이들의 차이점, 개선방안들에 대해 정리해보려고한다.

1. Servlet

먼저 Servlet이란 클라이언트와 서버간의 HTTP 통신을 자바를 통해 구현하고, 이를 통하여 쉽게 WAS를 구축할 수 있도록 도와주는 기술이다.

img1

@WebServlet 어노테이션으로 간단하게 url을 지정해주며 보기쉽게 이름을 지정해줄 수 있다. HTTPServlet 클래스를 상속받아서 service() 메소드 오버라이딩을 통해 HTTP 통신의 request, response를 각각의 클래스 인스턴스로 받아올 수 있기 때문에 HTTP 요청 파라미터를 가져오거나, HTTP 응답 데이터를 쉽게 지정해줄 수 있다.

@WebServlet("/page")
public class PageServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) {
        
        // HTTP 헤더 정보 가져오기
        Enumeration<String> headers = req.getHeaderNames();
        
        // 특정 파라미터 값 가져오기
        String data = req.getParameter("name");
        
        // HTTP 메세지 바디 전체 가져오기
        ServletInputStream inputStream = req.getInputStream();
        String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);

        // ObjectMapper를 통해 JSON 데이터 오브젝트로 파싱
        private ObjectMapper objectMapper = new ObjectMapper();
        SomeClass someClass = objectMapper.readValue(messageBody, SomeClass.class);
    }
}

이처럼 서블릿을 통하여 HTTP 통신을 간단하게 구현할 수 있다. 하지만 클라이언트에게 text/html 데이터를 전송해줘야 할때(웹 페이지 뷰를 전송할 때)는 그렇게 간단하지 않다.

@Override
protected void service(생략...) {

    // Content-Type: text/html
    resp.setContentType("text/html");
    // charset: UTF-8
    resp.setCharacterEncoding("UTF-8");
    
    PrintWriter w = resp.getWriter();
    
    w.write("<html>\n...")
    // 모든 html 구문을 작성해야 한다.
    
}

html 파일을 직접 생성하지 않고 자바 코드만으로 text/html 포맷을 전송할 수는 있지만 이는 매우 불편하고 구문 작성중 문제가 생겼을때 디버그가 매우 어렵다.

2. JSP

JSP는 기존 Servlet에서 html 구문 작성 부분의 단점을 없애고 html 코드 내부에서 자바 문법을 사용할 수 있도록 하는 웹페이지 스크립트 언어이다.

JSP문법은 <% %>처럼 블록을 지정하여 블록 안에서 자바 코드를 구현할 수 있고 문법에 대한 자료는 인터넷에서 쉽게 찾아볼 수 있다.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    SomeClassRepository repo = SomClassRepository.getInstance();
%>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
    for (SomeClass some : repo.findAll()) {
        out.write(some.attribute);
    }
%>
</form>
</body>
</html>

JSP를 통해 기존 서블릿의 단점을 보완하여 View(html)와 HttpServlet 클래스를 나누어 표현하며 동적으로 변경이 필요한 html 부분에 자바 코드를 추가할 수 있게 되었다.

하지만 이렇게 Hello-World와 같은 간단한 예제가 아닌 실제 프로젝트를 진행하게 되면 JSP파일 안에 자바 코드, 비즈니스 로직, html의 view와 같은 부분들이 모두 들어가게 되는데 수백, 수천줄이 넘어간다면 유지보수가 매우 힘들것이다.

3. MVC 패턴

위와 같은 문제를 해결하기 위해서는 Model, View, Controller와 같이 각각의 역할을 기준으로 나누는 MVC패턴을 사용해야 한다.

컨트롤러와 뷰는 각각 모델에 데이터를 전달, 참조한다. 서블릿을 컨트롤러로, JSP를 뷰로 사용한다면 아래와 같이 구현할 수 있다.

@WebServlet("/some/page/jsp")
public class SomeJspPage extends HttpServlet {

    @Override
    protected void service(생략...) {

        SomeClass data = SomeClass();

        //Model에 데이터 전달 (MVC 패턴)
        req.setAttribute("member", data);

        String viewPath = "/WEB-INF/views/some-page.jsp";
        RequestDispatcher dispatcher = req.getRequestDispatcher(viewPath);
        dispatcher.forward(req, resp);
    }
}

RequestDispatcher.forward()를 통해 viewPath를 지정하여 다른 서블릿이나 JSP로 이동하게 되는데 forward는 redirect와는 성질이 다르다.

  • redirect의 경우 기존의 환경에서 클라이언트는 리다이렉트가 이루어지는 페이지에도 접근하여 하나의 웹 서비스에서도 두번의 HTTP 요청이 이루어진다.

  • forward의 경우 클라이언트가 jsp파일을 직접 접근하는 것이 아니라 HTTP 요청이 이루어질 때 내부 서버에서 jsp파일에 접근한 후 결과(View)를 클라이언트에게 전송한다.

또한 파라미터로 req와 resp를 직접 jsp파일로 보내게 되고 jsp파일에서는 request, response라는 변수 명으로 직접 각각의 HTTP 인스턴스들에 접근이 가능하다.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
Model의 데이터 참조
<ul>
    <li>id=${member.id}</li>
    <li>username=${member.username}</li>
    <li>age=${member.age}</li>
</ul>
</body>
</html>

지금의 MVC패턴이 나오기 전까지 Servlet과 JSP를 통해서만 WAS를 구성하는 것이 얼마나 복잡한 일인지, 어떤 필요성으로 인해 MVC패턴이 나오게 되었는지 알게 되었다.

역할을 구분지어줌으로서 각 부분의 업무만 담당하는 것이 가능하므로 효과적인 설계인 것 같다. 또한 뷰와 비즈니스 로직 각각의 변경의 라이프 사이클이 다르기 때문에 유지보수 측면에서도 MVC패턴으로서 얻는 이점은 매우 크다.

댓글남기기