풀스택 백엔드 2026.06.08-2

syyu21b·2026년 6월 8일

풀스택 - 백엔드

목록 보기
25/49
post-thumbnail

[SECTION 02. View를 분리하자 — JSP와 MVC 패턴의 등장]

  • JSP(Java Server Pages) = HTML 파일 안에 Java 코드를 삽입할 수 있는 기술

서블릿 vs JSP 비교

서블릿JSP
기본 형태Java 코드 안에 HTMLHTML 안에 Java 코드
주요 역할요청 처리, 비즈니스 로직화면 출력 (View)
파일 위치src/main/java/src/main/webapp/
확장자.java.jsp

JSP의 동작 원리

JSP는 마법이 아닙니다. 결국 서블릿으로 변환되어 실행됩니다.

hello.jsp
    ↓ (Tomcat이 자동 변환)
hello_jsp.java (서블릿)
    ↓ (컴파일)
hello_jsp.class
    ↓ (실행)
HTML 응답

즉 JSP는 "서블릿을 더 편하게 쓰기 위한 도구"입니다. 내부적으로는 똑같이 서블릿입니다.


JSP 기본 문법

pom.xml에 JSP 의존성 추가

<dependencies>
    <!-- 기존 서블릿 API -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>

    <!-- JSP API 추가 -->
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.3.1</version>
        <scope>provided</scope>
    </dependency>

    <!-- JSTL 추가 (JSP 태그 라이브러리) -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>

JSP 태그 종류

태그문법용도
지시자<%@ %>JSP 설정 (인코딩, import 등)
선언<%! %>멤버 변수/메서드 선언
스크립틀릿<% %>Java 코드 실행
표현식<%= %>값을 HTML에 출력
주석<%-- --%>JSP 주석 (HTML에 미포함)

첫 번째 JSP 파일

src/main/webapp/hello.jsp 생성:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>첫 번째 JSP</title>
</head>
<body>

    <h1>안녕하세요!</h1>

    <%-- JSP 주석: HTML 소스에 보이지 않음 --%>

    <%
        // 스크립틀릿: Java 코드 실행
        String name = "홍길동";
        int age = 25;
    %>

    <p>이름: <%= name %></p>       <%-- 표현식: 값 출력 --%>
    <p>나이: <%= age %>세</p>

    <%
        // 조건문도 사용 가능
        if (age >= 20) {
    %>
        <p>성인입니다.</p>
    <%
        } else {
    %>
        <p>❌ 미성년자입니다.</p>
    <%
        }
    %>

</body>
</html>

브라우저에서 직접 접속: http://localhost:8080/hello.jsp

체감 포인트: HTML 안에 Java 코드가 <% %> 로 들어가니까 훨씬 읽기 쉬워졌습니다. 하지만 코드가 복잡해질수록 <% %> 가 난무해서 또 지저분해집니다.


서블릿 → JSP로 데이터 전달하기

RequestDispatcher

서블릿에서 JSP로 요청을 포워드(forward) 하는 도구입니다.

[브라우저] → 요청 → [서블릿] → forward → [JSP] → 응답 → [브라우저]
                      데이터 준비      화면 출력

forward vs redirect

  • forward: 서버 내부에서 이동. URL이 바뀌지 않음. request 객체 공유 가능
  • redirect: 브라우저에게 "다른 URL로 가세요" 지시. URL이 바뀜. request 객체 새로 생성

실습: 회원 목록 페이지

MemberListServlet.java 작성:

package com.example;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@WebServlet("/members")
public class MemberListServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        // 1. 데이터 준비 (나중에는 DB에서 가져옴)
        List<String[]> members = new ArrayList<>();
        members.add(new String[]{"홍길동", "hong@email.com", "서울"});
        members.add(new String[]{"김철수", "kim@email.com", "부산"});
        members.add(new String[]{"이영희", "lee@email.com", "대구"});

        // 2. request에 데이터 저장 (JSP에서 꺼내 쓸 수 있음)
        req.setAttribute("members", members);
        req.setAttribute("totalCount", members.size());

        // 3. JSP로 포워드 (화면 출력은 JSP에게 위임)
        RequestDispatcher dispatcher = req.getRequestDispatcher("/WEB-INF/views/memberList.jsp");
        dispatcher.forward(req, resp);
    }
}

