서블릿(Servlet) 방식: HTML을 Java 코드로 출력하는 구조

@WebServlet(name = "memberSaveServlet", urlPatterns = "/servlet/members/save")
public class MemberSaveServlet extends HttpServlet {

    MemberRepository memberRepository = MemberRepository.getInstance();

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("MemberSaveServlet.service");

        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");
        PrintWriter w = response.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을 Java 코드로 출력해야 함
  • HttpServletRequest, HttpServletResponse를 직접 다룸
  • 화면 출력 로직과 비즈니스 로직이 한 파일에 뒤섞임 → 가독성 및 유지보수에 악영향

🔻 한계

  • HTML 작성이 불편하고 가독성 낮음
  • 프론트 개발자와의 협업 어려움 (Java 코드 안에 HTML 박제됨)
  • 작은 UI 수정도 Java 코드를 수정해야 함 → 유지보수 지옥

 

 

JSP (Java Server Pages): HTML 안에 Java 코드 삽입하는 구조

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "hello.servlet.domain.member.Member" %>
<%@ page import = "hello.servlet.domain.member.MemberRepository" %>
<%
    // reqeust, response 그냥 사용 가능
    MemberRepository memberRepository = MemberRepository.getInstance();
    System.out.println("MemberSaveServlet.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>
  • HTML을 기반으로 화면을 구성
  • 필요한 곳에만 <% 자바 코드 %> 삽입
  • 서블릿보다 가독성과 생산성이 크게 향상됨

장점

  • HTML을 그대로 사용할 수 있어 프론트 작업이 쉬움
  • 뷰 템플릿처럼 활용 가능
  • 서블릿보다 직관적인 구조 → 빠른 개발 가능

🔻 한계

  • 자바 코드가 JSP에 점점 많아지면, 뷰와 로직이 결국 다시 뒤섞임
  • 구조가 무너지면 유지보수는 또 다시 서블릿처럼 어려워짐
  • 결국 JSP도 내부적으로 서블릿으로 변환되기 때문에 오류 추적(디버깅)이 복잡하고 직관적이지 않음

 

방식 문제점
서블릿 모든 역할이 한 클래스에 몰려 있음 → 화면 코드까지 out.println()
JSP HTML 중심이지만 자바 로직이 섞임 → 뷰와 비즈니스 로직이 뒤섞임

서블릿과 JSP 패턴의 문제점

 

 

 

 

"HTML은 HTML대로, 자바 로직은 따로"

역할 분리가 필요하다






그래서 등장한 구조: MVC 패턴

@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);
    }
}



// ---------------------------------------------------------------------



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



// --------------------------------------------------------------------


(1) 클라이언트 요청 → /members/save

(2) Controller (Servlet)
    - request 파라미터 추출
    - Member 객체 생성 & 저장
    - request.setAttribute("member", member)

(3) View 호출
    - dispatcher.forward("/WEB-INF/views/save-result.jsp")

(4) JSP (View)
    - ${member.username} 으로 데이터 출력

(5) 응답 → 클라이언트 브라우저로 HTML 전달

 

 

구성 요소 역할
Model 데이터, 비즈니스 로직 (Service, Repository)
View 화면 출력 전담 (HTML, JSP, Thymeleaf 등)
Controller 클라이언트 요청을 받고, 모델을 호출하고, 결과를 뷰에 전달

MVC

 

  • Model: 데이터를 담고 전달하는 객체 (DTO, VO)
  • Controller: 요청을 받아서, 로직을 호출하고, 결과를 뷰로 전달
  • View: HTML을 생성하여 사용자에게 보여주는 역할
  • Service: 비즈니스 로직을 담당 (선택적)
  • DispatcherServlet: 스프링 MVC에서는 요청을 가장 먼저 받는 프론트 컨트롤러

 

  • 컨트롤러에 비즈니스 로직을 두면, 너무 많은 역할을 하게 된다. 그래서 비즈니스 로직은 Service라는 계층을 별도로 만들어서 처리.
  • WEB-INF, 이 경로안에 JSP가 있으면 외부에서 직접 JSP를 호출할 수 없다. 항상 컨트롤러를 통해서 JSP를 호출하는 것.
  • 리다이렉트는 실제 클라이언트(웹 브라우저)에 응답이 나갔다가, 클라이언트가 redirect 경로로 다시 요청한다. 따라서 클라이언트가 인지할 수 있고, URL 경로도 실제로 변경된다. 반면에 포워드는 서버 내부에서 일어나는 호 출이기 때문에 클라이언트가 전혀 인지하지 못한다.
항목 Forward Redirect
경로 이동 서버 내부 클라이언트가 새 요청
브라우저 주소창 변하지 않음 변경됨
데이터 전달 request 유지 새 request (기존 request 사라짐)
사용 목적 뷰 렌더링 PRG 패턴, 이동/새로고침 방지 등
속도 빠름 느림 (두 번 요청)

Forward vs Redirect

 

MVC 패턴의 로직 흐름

 

 

 

MVC 패턴 - 한계

  • Controller의 과도한 책임 (Fat Controller)
    → 요청 파라미터 추출, 검증, 모델 조작, 뷰 경로 반환 등 모든 책임이 몰림 → 유지보수 어려움
  • ViewPath의 중복
      뷰 경로 문자열을 반복 사용 → 오타 위험, 뷰 기술 변경 시 전체 코드 수정 필요
  • HttpServletRequest, HttpServletResponse 항상 매개변수로 존재
      사용하지 않더라도 항상 받아야 함 → 테스트 어려움, 코드 복잡도 증가
  • 공통 처리의 어려움
      인증, 로깅, 예외 처리 등 공통 기능을 각 컨트롤러마다 수동으로 호출해야 함 → 실수 및 중복 발생

 

 

 

 

 "모든 요청을 한 곳에서 먼저 처리하면 어떨까?"
→ Front Controller 패턴 도입



 

 

Front Controller

  • 입구를 하나로 만든다
  • 모든 요청을 한 곳에서 받아 공통 처리를 먼저 수행
  • 이후 각 기능별 컨트롤러로 위임
  • Spring MVC의 DispatcherServlet이 바로 이 구조

효과

  • 공통 처리 용이 (AOP, 필터, 인터셉터 등)
  • 중복 제거 (뷰 경로 prefix/suffix 공통 설정 가능)
  • 구조적 강제력 확보 (모든 요청이 반드시 해당 컨트롤러를 거치게 됨)