패스트캠퍼스 데브캠프 : 남궁성의 백엔드 개발 3기

Spring MVC | 서블릿과 JSP (2)

Tech_JINI 2025. 2. 26. 18:29

HTTP는 상태 정보를 저장하지 않는 프로토콜로, 상태를 유지하려면 저장소가 필요하다.

이를 위해 웹 애플리케이션은 여러 종류의 저장소를 제공한다.

저장소는 주로 Map 형태로 데이터를 저장하며, 다양한 범위와 생존 기간을 가진 저장소들이 존재한다.

HTTP의 상태 정보 저장

HTTP는 기본적으로 상태 정보를 저장할 수 없다.

이는 매번 요청마다 새로운 연결을 맺기 때문에, 예를 들어 로그인 정보나 장바구니 정보 같은 사용자 상태를 유지할 수 없다는 것을 의미한다. 이를 해결하기 위해 저장소가 필요하다.

저장소 종류

웹 애플리케이션은 4가지 종류의 저장소를 제공한다:

  • pageContext: 현재 페이지에만 접근 가능한 저장소로, 요청할 때마다 새로 초기화된다.
  • application: 애플리케이션 전체에서 접근할 수 있는 저장소로, 한 번만 존재하며 모든 페이지에서 공유된다.
  • session: 클라이언트마다 개별적으로 저장소를 제공하여, 각 사용자의 상태를 유지할 수 있게 한다.
  • request: 요청마다 생성되는 저장소로, 요청에 관련된 데이터를 일시적으로 저장한다.

이 저장소들은 각각 setAttribute와 getAttribute 메서드를 이용하여 데이터를 저장하고 불러올 수 있다.

 

웹 애플리케이션은 전체에서 접근 가능한 단일 저장소로, 애플리케이션 내에서 데이터를 읽고 쓰는 데 사용된다.

이 저장소는 getAttribute와 setAttribute를 통해 데이터를 가져오고 설정할 수 있다.

 

하지만 HTTP는 상태 정보를 저장하지 않기 때문에, login.jsp와 write.jsp가 서로 다른 요청으로 처리될 경우, 두 페이지가 요청하는 클라이언트가 같은 사람인지 알 수 없다.

즉, 사용자가 로그인 후 다른 페이지로 이동한다고 해도 서버는 그 사용자가 동일인물인지 인식하지 못한다.

이를 해결하기 위해서는 상태 정보를 저장할 수 있는 저장소가 필요하다.

 

application 저장소에 id와 같은 속성을 저장한다고 하더라도, 이는 애플리케이션 전체에서 공유되는 데이터이기 때문에 각 사용자의 정보가 모두에게 노출될 수 있다.

 

따라서 사용자별 정보를 저장하기에는 적합하지 않다.

이런 이유로 세션이 등장하여, 각 사용자별로 독립적인 저장소를 제공하게 된다.

 

 

세션(Session)

세션은 클라이언트마다 개별 저장소를 갖는다.

즉, 각 사용자는 독립적인 세션 객체를 가지며, 이 객체에 데이터를 저장할 수 있다.

예를 들어, id장바구니와 같은 사용자가 고유하게 사용하는 데이터를 세션에 저장하면 적합하다.

 

세션은 쿠키를 이용해 해당 세션 객체가 어느 클라이언트에 속하는지를 연결한다.

쿠키는 세션 ID를 클라이언트에 저장하고, 클라이언트는 이후 요청을 보낼 때 해당 세션 ID를 서버로 전송하여 세션을 식별한다.

 

사용자가 로그아웃하면 해당 세션은 종료되고, 세션에 저장된 데이터는 삭제된다.

이를 통해 사용자별로 개별적인 정보를 관리할 수 있다.

세션은 사용자마다 하나의 세션 객체가 생성되며, 저장공간에는 최소한의 데이터만 저장한다.

세션 객체는 로그인한 동안 여러 페이지에 접근할 수 있어 편리하다.

// 세션에 id 값을 저장
HttpSession session = request.getSession();
session.setAttribute("id", "user123");

// 세션에서 id 값을 불러오기
String id = (String) session.getAttribute("id");

request 저장소

