Dev Study/Backend

[Java & SpringBoot] Spring Boot Auto-configuration — web.xml이 사라지고 빈 160개 이상 빈 자동 등록

parkhh98 2026. 5. 24. 09:24

설정 파일이 세 개였던 시절

Spring MVC로 웹 애플리케이션을 만들려면 최소 세 개의 설정 파일이 필요했다. web.xml, root-context.xml, servlet-context.xml. 각각 서블릿 등록, 빈 스캔, ViewResolver 설정을 담당했다. 프로젝트를 시작할 때마다 이 세 파일을 손으로 작성해야 했고, 빠뜨린 설정 하나가 런타임 오류로 이어졌다.

Spring Boot는 이 반복을 없앴다. @SpringBootApplication 어노테이션 하나와 main() 메서드 하나로 서버가 뜬다. 설정 파일은 application.properties 하나만 남는데, 그마저도 ViewResolver 경로만 직접 쓰면 된다.

Insight: Spring Boot의 핵심은 "합리적인 기본값(Convention over Configuration)"이다. 모든 프로젝트가 공통으로 필요한 설정은 자동으로 처리하고, 다른 것만 명시하게 한다.

Bad Code: 반복되던 설정들

Spring MVC 시절 web.xml에는 이런 내용이 반드시 들어갔다.

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

<!-- 인코딩 필터도 직접 등록 -->
<filter>
  <filter-name>encodingFilter</filter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
</filter>

servlet-context.xml에는 ViewResolver, 컴포넌트 스캔, 리소스 핸들러를 또 작성했다. 프로젝트마다 똑같은 내용이 반복됐다.

<!-- servlet-context.xml -->
<context:component-scan base-package="com.example.mvc" />

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/views/" />
  <property name="suffix" value=".jsp" />
</bean>

<mvc:resources mapping="/resources/**" location="/resources/" />
Insight: 모든 Spring MVC 프로젝트가 이 설정을 반복했다. 다른 건 패키지명 정도뿐이었다. 이 반복이 Spring Boot 탄생의 이유다.

Spring Boot: 설정 파일이 하나로

Spring Boot 프로젝트의 설정 전부다.

# application.properties
spring.application.name=MyApp

# JSP ViewResolver (이것만 직접 설정)
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

DispatcherServlet 등록, 인코딩 필터, 컴포넌트 스캔, 리소스 핸들러는 Spring Boot가 자동으로 처리한다. ViewResolver prefix/suffix만 프로젝트마다 다르니까 이것만 명시한다.

진입점도 단순하다.

@SpringBootApplication
public class AppMain {
    public static void main(String[] args) {
        SpringApplication.run(AppMain.class, args);
    }
}

@SpringBootApplication은 세 어노테이션의 합성이다.

포함된 어노테이션 역할
@SpringBootConfiguration 이 클래스가 설정 클래스임을 선언 (@Configuration과 동일)
@EnableAutoConfiguration classpath의 의존성을 보고 필요한 빈 자동 등록
@ComponentScan 이 클래스 위치 기준 하위 패키지 전체 스캔

 

Insight: @EnableAutoConfiguration이 핵심이다. spring-boot-starter-web 의존성이 있으면 웹 관련 빈을, Jackson이 있으면 JSON 변환 빈을 자동으로 올린다. 의존성이 곧 설정이다.

160개의 빈 — 자동 등록의 실체

Spring Boot 앱을 실행하고 등록된 빈 목록을 출력해봤다.

@SpringBootApplication
public class AppMain {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(AppMain.class, args);

        for (String bean : context.getBeanDefinitionNames())
            System.out.println(bean);
    }
}

결과는 160여 개. 그 중 개발자가 직접 만든 건 두 개뿐이었다.

helloController       // 내가 만든 것
userController        // 내가 만든 것
// 나머지 158개는 Spring Boot 자동 등록

자동 등록된 빈들이 무엇을 처리하는지 분류하면 이렇다.

빈 이름 역할
dispatcherServlet 요청 라우팅 (web.xml에 직접 등록하던 것)
tomcatServletWebServerFactory 내장 Tomcat 서버
viewResolver, defaultViewResolver 뷰 이름 → JSP 경로 변환
jacksonObjectMapper Java 객체 ↔ JSON 변환
multipartResolver 파일 업로드 처리
characterEncodingFilter UTF-8 인코딩 필터
requestMappingHandlerMapping @GetMapping, @PostMapping URL 매핑
basicErrorController 에러 페이지 처리
liveReloadServer devtools 핫리로드

 

Spring MVC 시절에는 이것들을 하나씩 XML에 등록하거나 JavaConfig로 구현했다. Spring Boot 이전 개발자들이 Spring Boot를 처음 봤을 때 충격받은 이유가 여기 있다. main() 하나로 서버가 뜨는 게 말이 안 됐던 것이다.

Insight: 자동 등록된 빈들은 숨겨진 게 아니다. 언제든 getBeanDefinitionNames()로 확인할 수 있고, application.properties로 오버라이드할 수 있다. Spring Boot는 설정을 없앤 게 아니라 기본값을 제공한 것이다.

JSP를 쓰려면 의존성이 더 필요한 이유

Spring Boot 내장 Tomcat은 기본적으로 JSP를 지원하지 않는다. REST API가 주류가 되면서 JSP 렌더링 기능을 기본에서 제외했기 때문이다. JSP를 사용하려면 pom.xml에 세 가지를 추가해야 한다.

