Spring

Spring MVC 요청 처리 흐름 가이드


📋 목차

  1. 프로젝트 구조
  2. Spring MVC 아키텍처
  3. 요청 처리 흐름
  4. 설정 파일 분석
  5. 컨트롤러 엔드포인트 상세
  6. 데이터 바인딩

프로젝트 구조

Spring MVC 프로젝트의 전형적인 디렉토리 구조를 먼저 살펴보겠습니다. 각 폴더와 파일의 역할을 이해하는 것이 Spring MVC 동작 원리를 파악하는 첫 걸음입니다.

sp1/
├── src/main/
│   ├── java/
│   │   └── org/zerock/
│   │       ├── controller/
│   │       │   └── HelloController.java    # 컨트롤러 (요청 처리)
│   │       ├── service/
│   │       │   └── HelloService.java      # 서비스 레이어
│   │       └── dto/
│   │           └── SampleDTO.java         # 데이터 전송 객체
│   └── webapp/
│       └── WEB-INF/
│           ├── web.xml                    # 웹 애플리케이션 설정
│           ├── spring/
│           │   ├── root-context.xml       # 루트 컨텍스트 (서비스, DB)
│           │   └── servlet-context.xml    # 서블릿 컨텍스트 (MVC)
│           └── views/
│               └── sample/
│                   ├── ex1.jsp
│                   ├── ex3Result.jsp
│                   └── success.jsp

주요 디렉토리 설명

  • controller: 사용자 요청을 처리하는 컨트롤러 클래스들이 위치합니다.
  • service: 비즈니스 로직을 담당하는 서비스 클래스들이 위치합니다.
  • dto: 데이터 전송 객체(Data Transfer Object)로, 계층 간 데이터 전달에 사용됩니다.
  • WEB-INF/spring: Spring 설정 파일들이 위치합니다.
  • WEB-INF/views: JSP 뷰 템플릿 파일들이 위치합니다.

Spring MVC 아키텍처

Spring MVC의 핵심은 이중 컨텍스트 구조입니다. 이는 웹 계층과 비즈니스 계층을 명확하게 분리하여 애플리케이션의 유지보수성과 확장성을 높입니다.

1. 이중 컨텍스트 구조

graph TB
    subgraph "Root ApplicationContext"
        RC[Root Context<br/>root-context.xml]
        S[Service Layer<br/>HelloService]
        DS[DataSource<br/>Database 연결]
        RC --> S
        RC --> DS
    end
    
    subgraph "Servlet ApplicationContext"
        SC[Servlet Context<br/>servlet-context.xml]
        C[Controller<br/>HelloController]
        VR[ViewResolver<br/>JSP 처리]
        SC --> C
        SC --> VR
    end
    
    SC -.부모 참조.-> RC
    
    style RC fill:#e1f5ff
    style SC fill:#fff4e1
    style S fill:#c8e6c9
    style DS fill:#c8e6c9
    style C fill:#ffe0b2
    style VR fill:#ffe0b2

Root ApplicationContext (root-context.xml)

  • 용도: 비즈니스 로직, 서비스, 데이터베이스 관련 Bean
  • 스캔 패키지: org.zerock.service
  • 주요 Bean:
    • HelloService (서비스 레이어)
    • hikariConfig (HikariCP 설정)
    • dataSource (데이터베이스 연결)
  • 생성 시점: 웹 애플리케이션 시작 시 (ContextLoaderListener)
  • 특징: 애플리케이션 전체에서 공유되는 Bean을 관리합니다.

Servlet ApplicationContext (servlet-context.xml)

  • 용도: 웹 관련 Bean (컨트롤러, 뷰 리졸버)
  • 스캔 패키지: org.zerock.controller
  • 주요 Bean:
    • HelloController (컨트롤러)
    • InternalResourceViewResolver (JSP 뷰 리졸버)
  • 생성 시점: DispatcherServlet 초기화 시
  • 특징: 웹 요청 처리에 필요한 Bean을 관리하며, Root Context를 부모로 참조할 수 있습니다.

2. 컨텍스트 계층 구조의 이점

