Spring/Spring Boot

[Spring Boot] 2-1. Core Features: SpringApplication 클래스

noahkim_ 2023. 10. 8. 02:15

0. SpringApplication

  • SpringApplication 클래스는 Spring application을 편리하게 기동하는 방법을 제공하는 클래스입니다.
  • SpringApplication.run 메서드가 main method로부터 호출되어 bootstrapping 작업을 시작합니다.
    • startup과 연관된 메시지가 출력됩니다. (INFO 레벨의 로그)

 

1. Startup Failure

FaliureAnalyzers

  • pring Boot 기동중 에러 발생 시 문제 해결을 담당합니다.
  • 여러 에러에 대해 다양한 구현체를 가지고 있습니다.
  • 에러 메시지를 출력합니다. 

 

ConditionEvaluationReport 

  • 어떤 자동 설정이 적용되었고 제외되었는지 그리고 그 이유가 무엇인지를 DEBUG 레벨 로그로 출력해주는 객체
  • 애플리케이션이 실행되지 않았는데도 FailureAnalyzer에 의해 명확한 에러 메시지를 받지 못한 경우, 시스템 진단용으로 적합합니다.
활성화 방법 설정
application.properties debug=true
JVM 옵션 --debug 또는 -Ddebug=true
Gradle 실행 ./gradlew bootRun --debug
IDEA에서 실행 VM 옵션에 --debug 추가

 

2. Lazy Initialization

  • 빈이 애플리케이션 시작 시점이 아닌 필요 시점에 초기화되는 기능
항목 설명
장점 - 애플리케이션 기동 시간을 줄여줌.
- 빈들이 필요할 때만 초기화됨. (HTTP Request로 인한 필요)
단점 - 잘못 셋팅된 빈이 지연 초기화되면 문제를 늦게 발견.
- 서버 메모리 부족 시 런타임 오류 발생 가능.
지연 초기화 활성화 방법 모든 빈에 적용
- SpringApplicationBuilder#lazyInitialization()을 통한 SpringApplication 생성
- SpringApplication#setLazyInitialization() 호출
- spring.main.lazy-initialization 속성 사용

특정 빈에만 적용
- @Lazy 어노테이션 사용 

 

예제) @Lazy

더보기
@Component
@Lazy
public class HeavyComponent {
    public HeavyComponent() {
        System.out.println("HeavyComponent 초기화");
    }

    public void work() {
        System.out.println("HeavyComponent 작업 수행");
    }
}
@Component
public class AppRunner {
    private final HeavyComponent heavyComponent;

    public AppRunner(@Lazy HeavyComponent heavyComponent) {
        this.heavyComponent = heavyComponent;
        System.out.println("AppRunner 초기화");
    }

    @PostConstruct
    public void run() {
        System.out.println("AppRunner 생성 완료");
        heavyComponent.work();
    }
}
  • 주입 받는 쪽에서도 @Lazy를 넣어줘야 프록시 객체로 주입받음

 

3. Application Availability

  • 관리 플랫폼에서 애플리케이션이 배포될 경우, Spring Application은 플랫폼에게 availability 정보를 제공해야 합니다
  • 이를 위해 Spring Boot는 health check endpoint를 제공하는 actuator 모듈을 지원합니다. (spring-boot-starter-actuator)

 

availability 정보

구분 상태 종류 의미 예시 / 시점 주의사항
Liveness
정상
(CORRECT)
애플리케이션 내부가 정상 동작 중이거나 에러를 스스로 회복할 수 있는 상태 ApplicationContext 리프레시 완료 이후
외부 시스템 상태에 의존하면 안 됨
손상
(BROKEN)
애플리케이션이 스스로 회복 불가능한 상태 내부 오류가 지속되는 경우
손상 상태 판단 시 외부 시스템 영향 고려 ❌
Readiness
정상
(ACCEPTING_TRAFFIC)
애플리케이션이 트래픽을 처리할 준비가 된 상태 ApplicationRunner 또는 CommandLineRunner 실행 완료 후
@PostConstruct는 Readiness와 무관
손상
(REFUSING_TRAFFIC)
애플리케이션이 트래픽을 받을 수 없는 상태 기동 중 또는 일부 구성 요소 지연 시
초기화 미완료 혹은 종속 리소스 준비 안 됨

 