<!-- JSP 컴파일러 (Jasper) -->
<dependency>
  <groupId>org.apache.tomcat.embed</groupId>
  <artifactId>tomcat-embed-jasper</artifactId>
</dependency>

<!-- JSTL 인터페이스 -->
<dependency>
  <groupId>jakarta.servlet.jsp.jstl</groupId>
  <artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
</dependency>

<!-- JSTL 구현체 (GlassFish) -->
<dependency>
  <groupId>org.glassfish.web</groupId>
  <artifactId>jakarta.servlet.jsp.jstl</artifactId>
</dependency>
의존성 역할
tomcat-embed-jasper .jsp 파일을 서블릿으로 컴파일하는 Jasper 엔진
jstl-api JSTL 태그 인터페이스 정의 (<c:if>, <c:forEach> 등)
jstl (GlassFish) JSTL 실제 구현체

 

Thymeleaf를 사용하면 이 세 가지가 필요 없다. spring-boot-starter-thymeleaf 하나로 끝난다. 현업에서 신규 프로젝트에 JSP보다 Thymeleaf나 React/Vue를 선호하는 이유 중 하나다.

Insight: JSTL은 API와 구현체가 분리되어 있다. 인터페이스를 정의한 스펙과 그것을 실제로 동작하게 만든 구현체가 별개 라이브러리다. Jakarta EE 생태계의 전반적인 설계 방식이기도 하다.

세션과 @ModelAttribute — 로그인 흐름 구현

Spring Boot 컨트롤러 코드는 Spring MVC와 완전히 동일하다. 바뀐 건 설정뿐이다. 로그인 처리 코드를 통해 두 가지 개념을 짚는다.

@Controller
public class AccountController {

    @GetMapping("/login")
    public String loginForm() {
        return "/account/loginForm";
    }

    @PostMapping("/login")
    public String login(@ModelAttribute Member member, HttpSession session) {
        session.setAttribute("loginMember", member.getId());
        return "redirect:/home";
    }

    @GetMapping("/logout")
    public String logout(HttpSession session) {
        session.invalidate();  // 세션 전체 초기화
        return "redirect:/";
    }
}

@ModelAttribute Member member — 빈 컨테이너에서 꺼내는 게 아니다. POST 요청의 폼 데이터를 객체에 바인딩한다. id=hong&pw=1234가 넘어오면 Spring이 new Member()를 만들고 setId("hong"), setPw("1234")를 호출한다. 파라미터 이름과 setter 이름이 일치하면 된다.

HttpSession session — 직접 생성하지 않아도 된다. Spring이 현재 요청의 세션을 파라미터에 자동 주입한다. 세션은 Map<String, Object>처럼 동작한다.

session.setAttribute("loginMember", "hong");  // Map.put
session.getAttribute("loginMember");           // Map.get → "hong"
session.invalidate();                          // Map 전체 비우기

"loginMember"는 Java 어딘가 정의된 필드가 아니라 개발자가 정한 key 문자열이다. JSP에서 ${loginMember}로 꺼낼 수 있다.

return "redirect:/home" — 뷰 이름이 아니라 리다이렉트다. ViewResolver를 거치지 않고 브라우저에게 해당 URL로 다시 GET 요청하라는 응답을 보낸다. 로그인 POST 처리 후 새로고침하면 폼이 중복 제출되는 문제를 막기 위해 사용한다 (PRG 패턴).

Insight: HTTP는 요청/응답이 끝나면 상태를 기억하지 않는다. 로그인 상태 유지는 서버가 세션 Map을 관리하고, 브라우저는 JSESSIONID 쿠키만 들고 다니는 방식으로 구현된다.

Q. Spring Boot에서 Tomcat을 별도로 설치해야 하나?

필요 없다. spring-boot-starter-web이 내장 Tomcat을 포함한다. ./mvnw spring-boot:run 하면 Tomcat이 자동으로 뜬다. 배포 시에도 mvnw package로 만든 JAR 파일에 Tomcat이 내장되어 있어서 java -jar app.jar로 실행된다.

 

Q. application.properties 말고 YAML 파일로 설정할 수도 있나?

가능하다. application.yml로 작성해도 된다. 계층 구조가 있는 설정은 YAML이 가독성이 좋아서 현업에서도 많이 쓴다. spring.mvc.view.prefix는 YAML에서 spring: mvc: view: prefix: 형태가 된다. 둘 다 동시에 있으면 properties가 우선한다.

 

Q. 자동 등록된 빈의 기본 설정을 바꾸고 싶으면?

두 가지 방법이 있다. application.properties에 해당 속성을 명시하면 자동 설정이 그 값으로 오버라이드된다. 더 세밀한 제어가 필요하면 @Configuration 클래스에서 해당 빈을 직접 정의하면 자동 등록 대신 그 빈이 사용된다.


Spring Boot는 설정을 없앤 게 아니라 반복되는 기본값을 대신 써준다. 160개의 자동 빈은 숨겨진 마법이 아니라 언제든 확인하고 오버라이드할 수 있는 기본 설정이다. 컨트롤러 코드는 Spring MVC와 동일하고, 달라진 건 설정 파일 세 개가 application.properties 다섯 줄로 줄었다는 것뿐이다. Spring Boot를 쓴다는 건 선택지가 줄어드는 게 아니라 시작점이 달라지는 것이다.


Environment: Windows 11, JDK 17, Spring Boot 3.4.4, VS Code