graph LR
    A[Root Context] --> B[Servlet Context]
    B -.참조 가능.-> A
    
    style A fill:#e1f5ff
    style B fill:#fff4e1
  • Servlet ContextRoot Context의 Bean을 참조할 수 있습니다.
  • 이를 통해 컨트롤러에서 서비스 레이어를 주입받아 사용할 수 있습니다.
  • 역으로 Root Context는 Servlet Context의 Bean을 참조할 수 없어 계층 분리가 명확합니다.

요청 처리 흐름

Spring MVC의 요청 처리 과정을 단계별로 자세히 살펴보겠습니다.

전체 흐름도

sequenceDiagram
    participant B as 브라우저
    participant T as Tomcat 서버
    participant DS as DispatcherServlet
    participant HM as HandlerMapping
    participant HA as HandlerAdapter
    participant C as Controller
    participant VR as ViewResolver
    participant V as View (JSP)
    
    B->>T: HTTP GET /ex1
    T->>DS: 요청 전달
    DS->>HM: 핸들러 조회
    HM-->>DS: HelloController.ex1()
    DS->>HA: 핸들러 어댑터 조회
    HA->>C: ex1() 실행
    C-->>HA: "sample/ex1" 반환
    HA-->>DS: ModelAndView
    DS->>VR: 뷰 이름 전달
    VR-->>DS: /WEB-INF/views/sample/ex1.jsp
    DS->>V: JSP 렌더링
    V-->>DS: HTML 응답
    DS-->>T: 응답 전달
    T-->>B: HTTP Response

이 다이어그램은 실제 Spring MVC의 요청 처리 과정을 시간 순서대로 보여줍니다. 각 단계마다 어떤 컴포넌트가 관여하는지 명확하게 파악할 수 있습니다.

단계별 상세 설명

1단계: 요청 수신 (DispatcherServlet)

<!-- web.xml -->
<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>  <!-- 모든 요청을 DispatcherServlet이 처리 -->
</servlet-mapping>
  • 모든 HTTP 요청(/로 시작하는 모든 경로)이 DispatcherServlet으로 전달됩니다.
  • DispatcherServlet은 Spring MVC의 프론트 컨트롤러(Front Controller) 역할을 합니다.
  • 모든 요청을 중앙에서 받아 적절한 핸들러로 분배합니다.

2단계: 핸들러 매핑 (HandlerMapping)

@Controller
public class HelloController {
    @GetMapping("/ex1")  // URL 패턴 매핑
    public String ex1() { ... }
}
  • HandlerMapping이 요청 URL(/ex1)에 매핑된 컨트롤러 메서드를 찾습니다.
  • @GetMapping("/ex1") 어노테이션을 통해 URL과 메서드를 연결합니다.
  • servlet-context.xml<mvc:annotation-driven/>이 어노테이션 기반 매핑을 활성화합니다.

3단계: 핸들러 어댑터 (HandlerAdapter)

  • 찾아낸 핸들러(컨트롤러 메서드)를 실행하기 위한 어댑터를 선택합니다.
  • @Controller + @GetMapping 조합은 RequestMappingHandlerAdapter가 처리합니다.
  • 어댑터 패턴을 사용하여 다양한 종류의 핸들러를 일관된 방식으로 실행할 수 있습니다.

4단계: 컨트롤러 실행

@GetMapping("/ex1")
public String ex1() {
    log.info("========== /ex1 컨트롤러 호출됨 ==========");
    return "sample/ex1";  // 뷰 이름 반환
}
  • 실제 비즈니스 로직을 처리합니다.
  • 처리 결과와 함께 뷰 이름을 반환합니다.
  • 필요한 경우 Model에 데이터를 담아 뷰로 전달할 수 있습니다.

5단계: 뷰 리졸버 (ViewResolver)

<!-- servlet-context.xml -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp" />
</bean>
  • 반환된 뷰 이름 "sample/ex1"을 실제 JSP 파일 경로로 변환합니다:
    • Prefix: /WEB-INF/views/
    • 뷰 이름: sample/ex1
    • Suffix: .jsp
    • 최종 경로: /WEB-INF/views/sample/ex1.jsp