availability 조회

  • ApplicationAvailability를 사용하면 현재 애플리케이션의 Liveness / Readiness 상태를 조회할 수 있습니다.

 

예제) AvailabilityChecker

더보기
@Component
public class AvailabilityChecker {
    private final ApplicationAvailability availability;

    public AvailabilityChecker(ApplicationAvailability availability) {
        this.availability = availability;
    }

    public void checkAvailability() {
        LivenessState liveness  = availability.getLivenessState();

        if (liveness == LivenessState.BROKEN) System.out.println("⚠ 애플리케이션이 손상된 상태입니다.");
        else if (liveness == LivenessState.CORRECT) System.out.println("✅ 애플리케이션이 정상적으로 초기화되었습니다.");

        ReadinessState readiness = availability.getReadinessState();

        if (readiness == ReadinessState.REFUSING_TRAFFIC) System.out.println("❌ 트래픽을 받을 준비가 안된 상태입니다.");
        else if (readiness == ReadinessState.ACCEPTING_TRAFFIC) System.out.println("✅ 정상적으로 트래픽을 수용할 수 있는 상태입니다.");
    }
}

 

4. Application Events and Listeners

  • SpringApplication 클래스는 애플리케이션의 시작 프로세스를 관리합니다
  • 이벤트 드리븐 매커니즘으로 초기화 작업을 수행합니다.
  • 애플리케이션 기동 시 ApplicationEvent를 발행합니다.

 

ApplicationEvent

  • ApplicationEvent는 Spring Framework의 이벤트 발행 매커니즘을 통해 발행됩니다.
  • 발행된 ApplicationEvent는 등록된 Listener들에게 전달됩니다.

 

종류
이벤트 발생 시점 리스너 설명
ApplicationStartingEvent SpringApplication.run() 호출 직후 - LoggingApplicationListener: 초기 로깅 시스템 구성 (color, logback 등)
- BackgroundPreinitializer: 공통 클래스/기능 선행 초기화 (비동기)
- DelegatingApplicationListener: spring.factories 기반 리스너 위임
ApplicationEnvironmentPreparedEvent 환경 설정 완료 (Environment) - EnvironmentPostProcessor...: application.yml 등 config 파일 적용
- AnsiOutputApplicationListener: ANSI 로그 컬러 설정 반영
- FileEncoding...: 시스템 인코딩 유효성 체크 (file.encoding)
ApplicationContextInitializedEvent ApplicationContext 생성됨
(Bean 등록 전)
주로 위임 리스너들과 백그라운드 프리로더만 동작
ApplicationPreparedEvent Bean 정의 로드 완료
refresh() 전 (webserver 초기화 전)
- 설정이 반영된 최종 로깅 환경 구성
- Delegating 리스너가 다음 단계 리스너들 위임 준비
WebServerInitializedEvent 내장 톰캣/웹 서버 포트 바인딩 완료 - SpringApplicationAdminMXBeanRegistrar: JMX MBean 등록
- ServerPortInfo...: 실제 바인딩된 포트 정보를 환경에 기록
ContextRefreshedEvent 모든 Bean 초기화 완료 - ConditionEvaluationReport...: @Conditional 평가 결과 로그 출력
- ClearCaches...: 불필요한 캐시 제거
- SharedMetadataReader...: 클래스 스캐닝에 사용하는 Bean 초기화
ApplicationStartedEvent refresh() 이후
Runner 실행 전
- StartupTimeMetricsListener: 애플리케이션 시작 시간 측정 및 기록 (Micrometer)
- TomcatMetricsBinder: 톰캣 리소스 상태 모니터링 (active thread, connection 등)
AvailabilityChangeEvent
(CORRECT)
애플리케이션이 "살아있음"  - ApplicationAvailabilityBean: /actuator/health/liveness 엔드포인트에서 사용
- 애플리케이션이 정상 실행 중임을 나타냄
ApplicationReadyEvent ApplicationRunner
CommandLineRunner 실행 완료
- StartupTimeMetricsListener: 최종 시작 시간 기록 완료
- JMX 등록 리스너가 여기도 있음
AvailabilityChangeEvent
(ACCEPTING_TRAFFIC)
애플리케이션이 트래픽 받을 준비 완료 - /actuator/health/readiness 상태로 트래픽 수신 가능 표시
ApplicationFailedEvent 실행 중 예외 발생 시 - 실패 시 로그 남기고 상태 전파, 종료 처리

 

