◆ TURNING · MVC

설정 파일 3종 해부

XML 공포증 해소 — 라인별로 읽기

학습 목표

  • 3 가지 XML 설정 파일의 서로 다른 책임 을 안다
  • 각 파일을 라인별로 짚을 수 있다
  • 「설정에 빨간 줄」 같은 오류를 만나면 어디를 봐야 하는지 안다
  • XML 이 더 이상 무섭지 않다

⚠️ XML 의 첫인상

처음 본 사람의 반응

Spring Legacy Project 를 만들면 자동 생성되는 파일들:

  • web.xml — 30 줄
  • root-context.xml — 비어 있는데 namespace 가 5 줄
  • servlet-context.xml — 30 줄, 어노테이션·xmlns·xsi 가득

"태그가 너무 많고, 같은 prefix 가 반복되고, 어디부터 봐야 할지 모르겠어요..."

🛠️ 3 가지 파일의 분업

각자 다른 책임

3 파일이 비슷해 보이지만 명확히 다른 역할이 있습니다. 한 번 외우면 다시 헷갈리지 않습니다.

┌───────────────────────────────────────────────┐ │ web.xml │ 애플리케이션의 「관문」 │ │ │ DispatcherServlet 등록 │ ├─────────────────────┼─────────────────────────┤ │ root-context.xml │ 백엔드 「설계도」 │ │ │ DB·Service 등 전역 Bean │ ├─────────────────────┼─────────────────────────┤ │ servlet-context.xml │ 웹 「설계도」 │ │ │ Controller·View 관련 │ └───────────────────────────────────────────────┘

① web.xml — 관문

위치: src/main/webapp/WEB-INF/web.xml

책임: 톰캣(WAS)이 처음 우리 애플리케이션을 시작할 때 읽는 파일. 「입구」를 정의.

아래 세 블록이 들어갑니다 — Spring 컨테이너 시작 / DispatcherServlet 등록 / 한글 깨짐 필터.

① web.xml — 코드 (1/3)

① Spring Root 컨테이너 시작


<web-app xmlns="...">

    <!-- ① Spring 컨테이너 시작 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

① web.xml — 코드 (2/3)

② DispatcherServlet 등록 + URL 매핑


    <!-- ② 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/appServlet/servlet-context.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

① web.xml — 코드 (3/3)

③ 한글 깨짐 필터


    <!-- ③ 한글 깨짐 필터 -->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>
            org.springframework.web.filter.CharacterEncodingFilter
        </filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

① web.xml 의 핵심 3 가지

블록역할
<context-param> + <listener>Spring 「Root 컨테이너」 시작. root-context.xml 을 읽음
<servlet>DispatcherServlet 등록. servlet-context.xml 을 읽음
<servlet-mapping>모든 URL(/) 을 DispatcherServlet 으로 보냄
<filter> + <filter-mapping>요청 전처리 (한글 인코딩 등)

👉 web.xml 을 한 번 작성하면 거의 안 건드림. encoding 필터만 자주 등장.

② root-context.xml — 백엔드 설계도

위치: src/main/webapp/WEB-INF/spring/root-context.xml

책임: 웹과 무관한 전역 Bean 등록. DataSource·Service·Mapper 같은 백엔드 부품.

크게 다섯 블록 — Service 스캔 / DB 커넥션 풀 / SqlSessionFactory / Mapper 스캔 / 트랜잭션.

② root-context.xml — 코드 (1/2)


<beans xmlns="..." xmlns:context="...">

    <!-- ① Service / DAO 자동 스캔 -->
    <context:component-scan base-package="com.example.demo.service" />

    <!-- ② DB 커넥션 풀 -->
    <bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db" />
        <property name="username" value="root" />
        <property name="password" value="1234" />
    </bean>
    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
          destroy-method="close">
        <constructor-arg ref="hikariConfig" />
    </bean>

② root-context.xml — 코드 (2/2)


    <!-- ③ MyBatis SqlSessionFactory -->
    <bean id="sqlSessionFactory"
          class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <property name="mapperLocations" value="classpath:mappers/*Mapper.xml" />
    </bean>

    <!-- ④ Mapper 자동 스캔 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.example.demo.mapper" />
    </bean>

    <!-- ⑤ 트랜잭션 -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <tx:annotation-driven />