request 저장소는 요청마다 생기는 저장소로, 각 요청에 대해 데이터를 저장하는 맵(Map)을 제공한다.

보통 요청을 처리하는 페이지는 하나의 JSP 파일이 담당한다.

 

하지만, 요청이 하나의 JSP에서 끝나지 않고 다음 JSP로 넘겨지는 경우(forward), 해당 페이지가 응답을 담당하게 된다.

이처럼 JSP 간에 데이터를 넘기고 전달하는 구조가 웹 프로그래밍의 핵심이다.

 

request는 단기적인 데이터 저장소로 사용되며, 세션보다는 메모리 부담이 적고, 필요한 경우 데이터를 전달하고 삭제할 수 있다.

 

 

setAttribute와 getAttribute

  • setAttribute: 값을 저장하는 메서드로, 예를 들어 request.setAttribute("id", "aaa");로 id 값을 aaa로 설정할 수 있다.
  • getAttribute: 값을 가져오는 메서드로, 예를 들어 request.getAttribute("id")로 id 값을 가져올 수 있다.
// request에 id 값을 저장
request.setAttribute("id", "user123");

// request에서 id 값을 불러오기
String id = (String) request.getAttribute("id");

 

URL 패턴과 서블릿 매핑

@WebServlet을 사용하여 서블릿을 URL에 매핑할 때 사용하는 패턴이 있다. 서블릿의 URL 매핑 방식은 다음과 같이 4가지로 구분된다.

 

  1. Exact Mapping: URL이 정확히 일치해야 한다.
  2. Path Mapping: 경로 패턴이 일치하는 URL에 매핑된다.
  3. Extension Mapping: URL 확장자에 따라 매핑된다.
  4. Default Mapping: 모든 URL에 매핑된다.

Spring에서는 @RequestMapping을 사용하여 URL 패턴을 설정하며, 이를 통해 서블릿을 매핑할 수 있다.

 

 

 

서블릿 처리 흐름

요청이 들어오면 서블릿 매핑을 확인하여, 요청과 일치하는 URL 패턴이 있는지 확인한다.

만약 일치하면 해당 서블릿이 요청을 처리하고, 일치하는 것이 없으면 디폴트 서블릿이 요청을 처리한다.

 

/hello, *.jsp와 같은 패턴은 동적 리소스(서블릿)에 해당하고, 디폴트 서블릿은 정적 리소스(image, css, text) 처리에 사용된다.

실제로는 서블릿은 DefaultServlet을 사용하지 않고, DispatcherServlet과 연결된다.

따라서 요청한 값과 일치하는 서블릿이 없으면 DispatcherServlet이 실행된다.

 

Spring에서 프로젝트를 진행할 때는 서블릿이나 JSP 파일을 직접 등록하지 않아도, 모든 요청은 DispatcherServlet이 처리한다. DispatcherServlet 안에는 servletMapping과 같은 매핑 정보가 포함되어 있다.

 

 

EL(Expression Language)

EL은 JSP에서 값을 쉽게 표현할 수 있도록 도와주는 언어이다. 예를 들어, <%= 값 %> 대신 ${값}을 사용하여 값을 출력할 수 있다. EL을 사용하면 코드가 더 간결하고 편리하다.

${person.name} // person 객체의 name 프로퍼티 출력

 

JSTL(JSP Standard Tag Library)

JSTL은 JSP에서 Java 코드를 없애기 위한 태그 라이브러리로, 복잡한 Java 코드를 JSP 페이지에서 제거하고, 보다 직관적인 태그로 동적인 콘텐츠를 처리할 수 있게 해준다.

<c:set var="name" value="John" />
<p>${name}</p> // 출력: John

 

Filter

필터는 요청 전처리응답 후처리를 담당하는 컴포넌트이다. 로깅, 인코딩 설정, 권한 체크 등을 필터에서 처리할 수 있다. 필터는 여러 개가 있을 수 있으며, 공통적인 작업을 여러 요청에 대해 수행할 수 있다.

@WebFilter("/secure/*")
public class AuthenticationFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 인증 체크 코드
        chain.doFilter(request, response); // 요청 계속 진행
    }
}