서블릿(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 패턴 - 한계
- Controller의 과도한 책임 (Fat Controller)
→ 요청 파라미터 추출, 검증, 모델 조작, 뷰 경로 반환 등 모든 책임이 몰림 → 유지보수 어려움 - ViewPath의 중복
→ 뷰 경로 문자열을 반복 사용 → 오타 위험, 뷰 기술 변경 시 전체 코드 수정 필요 - HttpServletRequest, HttpServletResponse 항상 매개변수로 존재
→ 사용하지 않더라도 항상 받아야 함 → 테스트 어려움, 코드 복잡도 증가 - 공통 처리의 어려움
→ 인증, 로깅, 예외 처리 등 공통 기능을 각 컨트롤러마다 수동으로 호출해야 함 → 실수 및 중복 발생
"모든 요청을 한 곳에서 먼저 처리하면 어떨까?"
→ Front Controller 패턴 도입
Front Controller
- 입구를 하나로 만든다
- 모든 요청을 한 곳에서 받아 공통 처리를 먼저 수행
- 이후 각 기능별 컨트롤러로 위임
- Spring MVC의 DispatcherServlet이 바로 이 구조
✅ 효과
- 공통 처리 용이 (AOP, 필터, 인터셉터 등)
- 중복 제거 (뷰 경로 prefix/suffix 공통 설정 가능)
- 구조적 강제력 확보 (모든 요청이 반드시 해당 컨트롤러를 거치게 됨)
'공부 > Spring' 카테고리의 다른 글
| [스프링 MVC 5 / 인프런 김영한] 스프링 MVC - 구조 이해 (0) | 2025.03.30 |
|---|---|
| [스프링 MVC 4 / 인프런 김영한] MVC 프레임워크 만들기 (0) | 2025.03.29 |
| [스프링 MVC 2 / 인프런 김영한] 서블릿 (0) | 2025.03.28 |
| [스프링 MVC 1 / 인프런 김영한] 웹 애플리케이션 이해 (0) | 2025.03.27 |
| [스프링 핵심 원리 8 / 인프런 김영한] 빈 스코프 (0) | 2025.03.25 |