</beans>

② root-context.xml 5 블록

블록역할
component-scan service@Service 가 붙은 클래스 자동 등록
DataSourceDB 연결 정보 + 커넥션 풀
SqlSessionFactoryMyBatis 의 핵심 — SQL 실행자
MapperScannerConfigurer@Mapper 인터페이스 자동 등록
transactionManager + tx:annotation-driven@Transactional 활성화

👉 「DB 5 단계 지도」 차시의 ②번이 바로 이 파일.

③ servlet-context.xml — 웹 설계도

위치: src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml

책임: 웹 관련 Bean. Controller·ViewResolver·인터셉터 등.

다섯 블록 — MVC 활성화 / 정적 자원 / ViewResolver / Controller 스캔 / 인터셉터.

③ servlet-context.xml — 코드 (1/2)


<beans:beans xmlns="http://www.springframework.org/schema/mvc"
              xmlns:beans="http://www.springframework.org/schema/beans"
              xmlns:context="...">

    <!-- ① Spring MVC 활성화 -->
    <annotation-driven />

    <!-- ② 정적 자원 (JS, CSS, 이미지) 처리 -->
    <resources mapping="/resources/**" location="/resources/" />

    <!-- ③ ViewResolver — JSP 경로 조립 -->
    <beans:bean id="viewResolver"
                 class="...InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>

③ servlet-context.xml — 코드 (2/2)


    <!-- ④ Controller 자동 스캔 -->
    <context:component-scan base-package="com.example.demo.controller" />

    <!-- ⑤ 인터셉터 등록 (v4 에서 추가) -->
    <interceptors>
        <interceptor>
            <mapping path="/board/write"/>
            <mapping path="/mypage/**"/>
            <exclude-mapping path="/login"/>
            <beans:bean class="com.example.demo.interceptor.LoginInterceptor"/>
        </interceptor>
    </interceptors>

</beans:beans>

③ servlet-context.xml 5 블록