6단계: 뷰 렌더링

  • JSP 파일을 컴파일하고 동적 데이터를 삽입하여 최종 HTML을 생성합니다.
  • 생성된 HTML이 HTTP 응답 본문으로 브라우저에 전송됩니다.

설정 파일 분석

Spring MVC의 동작을 제어하는 주요 설정 파일들을 자세히 살펴보겠습니다.

1. web.xml - 웹 애플리케이션 배포 설명자

<!-- 루트 컨텍스트 설정 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring/root-context.xml</param-value>
</context-param>

<!-- DispatcherServlet 설정 -->
<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/servlet-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

주요 구성 요소:

  • ContextLoaderListener: 서버 시작 시 Root ApplicationContext를 생성합니다.
  • DispatcherServlet: 모든 HTTP 요청을 받아 처리하는 프론트 컨트롤러입니다.
  • load-on-startup=“1”: 서버 시작 시 즉시 서블릿을 초기화합니다.
  • url-pattern=”/”: 정적 파일을 제외한 모든 요청을 DispatcherServlet이 처리합니다.

2. servlet-context.xml - 웹 계층 설정

<!-- 어노테이션 기반 MVC 활성화 -->
<mvc:annotation-driven/>

<!-- 뷰 리졸버 설정 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp" />
</bean>

<!-- 컨트롤러 스캔 -->
<context:component-scan base-package="org.zerock.controller" />

역할:

  • mvc:annotation-driven: @Controller, @GetMapping 등의 어노테이션을 활성화합니다.
  • InternalResourceViewResolver: JSP 파일의 위치를 자동으로 조합하는 뷰 리졸버를 설정합니다.
  • component-scan: 지정된 패키지에서 @Controller 어노테이션이 붙은 클래스를 자동으로 Bean으로 등록합니다.

3. root-context.xml - 비즈니스 계층 설정

<!-- 서비스 레이어 스캔 -->
<context:component-scan base-package="org.zerock.service" />

<!-- 데이터베이스 연결 설정 -->
<bean name="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springdb?serverTimezone=Asia/Seoul" />
    ...
</bean>
<bean name="dataSource" class="com.zaxxer.hikari.HikariDataSource">
    <constructor-arg ref="hikariConfig" />
</bean>

역할:

  • 서비스 레이어 스캔: @Service 어노테이션이 붙은 클래스를 Bean으로 등록합니다.
  • 데이터베이스 연결 풀: HikariCP를 사용한 효율적인 DB 연결 관리를 설정합니다.

컨트롤러 엔드포인트 상세

각 엔드포인트의 동작 방식과 특징을 예제와 함께 살펴보겠습니다.

1. /ex1 - 명시적 뷰 이름 반환

@GetMapping("/ex1")
public String ex1() {
    log.info("========== /ex1 컨트롤러 호출됨 ==========");
    return "sample/ex1";  // 명시적 뷰 이름 반환
}

요청: GET http://localhost:8080/ex1

처리 흐름:

graph LR
    A[요청: /ex1] --> B[DispatcherServlet]
    B --> C[@GetMapping 매핑]
    C --> D[ex1 메서드 실행]
    D --> E[뷰 이름 반환: sample/ex1]
    E --> F[ViewResolver]
    F --> G[JSP: /WEB-INF/views/sample/ex1.jsp]
    G --> H[HTML 응답]
    
    style A fill:#e1f5ff
    style H fill:#c8e6c9

특징:

  • 가장 명확하고 권장되는 방식입니다.
  • 반환할 뷰의 이름을 명시적으로 지정합니다.
  • 뷰 이름과 URL이 달라도 상관없습니다.

2. /ex2 - 암시적 뷰 이름 반환

@GetMapping("/ex2")
public void ex2() {
    log.info("sample/ex2");
    // 반환값이 없음 (void)
}

요청: GET http://localhost:8080/ex2

처리 과정:

  1. 메서드가 void를 반환하면 Spring은 요청 URL을 기반으로 뷰 이름을 추론합니다.
  2. URL: /ex2 → 뷰 이름: ex2
  3. ViewResolver가 /WEB-INF/views/ex2.jsp로 변환합니다.
  4. 주의: 해당 경로에 JSP 파일이 반드시 존재해야 합니다.

