XML 공포증 해소 — 라인별로 읽기
Spring Legacy Project 를 만들면 자동 생성되는 파일들:
web.xml — 30 줄root-context.xml — 비어 있는데 namespace 가 5 줄servlet-context.xml — 30 줄, 어노테이션·xmlns·xsi 가득"태그가 너무 많고, 같은 prefix 가 반복되고, 어디부터 봐야 할지 모르겠어요..."
3 파일이 비슷해 보이지만 명확히 다른 역할이 있습니다. 한 번 외우면 다시 헷갈리지 않습니다.
위치: src/main/webapp/WEB-INF/web.xml
책임: 톰캣(WAS)이 처음 우리 애플리케이션을 시작할 때 읽는 파일. 「입구」를 정의.
아래 세 블록이 들어갑니다 — Spring 컨테이너 시작 / DispatcherServlet 등록 / 한글 깨짐 필터.
① 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>
② 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>
③ 한글 깨짐 필터
<!-- ③ 한글 깨짐 필터 -->
<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>
| 블록 | 역할 |
|---|---|
<context-param> + <listener> | Spring 「Root 컨테이너」 시작. root-context.xml 을 읽음 |
<servlet> | DispatcherServlet 등록. servlet-context.xml 을 읽음 |
<servlet-mapping> | 모든 URL(/) 을 DispatcherServlet 으로 보냄 |
<filter> + <filter-mapping> | 요청 전처리 (한글 인코딩 등) |
👉 web.xml 을 한 번 작성하면 거의 안 건드림. encoding 필터만 자주 등장.
위치: src/main/webapp/WEB-INF/spring/root-context.xml
책임: 웹과 무관한 전역 Bean 등록. DataSource·Service·Mapper 같은 백엔드 부품.
크게 다섯 블록 — Service 스캔 / DB 커넥션 풀 / SqlSessionFactory / Mapper 스캔 / 트랜잭션.
<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>
<!-- ③ 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>
| 블록 | 역할 |
|---|---|
| component-scan service | @Service 가 붙은 클래스 자동 등록 |
| DataSource | DB 연결 정보 + 커넥션 풀 |
| SqlSessionFactory | MyBatis 의 핵심 — SQL 실행자 |
| MapperScannerConfigurer | @Mapper 인터페이스 자동 등록 |
| transactionManager + tx:annotation-driven | @Transactional 활성화 |
👉 「DB 5 단계 지도」 차시의 ②번이 바로 이 파일.
위치: src/main/webapp/WEB-INF/spring/appServlet/servlet-context.xml
책임: 웹 관련 Bean. Controller·ViewResolver·인터셉터 등.
다섯 블록 — MVC 활성화 / 정적 자원 / ViewResolver / Controller 스캔 / 인터셉터.
<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>
<!-- ④ 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>
| 블록 | 역할 |
|---|---|
| annotation-driven | @Controller·@RequestMapping 활성화 |
| resources | /resources/** URL 은 정적 파일로 직접 응답 |
| ViewResolver | "home" → /WEB-INF/views/home.jsp 자동 변환 |
| component-scan controller | @Controller 자동 등록 |
| interceptors | 로그인 가드 등 공통 처리 |
같은 component-scan 인데 왜 두 파일에 따로?
계층 구조:
Root 컨테이너 (root-context.xml)
│ ▲ Servlet 컨테이너가 Root 의 Bean 사용 가능
│ │ Root 는 Servlet 의 Bean 사용 불가
Servlet 컨테이너 (servlet-context.xml)
→ Service 는 Root 에 → 어디서든 사용. Controller 는 Servlet 에 → 웹에서만.
Spring Legacy 는 「설정 모든 것을 명시」 하는 철학:
Spring Boot 는 이 모든 것을 자동화. 본 과정에서 Legacy 를 보는 이유는 — 자동화 뒤에 무엇이 숨어있는지 한 번은 봐두기.
<!-- 방식 ① 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 |
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" |
👉 일부러 한 번씩 깨뜨려보면 「설정 한 줄이 어떤 동작을 책임지는지」 가 머리에 박힙니다.
3 개 XML 파일에 명시적 설정
초기 설정 부담 ↑
「뭐가 어디서 결정되는지」 다 보임
XML 거의 없음 (application.properties 만)
의존성만 추가하면 자동 설정
@SpringBootApplication 한 줄
👉 Boot 는 이 차시의 모든 XML 을 「자동」 으로. Legacy 를 한 번 봐두면 Boot 가 무엇을 자동화하는지 보임.
web.xml, root-context.xml, servlet-context.xml — 같이 보면 어지러움. 어디부터 봐야 할지 모름.
3 파일의 책임이 명확. 라인별로 짚을 수 있다. 설정 오류 시 어느 파일을 봐야 할지 안다.
다음: 한글 깨짐 필터 — 학생이 가장 먼저 만나는 「??? 글자」.