Notice
Recent Posts
Recent Comments
Link
관리 메뉴

look-forest

Java 웹 개발 변천사: Servlet → JSP → MVC 패턴 본문

Spring/Spring MVC - 웹 개발 핵심 기술

Java 웹 개발 변천사: Servlet → JSP → MVC 패턴

studyHub 2021. 6. 24. 22:38

이번 시간에는

Servlet, JSP, MVC 패턴 순으로, Java 기반의 웹 개발의 발전 과정을 알아보겠다.

 

특히, 당연하게 써왔던 MVC 패턴을 왜 써야하는지를 알아보자.

 


서블릿으로 웹 애플리케이션 만들기

서블릿을 이용해 아래와 같이, 비즈니스 로직을 실행하고, 동적으로 HTML을 만들 수도 있다.

@WebServlet(name = "memberSaveServlet", urlPatterns = "/servlet/members/save")
public class MemberSaveServlet 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);

        response.setContentType("text/html");
        response.setCharacterEncoding("utf-8");
        response.getWriter().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 문서에서 필요한 곳만 코드를 적용해서 동적으로 변경할 수 있다.

※ 템플릿 엔진에는 JSP, Thymeleaf, Freemarker, Velocity등이 있다.

 


JSP로 웹 애플리케이션 만들기

JSP 라이브러리를 추가한 후 .jsp 파일을 만들어 쓸 수 있다.

JSP는 서버 내부에서 서블릿으로 변환된다.

 

아래 회원 저장 JSP를 보면, 회원 저장 서블릿 코드와 같다.

다른 점이 있다면, HTML을 중심으로 하고, 자바 코드를 부분부분 입력해주었다.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="study.mvc.domain.member.MemberRepository" %>
<%@ page import="study.mvc.domain.member.Member" %>
<%@ page import="java.util.List" %>
<%
    MemberRepository memberRepository = MemberRepository.getInstance();
    List<Member> members = memberRepository.findAll();
%>
<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>
    <%
        //request, response, out은 예약어. (서블릿으로 변환되어 쓰이니까)
        for (Member member : members) {
            out.write(" <tr>");
            out.write(" <td>" + member.getId() + "</td>");
            out.write(" <td>" + member.getUsername() + "</td>");
            out.write(" <td>" + member.getAge() + "</td>");
            out.write(" </tr>");
        }
    %>
    </tbody>
</table>
</body>
</html>

 

그런데.. 
관심사의 분리가 안된다.
비즈니스 로직과 뷰 영역이 모두 한 jsp 파일에 담겨 있다.
이렇게 되면 유지보수 하기가 힘들어진다.

 

관심사를 분리하여,
비즈니스 로직은 서블릿처럼 다른 곳에서 처리하고, 
JSP는 목적에 맞게 HTML로 화면을 그리는 일에만 집중하도록 분리할 수 없을까?

 

이를 위해 MVC 패턴이 등장했다.

 

 


MVC 패턴

MVC 패턴이 필요한 상황

단일 책임 원칙을 지키자

하나의 서블릿이나 JSP만으로 비즈니스 로직과 뷰 렌더링까지 모두 처리하게 되면,

너무 많은 역할을 하게되고, 결과적으로 유지보수가 어려워진다.

비즈니스 로직을 호출하는 부분에 변경이 발생해도 해당 코드를 손대야 하고,

UI를 변경할 일이 있어도 비즈니스 로직이 함께 있는 해당 파일을 수정해야 한다.

 

변경 주기가 다르면 분리해야 한다

UI 를 일부 수정하는 일과 비즈니스 로직을 수정하는 일은

각각 다르게 발생할 가능성이 매우 높고 대부분 서로에게 영향을 주지 않는다.

이렇게 변경의 라이프 사이클이 다른 부분을 하나의 코드로 관리하는 것은 유지보수하기 좋지 않다.

 

관심사를 분리하자

JSP 같은 뷰 템플릿은 화면을 렌더링 하는데 최적화 되어 있기 때문에 이 부분의 업무만 담당하는 것이 가장 효과적이다

서로 다른 관심사, 비즈니스 로직과 뷰 렌더링을 분리하자

 

Model, View, Controller

MVC 패턴은 컨트롤러(Controller)와 뷰(View)라는 영역으로 서로 역할을 나눈 것을 말한다.