전파
  • ApplicationContext는 계층 구조를 가짐
    • 자식 ApplicationContext에서 ApplicationEvent 발행하면 부모 ApplicationContext에 ApplicationEvent가 전파됩니다.
  • Listener는 전달받은 ApplicationEvent가 직접 발행한건지 전파된건지 구분해야 합니다.
    • 이를 위해 Listener는 자신이 속한 ApplicationContext의 참조를 가지고 있어야 합니다.
    • Listener는 ApplicationContextAware 인터페이스를 구현함으로써 자신의 ApplicationContext 참조 가질 수 있습니다
    • Listener가 빈으로 등록되어 있다면 @Autowired로 ApplicationContext를 주입받을 수 있습니다.

 

ApplicationListener

  • ApplicationEvent를 listen 하는데 쓰입니다.

 

등록 방법
방법 설명
META-INF/spring.factories에 등록
이벤트 리스너 구현체를 spring.factories에 등록하기
Environment에 등록
org.springframework.context.ApplicationListener를 key로 하여 구현체를 등록합니다.
SpringApplication을 통해 등록
SpringApplication 클래스에서 직접 ApplicationListener를 등록합니다.

 

예제) ApplicationListener 구현하기

더보기
public class MyEventListener implements ApplicationListener<ApplicationEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.out.println("hello event! " + event.getClass().getSimpleName());
    }
}

 

예제) ApplicationListener 등록하기 

더보기

1. META-INF/spring.factories에 등록

org.springframework.context.ApplicationListener=\
org.example.event.MyMetaInfListener

 

2. Environment에 등록

spring:
  application:
    listeners:
      - com.example.listener.MyEnvironmentListener
org.springframework.context.ApplicationListener=\
org.springframework.boot.context.config.DelegatingApplicationListener

 

3. SpringApplication에 등록

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Main.class);
        app.addListeners(new MyProgrammaticListener());
        app.run(args);
    }
}

 

Web Environment

  • SpringApplication은 현재 애플리케이션에 적절한 ApplicationContext 타입인 WebApplicationType을 정해줍니다.
  • 내부적으로 classpath에 DispatcherServlet 클래스의 존재 여부에 따라 결정됩니다. 
  • SpringApplication은 setWebApplicationType()을 호출하여 명시적으로 지정할 수 있습니다.
WebApplicationType 설명
사용되는 ApplicationContext 구현체
SERVLET Spring MVC 기반 웹 애플리케이션
AnnotationConfigServletWebServerApplicationContext
REACTIVE Spring WebFlux 기반 웹 애플리케이션
AnnotationConfigReactiveWebServerApplicationContext
NONE 웹 환경이 아닌 일반 애플리케이션 (CLI 등)
AnnotationConfigApplicationContext

 

Accessing Application Arguments

  • ApplicationArguments
    • SpringApplication.run() 실행 시, 전달된 인자를 파싱해서 제공하는 객체입니다.
    • 빈으로 자동 등록되므로 어디서든 주입받아 사용할 수 있습니다.
  • CommandLinePropertySource
    • Environment에 CommandLinePropertySource를 자동으로 등록합니다.
    • 이로 인해 @Value 어노테이션으로 프로퍼티 값을 주입받을 수 있습니다.

 

Using the ApplicationRunner or CommandLineRunner

항목 ApplicationRunner
CommandLineRunner
실행 시점 Spring Boot 애플리케이션 실행 후 빈 초기화가 끝난 직후 동일
run() 파라미터 ApplicationArguments args String... args
명령행 인자 접근 방식 구조화된 형태 (옵션, 값 등 분리 제공) 단순 문자열 배열
명령행 인자 파싱 args.getOptionNames(), args.getOptionValues() 등
수동으로 for문 등을 이용해 직접 파싱
주 용도 명령행 인자를 키-값 형태로 받아 파싱 필요할 때
단순한 인자 전달 및 처리
순서 지정 가능 여부 가능 (@Order, Ordered 인터페이스 구현) 동일
등록 방법 @Component 또는 @Bean 동일

 

예제) CommandLineRunner

더보기
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class MyCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) {
        // Do something...
    }

}

 

 

 

참고