왜 JSP를 WEB-INF 안에 넣는가?

webapp/
├── WEB-INF/
│   └── views/
│       └── memberList.jsp   ← ✅ 직접 URL 접근 불가 (보안)
└── hello.jsp                ← ❌ 브라우저에서 직접 접근 가능

WEB-INF 폴더 안의 파일은 브라우저에서 직접 URL로 접근할 수 없습니다. 반드시 서블릿을 통해서만 접근 가능하게 만들어 JSP가 단독으로 실행되는 상황을 방지합니다.


src/main/webapp/WEB-INF/views/memberList.jsp 생성:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="java.util.List" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>회원 목록</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 600px; margin: 30px auto; }
        table { width: 100%; border-collapse: collapse; }
        th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
        th { background-color: #4CAF50; color: white; }
        tr:nth-child(even) { background-color: #f2f2f2; }
    </style>
</head>
<body>

    <h2>회원 목록</h2>

    <%-- 서블릿이 저장한 데이터 꺼내기 --%>
    <%
        List<String[]> members = (List<String[]>) request.getAttribute("members");
        Integer totalCount = (Integer) request.getAttribute("totalCount");
    %>

    <p><strong><%= totalCount %></strong>명의 회원이 있습니다.</p>

    <table>
        <tr>
            <th>번호</th>
            <th>이름</th>
            <th>이메일</th>
            <th>지역</th>
        </tr>
        <%
            for (int i = 0; i < members.size(); i++) {
                String[] member = members.get(i);
        %>
        <tr>
            <td><%= i + 1 %></td>
            <td><%= member[0] %></td>
            <td><%= member[1] %></td>
            <td><%= member[2] %></td>
        </tr>
        <%
            }
        %>
    </table>

</body>
</html>

http://localhost:8080/members 접속해서 확인해 봅시다.


MVC 패턴 적용하기

MVC란?

역할담당하는 일
ModelJava 객체 (데이터)비즈니스 데이터 표현 (담아서 이동시키고 표현하는 것)
ViewJSP, Thymeleaf, mustache화면 출력
Controller서블릿요청 받기, 데이터 준비, View 선택
[브라우저]
    ↓ 요청
[Controller: 서블릿]
    ↓ 데이터 요청
[Model: Java 객체]
    ↓ 데이터 반환
[Controller: 서블릿]
    ↓ Model을 request에 담아 forward
[View: JSP]
    ↓ HTML 생성
[브라우저] ← 응답

MVC 패턴으로 게시판 만들기

섹션 1의 회원가입 예제를 MVC 패턴으로 리팩토링합니다.

  • 1단계: Model 클래스 만들기

src/main/java/com/example/model/Member.java 생성:

package com.example.model;

public class Member {

    private String name;
    private String email;
    private String password;

    // 생성자
    public Member(String name, String email, String password) {
        this.name = name;
        this.email = email;
        this.password = password;
    }

    // Getter
    public String getName()     { return name; }
    public String getEmail()    { return email; }
    public String getPassword() { return password; }
}
  • 2단계: Controller (서블릿) 수정

src/main/java/com/example/MemberController.java 생성:

package com.example;

import com.example.model.Member;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

// URL 패턴을 /member/* 로 지정해서 여러 기능을 하나의 서블릿에서 처리
@WebServlet("/member/*")
public class MemberController extends HttpServlet {

    // 임시 저장소 (실제로는 DB 사용)
    private List<Member> memberStore = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        String pathInfo = req.getPathInfo(); // /list, /new 등

        if (pathInfo == null || pathInfo.equals("/list")) {
            // 회원 목록 조회
            req.setAttribute("members", memberStore);
            forward(req, resp, "/WEB-INF/views/member/list.jsp");

        } else if (pathInfo.equals("/new")) {
            // 회원가입 폼
            forward(req, resp, "/WEB-INF/views/member/form.jsp");
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        req.setCharacterEncoding("UTF-8");
        String pathInfo = req.getPathInfo();

        if (pathInfo.equals("/new")) {
            String name     = req.getParameter("name");
            String email    = req.getParameter("email");
            String password = req.getParameter("password");

            // 유효성 검증
            if (name == null || name.trim().isEmpty()) {
                req.setAttribute("error", "이름을 입력해주세요.");
                forward(req, resp, "/WEB-INF/views/member/form.jsp");
                return;
            }

            // Model 저장
            memberStore.add(new Member(name, email, password));

            // ✅ POST 후 redirect (PRG 패턴: 새로고침 시 중복 제출 방지)
            resp.sendRedirect("/member/list");
        }
    }

    // forward 공통 메서드
    private void forward(HttpServletRequest req, HttpServletResponse resp, String path)
            throws ServletException, IOException {
        RequestDispatcher dispatcher = req.getRequestDispatcher(path);
        dispatcher.forward(req, resp);
    }
}
  • 3단계: View (JSP) 만들기

src/main/webapp/WEB-INF/views/member/form.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>회원가입</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 400px; margin: 50px auto; }
        input { width: 100%; padding: 8px; margin-top: 4px; box-sizing: border-box; }
        button { margin-top: 12px; padding: 10px 20px; background: #4CAF50; color: white; border: none; cursor: pointer; }
        .error { color: red; margin-bottom: 10px; }
    </style>
</head>
<body>

    <h2>회원가입</h2>

    <%-- 에러 메시지 출력 --%>
    <% if (request.getAttribute("error") != null) { %>
        <p class="error"><%= request.getAttribute("error") %></p>
    <% } %>

    <form action="/member/new" method="post">
        <label>이름 *<br>
            <input type="text" name="name">
        </label><br><br>

        <label>이메일<br>
            <input type="email" name="email">
        </label><br><br>

        <label>비밀번호<br>
            <input type="password" name="password">
        </label><br>

        <button type="submit">가입하기</button>
    </form>

</body>
</html>

src/main/webapp/WEB-INF/views/member/list.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="java.util.List" %>
<%@ page import="com.example.Member" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>회원 목록</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 600px; margin: 30px auto; }
        table { width: 100%; border-collapse: collapse; }
        th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
        th { background-color: #4CAF50; color: white; }
        a { display: inline-block; margin-bottom: 12px; padding: 8px 16px; background: #2196F3; color: white; text-decoration: none; }
    </style>
</head>
<body>

    <h2>회원 목록</h2>
    <a href="/member/new">+ 회원 추가</a>

    <%
        List<Member> members = (List<Member>) request.getAttribute("members");
    %>

    <% if (members == null || members.isEmpty()) { %>
        <p>등록된 회원이 없습니다.</p>
    <% } else { %>
        <p><strong><%= members.size() %></strong></p>
        <table>
            <tr>
                <th>번호</th><th>이름</th><th>이메일</th>
            </tr>
            <% for (int i = 0; i < members.size(); i++) {
                   Member m = members.get(i); %>
            <tr>
                <td><%= i + 1 %></td>
                <td><%= m.getName() %></td>
                <td><%= m.getEmail() %></td>
            </tr>
            <% } %>
        </table>
    <% } %>

</body>
</html>

테스트 순서

1. http://localhost:8080/member/new   → 회원가입 폼
2. 이름/이메일/비밀번호 입력 후 제출
3. http://localhost:8080/member/list  → 등록된 회원 목록 확인
4. 이름 비워두고 제출 → 에러 메시지 확인

섹션 정리

개념핵심 내용
JSPHTML 안에 Java 코드를 삽입할 수 있는 기술, 내부적으로 서블릿으로 변환
RequestDispatcher서블릿에서 JSP로 요청을 포워드하는 도구
request.setAttribute서블릿 → JSP로 데이터를 전달하는 방법
WEB-INF브라우저 직접 접근을 막는 보안 폴더, JSP는 여기에 보관
MVC 패턴Model(데이터), View(JSP), Controller(서블릿)로 역할 분리
PRG 패턴POST 후 Redirect하여 새로고침 중복 제출 방지
forward vs redirectforward는 서버 내부 이동, redirect는 브라우저에게 새 요청 지시

0개의 댓글