특징:

  • URL과 뷰 이름이 1:1로 매칭될 때 간결하게 사용할 수 있습니다.
  • URL이 변경되면 뷰 파일 위치도 함께 변경해야 하므로 유연성이 떨어집니다.

3. /ex3re - 일반 뷰 반환

@GetMapping("/ex3re")
public String ex3Re() {
    return "sample/ex3Result";
}

요청: GET http://localhost:8080/ex3re

특징:

  • URL(/ex3re)과 뷰 이름(sample/ex3Result)이 다른 경우에 사용합니다.
  • 하나의 컨트롤러에서 여러 뷰를 선택적으로 반환할 수 있습니다.

4. /ex4 - 요청 파라미터 바인딩

@GetMapping("/ex4")
public void ex4(@RequestParam(name="n1", defaultValue = "1") int num,
                @RequestParam("name") String name) {
    log.info("num :" + num);
    log.info("name : " + name);
}

요청 예시:

  • GET http://localhost:8080/ex4?n1=10&name=홍길동
  • GET http://localhost:8080/ex4?name=홍길동 (n1 생략 시 기본값 1 사용)

파라미터 바인딩 과정:

graph LR
    A[URL: /ex4?n1=10&name=홍길동] --> B[@RequestParam]
    B --> C[타입 변환]
    C --> D1[num: int = 10]
    C --> D2[name: String = 홍길동]
    D1 --> E[메서드 실행]
    D2 --> E
    
    style A fill:#e1f5ff
    style E fill:#c8e6c9

@ RequestParam 속성:

  • name: URL 파라미터 이름을 지정합니다.
  • defaultValue: 파라미터가 없을 때 사용할 기본값을 지정합니다.
  • required: 파라미터 필수 여부 (기본값: true)

파라미터 바인딩 규칙:

  • @RequestParam("name") (기본): 필수 파라미터 (없으면 400 Bad Request)
  • @RequestParam(required=false): 선택적 파라미터 (없으면 null)
  • @RequestParam(defaultValue = "1"): 선택적 파라미터 (없으면 기본값 사용)

5. /ex5 - 객체 바인딩 (DTO)

@GetMapping("/ex5")
public void ex5(SampleDTO dto) {
    log.info(dto);
}

SampleDTO 클래스:

@Setter
@ToString
public class SampleDTO {
    private String name;
    private int age;
}

요청 예시:

  • GET http://localhost:8080/ex5?name=홍길동&age=25

객체 바인딩 과정:

graph TD
    A[URL 파라미터<br/>name=홍길동&age=25] --> B[Spring MVC]
    B --> C{필드 이름 매칭}
    C -->|name| D[dto.setName 호출]
    C -->|age| E[dto.setAge 호출<br/>String→int 변환]
    D --> F[SampleDTO 객체 생성]
    E --> F
    F --> G[메서드에 전달]
    
    style A fill:#e1f5ff
    style G fill:#c8e6c9

자동 바인딩 규칙:

  • HTTP 파라미터 이름과 DTO 필드 이름이 일치하면 자동으로 매핑됩니다.
  • Spring이 타입 변환을 자동으로 처리합니다 (String → int, String → Date 등).
  • @RequestParam 없이도 객체로 바인딩 가능합니다.
  • Lombok@Setter가 setter 메서드를 자동 생성하여 바인딩을 가능하게 합니다.
  • @ToString은 로그 출력 시 객체 내용을 문자열로 변환합니다.

객체 바인딩의 장점:

  • 여러 파라미터를 하나의 객체로 깔끔하게 관리할 수 있습니다.
  • 파라미터가 많아질수록 코드가 간결해집니다.
  • 유효성 검증(@Valid)을 객체 단위로 적용할 수 있습니다.

데이터 바인딩

Spring MVC의 다양한 데이터 바인딩 방식을 비교해보겠습니다.

바인딩 방식 비교