둘 사이의 데이터는 모델(Model)을 통해 전달한다.

 

Controller: 흐름을 컨트롤한다.

HTTP 요청을 받아서 파라미터를 검증하고, 비즈니스 로직을 실행한다.

그리고 뷰에 전달할 결과 데이터를 조회해서 모델에 담고, 뷰를 지정해 제어권을 넘긴다.

 

Model: 뷰에 출력할 데이터를 담아둔다.

필요한 데이터를 모두 모델에 담아서 전달해주는 덕분에 뷰는 비즈니스 로직이나 데이터 접근을 몰라도 되고,

화면을 렌더링 하는 일에 집중할 수 있다.

 

View: 화면을 렌더링한다.

모델에 담겨있는 데이터를 사용해서 화면을 그리는 일에 집중한다

 

유지보수 하기 쉽도록 역할을 분리!

 

Model, View, Controller, Service, Repository

컨트롤러에 비즈니스 로직을 둘 수도 있지만, 이렇게 되면 컨트롤러가 너무 많은 역할을 담당한다.

그래서 일반적으로 비즈니스 로직은 서비스(Service)라는 계층을 별도로 만들어서 처리한다.

데이터 접근에 관한 부분은 Repository라는 계층을 만들어 처리한다.

컨트롤러는 비즈니스 로직이 있는 서비스를 호출하는 담당한다.

 

 


MVC 패턴 적용

서블릿을 Controller로, JSP를 View로 사용해서 MVC 패턴을 적용해보자

Model은 HttpServletRequest 객체를 사용한다. (내부에 데이터 저장소를 가지고 있다. request.setAttribute())

 

[비즈니스 로직 부분 - Servlet]

//Controller 역할
@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);

        //View로 forwarding
        String viewPath = "/WEB-INF/views/save-result.jsp";
        request.getRequestDispatcher(viewPath).forward(request, response);
    }
}

[View 부분 - JSP]

<%@ 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>

MVC 패턴을 적용하여 컨트롤러 로직과 뷰 로직 역할을 명확하게 구분할 수 있게 되었다!

향후 화면에 수정이 발생하면 뷰 로직만 변경하면 된다!

 


MVC 패턴의 한계

뷰는 깔끔해졌지만,

컨트롤러들은 중복이 많고 불필요한 코드가 많이 보인다는 것을 알 수 있다.

 

1. 포워드 중복

View로 이동하는 코드가 항상 중복 호출되어야 한다

String viewPath = "/WEB-INF/views/new-form.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);

 

2. ViewPath에 중복

만약 jsp가 아닌 thymeleaf와 같은 다른 뷰로 변경한다면 전체 코드(.jsp)를 다 변경해야 한다.

String viewPath = "/WEB-INF/views/new-form.jsp";

 

3. 사용하지 않는 코드

HttpServletRequest request, HttpServletResponse responses는 사용할 때도 있고, 사용하지 않을 때도 있다.

그리고 이런 HttpServletRequest, HttpServletResponse를 사용하는 코드는 테스트 케이스를 작성하기도 어렵다.

 

4. 공통 처리가 어렵다

중복되는 부분이 많은데, 공통 처리가 어렵다.

기능이 복잡해질수록 컨트롤러에서 공통으로 처리해야 하는 부분이 점점 더 많이 증가할 것이다(로그 출력 등)

이 문제를 해결하려면 컨트롤러 호출 전에 먼저 공통 기능을 처리해야 한다.

입구를 하나로 만들어 거쳐가도록, 소위 수문장 역할을 하는 기능이 필요하다!

프론트 컨트롤러(Front Controller) 패턴을 도입하면 이런 문제를 깔끔하게 해결할 수 있다.

스프링 MVC의 핵심도 바로 이 프론트 컨트롤러에 있다.

 

 


 

참고 자료 & 이미지 출처
스프링 MVC 1편(김영한 님)


'Spring > Spring MVC - 웹 개발 핵심 기술' 카테고리의 다른 글

스프링 MVC 기본 기능 - 요청  (0) 2023.05.07
Spring MVC 구조 이해  (0) 2023.05.01
MVC 프레임워크 만들기  (0) 2023.04.28
Servlet을 이용한 웹 개발  (0) 2021.06.23
Servlet과 멀티스레딩  (0) 2021.05.31