블록역할
annotation-driven@Controller·@RequestMapping 활성화
resources/resources/** URL 은 정적 파일로 직접 응답
ViewResolver"home" → /WEB-INF/views/home.jsp 자동 변환
component-scan controller@Controller 자동 등록
interceptors로그인 가드 등 공통 처리

왜 root 와 servlet 으로 나누나?

같은 component-scan 인데 왜 두 파일에 따로?

  • Root 컨테이너: 웹과 무관한 「공통」 Bean — Service·DAO·DataSource
  • Servlet 컨테이너: 웹에 특화된 Bean — Controller·ViewResolver·인터셉터

계층 구조:


   Root 컨테이너 (root-context.xml)
       │ ▲ Servlet 컨테이너가 Root 의 Bean 사용 가능
       │ │ Root 는 Servlet 의 Bean 사용 불가
   Servlet 컨테이너 (servlet-context.xml)

→ Service 는 Root 에 → 어디서든 사용. Controller 는 Servlet 에 → 웹에서만.

왜 이렇게 복잡한가

Spring Legacy 는 「설정 모든 것을 명시」 하는 철학:

  • 모든 Bean 을 직접 등록 (또는 component-scan)
  • 모든 부품의 의존 관계를 XML 에
  • 장점: 「뭐가 어디서 결정되는지」 다 보임
  • 단점: 처음엔 부담

Spring Boot 는 이 모든 것을 자동화. 본 과정에서 Legacy 를 보는 이유는 — 자동화 뒤에 무엇이 숨어있는지 한 번은 봐두기.

component-scan vs <bean>


<!-- 방식 ① component-scan (현대) -->
<context:component-scan base-package="com.example.demo.service" />

@Service
public class BoardService { ... }   // 자동 등록

<!-- 방식 ② <bean> 직접 (옛 방식) -->
<bean id="boardService"
      class="com.example.demo.service.BoardService" />

👉 component-scan 이 훨씬 간결. 본 과정은 component-scan 사용.
<bean> 은 외부 라이브러리의 클래스를 등록할 때 (예: HikariDataSource).

자주 만나는 오류

증상의심할 파일
"No qualifying bean of type 'XService'"root-context.xml component-scan 패키지 / @Service 누락
"No qualifying bean of type 'YController'"servlet-context.xml component-scan 패키지
404 + 정적 파일 안 보임servlet-context.xml resources 매핑
"Could not resolve view"ViewResolver prefix/suffix
한글 깨짐web.xml CharacterEncodingFilter
DB 연결 실패root-context.xml DataSource
@Transactional 무시root-context.xml tx:annotation-driven

네임스페이스 (xmlns) 정리

XML 의 xmlns:xx="..." 속성들 — 길고 무서워 보이지만 사실 「태그 그룹의 별명」 입니다.


<beans xmlns="...beans"
       xmlns:context="...context"
       xmlns:tx="...tx"
       xmlns:mvc="...mvc">

    <bean ... />                  ← 기본 (beans)
    <context:component-scan ... />  ← context 그룹의 태그
    <tx:annotation-driven />        ← tx 그룹의 태그
    <mvc:interceptors> ... </mvc:interceptors>
</beans>

→ 각 그룹마다 다른 태그를 제공. 사용 안 하면 namespace 도 안 적어도 됨.

실험 — 직접 깨뜨려보기

실험결과
component-scan base-package 오타해당 패키지 Bean 못 찾음
ViewResolver prefix 변경JSP 경로 다른 곳으로
CharacterEncodingFilter 비활성한글 깨짐
tx:annotation-driven 제거@Transactional 동작 안 함
DataSource 의 password 오타"Cannot create connection"

👉 일부러 한 번씩 깨뜨려보면 「설정 한 줄이 어떤 동작을 책임지는지」 가 머리에 박힙니다.

Spring Boot 와의 비교

Spring Legacy (이 과정)

3 개 XML 파일에 명시적 설정

초기 설정 부담 ↑

「뭐가 어디서 결정되는지」 다 보임

Spring Boot (후속)

XML 거의 없음 (application.properties 만)

의존성만 추가하면 자동 설정

@SpringBootApplication 한 줄

👉 Boot 는 이 차시의 모든 XML 을 「자동」 으로. Legacy 를 한 번 봐두면 Boot 가 무엇을 자동화하는지 보임.

읽는 순서 — 디버깅 시

"왜 안 되지?" 라는 의심이 들 때 ① web.xml 부터 - DispatcherServlet 등록 OK? - encoding 필터 있나? - servlet-mapping url-pattern 이 / 인가? ② root-context.xml - component-scan 패키지가 맞나? - DataSource 정보가 맞나? - SqlSessionFactory 의 mapperLocations 가 맞나? ③ servlet-context.xml - Controller 패키지 component-scan? - ViewResolver prefix/suffix? - 인터셉터 매핑이 너무 넓나? 대부분 ② 또는 ③ 에서 끝남

🔄 Before / After

전 차시 끝

web.xml, root-context.xml, servlet-context.xml — 같이 보면 어지러움. 어디부터 봐야 할지 모름.

이번 차시 끝

3 파일의 책임이 명확. 라인별로 짚을 수 있다. 설정 오류 시 어느 파일을 봐야 할지 안다.

이번 차시의 데이터 흐름

web.xml
관문
root-context.xml
백엔드
servlet-context.xml
Spring 컨테이너 가동
3 설정 파일이 단계적으로 컨테이너를 채워나감

정리

오늘 들고 가는 것

  • web.xml = 관문 (DispatcherServlet 등록)
  • root-context.xml = 백엔드 설계도 (DB·Service)
  • servlet-context.xml = 웹 설계도 (Controller·ViewResolver)
  • 두 컨테이너의 계층 (Root ⊃ Servlet)
  • 설정 오류 시 진단 순서: web.xml → root → servlet
  • Boot 는 이 모든 것을 자동화 — 그래서 Legacy 한 번 봐두기

다음: 한글 깨짐 필터 — 학생이 가장 먼저 만나는 「??? 글자」.