graph TB
    subgraph "1. 단순 파라미터 바인딩"
        A1[URL: ?name=홍길동] --> B1[@RequestParam String name]
        B1 --> C1[메서드 파라미터로 바로 사용]
    end
    
    subgraph "2. 객체 바인딩"
        A2[URL: ?name=홍길동&age=25] --> B2[SampleDTO dto]
        B2 --> C2[dto.name = 홍길동<br/>dto.age = 25]
    end
    
    subgraph "3. 기본값 설정"
        A3[URL: /ex4 파라미터 없음] --> B3[@RequestParam defaultValue=1]
        B3 --> C3[num = 1 기본값 사용]
    end
    
    style C1 fill:#c8e6c9
    style C2 fill:#c8e6c9
    style C3 fill:#c8e6c9

1. 단순 파라미터 바인딩

@GetMapping("/ex4")
public void ex4(@RequestParam("name") String name) { ... }

사용 시기: 파라미터가 1~2개로 적을 때

2. 객체 바인딩

@GetMapping("/ex5")
public void ex5(SampleDTO dto) { ... }

사용 시기: 파라미터가 3개 이상이거나, 관련된 데이터를 그룹화할 때

3. 기본값 설정

@RequestParam(name="n1", defaultValue = "1") int num

사용 시기: 선택적 파라미터에 기본값을 제공할 때


전체 흐름 요약

애플리케이션 시작 시

graph TD
    A[Tomcat 서버 시작] --> B[ContextLoaderListener 실행]
    B --> C[root-context.xml 로드]
    C --> D[Root ApplicationContext 생성]
    D --> E[HelloService Bean 등록]
    D --> F[DataSource Bean 등록]
    
    B --> G[DispatcherServlet 초기화]
    G --> H[servlet-context.xml 로드]
    H --> I[Servlet ApplicationContext 생성]
    I --> J[HelloController Bean 등록]
    I --> K[ViewResolver Bean 등록]
    
    I -.부모 참조.-> D
    
    style A fill:#e1f5ff
    style D fill:#fff4e1
    style I fill:#ffe0b2

요청 처리 시

  1. 요청 수신: GET /ex1
  2. DispatcherServlet: 요청 처리 시작
  3. HandlerMapping: @GetMapping("/ex1") 찾기
  4. HandlerAdapter: HelloController.ex1() 실행
  5. Controller: 비즈니스 로직 처리, 뷰 이름 반환
  6. ViewResolver: 뷰 이름 → JSP 경로 변환
  7. View: JSP 렌더링
  8. 응답: HTML 반환

주요 어노테이션 설명

어노테이션용도위치설명
@Controller컨트롤러 클래스 표시클래스웹 요청을 처리하는 컴포넌트
@GetMappingGET 요청 매핑메서드HTTP GET 요청과 메서드 연결
@RequestParam요청 파라미터 바인딩메서드 파라미터URL 파라미터를 메서드 인자로 매핑
@Service서비스 레이어 표시클래스비즈니스 로직 컴포넌트
@SetterSetter 메서드 자동 생성클래스/필드Lombok 어노테이션
@ToStringtoString() 메서드 자동 생성클래스Lombok 어노테이션

체크리스트

✅ 설정 확인 사항

  • web.xml에 DispatcherServlet 설정
  • servlet-context.xml<mvc:annotation-driven/> 설정
  • servlet-context.xml에 ViewResolver 설정
  • servlet-context.xml에 컨트롤러 패키지 스캔 설정
  • root-context.xml에 서비스 패키지 스캔 설정
  • 컨트롤러에 @Controller 어노테이션
  • 메서드에 @GetMapping 어노테이션
  • JSP 파일이 /WEB-INF/views/ 경로에 존재

⚠️ 주의사항

[!IMPORTANT] 뷰 이름 반환 방식

  • String 반환: 명시적 뷰 이름 (권장)
  • void 반환: URL 기반 암시적 뷰 이름 (URL과 뷰 이름이 일치할 때만 사용)

[!TIP] 파라미터 바인딩 권장사항

  • 파라미터가 1~2개: @RequestParam 사용
  • 파라미터가 3개 이상: DTO 객체 바인딩 사용
  • 타입 변환은 Spring이 자동 처리하므로 신경 쓰지 않아도 됩니다.

[!NOTE] 패키지 구조

  • 컨트롤러: org.zerock.controller
  • 서비스: org.zerock.service
  • DTO: org.zerock.dto

참고 자료