Posts HashMap의 put 메서드 들여다보기
Post
Cancel

HashMap의 put 메서드 들여다보기

사용 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Slf4j
@Component
@RequiredArgsConstructor
@Profile("!test")
public class ApplicationShutdownListener implements ApplicationListener<ContextClosedEvent> {

    private final FileBackupManager fileBackupManager;

    @Value("${file.address-book}")
    private String fileName;

    @Value("${file.address-book-backup-prefix}")
    private String backupFilePrefix;

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        try {
            fileBackupManager.backup(fileName, backupFilePrefix);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

ApplicationListener

Observer 패턴을 기반으로 한 이벤트 리스너 인터페이스

  • 관심 있는 이벤트 타입을 제네릭으로 선언 가능
    • ApplicationListener 형태로 특정 이벤트 타입을 지정할 수 있습니다.
    • 이렇게 하면 해당 이벤트 타입이 발생했을 때만 리스너가 실행됩니다.
  • Spring ApplicationContext에 등록되면 자동으로 이벤트 필터링
    • ApplicationContext에 등록된 리스너는 Spring에서 자동으로 관리됩니다.
    • 등록된 이벤트 리스너는 자신이 관심 있는 이벤트 타입이 발생할 때만 호출됩니다.
1
2
3
4
5
6
7
8
9
10
11
// org.springframework.context.ApplicationListener
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);

    static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
        return (event) -> {
            consumer.accept(event.getPayload());
        };
    }
}

ApplicationListener 등록

  • AbstractApplicationContext#addApplicationListener에서 담당

public void addApplicationListener(ApplicationListener<?> listener) { Assert.notNull(listener, “ApplicationListener must not be null”); if (this.applicationEventMulticaster != null) { this.applicationEventMulticaster.addApplicationListener(listener); }

1
this.applicationListeners.add(listener); }

애플리케이션 종료시 ContextClosedEvent 발행

  • AbstractApplicationContext#doClose ```java protected void doClose() { if (this.active.get() && this.closed.compareAndSet(false, true)) { if (this.logger.isDebugEnabled()) { this.logger.debug(“Closing “ + this); }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
      Throwable ex;
      try {
          this.publishEvent((ApplicationEvent)(new ContextClosedEvent(this)));
      } catch (Throwable var3) {
          ex = var3;
          this.logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
      }
    
      if (this.lifecycleProcessor != null) {
          try {
              this.lifecycleProcessor.onClose();
          } catch (Throwable var2) {
              ex = var2;
              this.logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
          }
      }
    
      this.destroyBeans();
      this.closeBeanFactory();
      this.onClose();
      if (this.earlyApplicationListeners != null) {
          this.applicationListeners.clear();
          this.applicationListeners.addAll(this.earlyApplicationListeners);
      }
    
      this.active.set(false);   }
    

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
### ApplicationListener 호출
- SimpleApplicationEventMulticaster#doInvokeListener
```java
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
        listener.onApplicationEvent(event);
    } catch (ClassCastException var6) {
        ClassCastException ex = var6;
        String msg = ex.getMessage();
        if (msg != null && !this.matchesClassCastMessage(msg, event.getClass()) && (!(event instanceof PayloadApplicationEvent) || !this.matchesClassCastMessage(msg, ((PayloadApplicationEvent)event).getPayload().getClass()))) {
            throw ex;
        }

        Log loggerToUse = this.lazyLogger;
        if (loggerToUse == null) {
            loggerToUse = LogFactory.getLog(this.getClass());
            this.lazyLogger = loggerToUse;
        }

        if (loggerToUse.isTraceEnabled()) {
            loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
        }
    }

}

ApplicationContext

// org.springframework.context.ApplicationContext public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver { @Nullable String getId();

1
2
3
4
5
6
7
8
9
10
String getApplicationName();

String getDisplayName();

long getStartupDate();

@Nullable
ApplicationContext getParent();

AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException; }

ApplicationContextEvent

Base class for events raised for an ApplicationContext. 4가지 구현체가 존재 (스프링 6.0.4 버전 기준) ContextClosedEvent, ContextRefreshedEvent, ContextStartedEvent, ContextStoppedEvent

// org.springframework.context.event.ApplicationContextEvent public abstract class ApplicationContextEvent extends ApplicationEvent { public ApplicationContextEvent(ApplicationContext source) { super(source); }

1
2
3
public final ApplicationContext getApplicationContext() {
    return (ApplicationContext)this.getSource();
} }

ApplicationContext의 생명주기

ApplicationContext는 애플리케이션의 전체 실행 흐름을 관리하며, 다음과 같은 주요 단계로 동작

단계설명
1. ApplicationContext 생성 (refresh() 호출)ApplicationContext가 인스턴스화되고, 필요한 초기 작업을 수행
2. Bean 정의 로드 (BeanFactory 설정)XML, JavaConfig, 또는 @ComponentScan을 통해 Bean 정의를 로드
3. Bean 객체 생성 (Instantiation & Dependency Injection)BeanFactory가 Bean을 생성하고, 의존성을 주입
4. ApplicationEvent 발생 (ContextRefreshedEvent)컨텍스트가 완전히 초기화되면 ContextRefreshedEvent를 발생
5. 애플리케이션 실행ApplicationContext를 통해 애플리케이션이 정상적으로 동작
6. 컨텍스트 종료 (ContextClosedEvent & ContextStoppedEvent)애플리케이션 종료 시, ContextClosedEvent가 발생
7. 컨텍스트 소멸 (destroy())모든 @PreDestroy 및 DisposableBean 메서드가 실행되고 종료

관련 이벤트

Spring은 ApplicationContext의 특정 시점에서 이벤트를 발생시켜, 개발자가 특정 타이밍에 작업을 수행할 수 있도록 합니다.

이벤트설명
ContextRefreshedEventApplicationContext가 초기화되었을 때 발생
ContextStartedEventstart() 메서드 호출 시 발생 (잘 사용하지 않음)
ContextStoppedEventstop() 메서드 호출 시 발생 (잘 사용하지 않음)
ContextClosedEventclose() 메서드 호출 시 발생 (애플리케이션 종료)
ApplicationReadyEventSpring Boot에서 ApplicationContext가 완전히 로드된 후 발생

Spring Bean 생명주기

ApplicationContext가 애플리케이션 전체의 생명주기를 관리하는 반면, Bean의 생명주기는 개별 객체에 초점이 맞춰져 있습니다.

단계설명
1. 인스턴스 생성 (new)Spring 컨테이너가 Bean 객체를 생성 (@Component, @Bean)
2. 의존성 주입 (@Autowired)필요한 의존성을 주입 (@Autowired, @Inject)
3. @PostConstruct 실행초기화 직전 실행 (DB 연결, 캐시 로딩 등)
4. InitializingBean#afterPropertiesSet() 실행@PostConstruct 이후 추가 설정 가능
5. Bean 사용 가능 (컨텍스트 실행 중)Bean이 정상적으로 사용됨
6. 컨텍스트 종료 (ContextClosedEvent)Spring 애플리케이션 종료
7. @PreDestroy 실행Bean이 삭제되기 전 정리 작업 수행
8. DisposableBean#destroy() 실행@PreDestroy 이후 추가 정리 작업 가능

관련 인터페이스, 어노테이션

방식설명
@PostConstructBean 초기화 시 자동 실행
InitializingBean#afterPropertiesSet()Bean 생성 후 실행 (@PostConstruct보다 먼저 실행)
DisposableBean#destroy()Bean 소멸 전에 실행 (@PreDestroy보다 먼저 실행)
@PreDestroyBean이 소멸되기 전 실행
@Bean(initMethod, destroyMethod)XML 또는 JavaConfig에서 초기화 & 종료 메서드 지정 가능

두 생명주기를 같이 보면 ?

Spring 생명주기 타임라인 (시간 흐름)

단계ApplicationContext 생명주기Bean 생명주기관련 이벤트 & 메서드
1. ApplicationContext 생성SpringApplication.run() → createApplicationContext() 호출--
2. ApplicationContext 초기화 시작refresh() 실행-ContextRefreshedEvent 발생
3. Bean 정의 로드BeanFactory가 Bean 정의 로드--
4. Bean 객체 생성 (new)BeanFactory.createBean() 호출Bean 인스턴스화-
5. 의존성 주입 (DI)BeanFactory가 의존성 주입 수행@Autowired 주입-
6. Bean 초기화-@PostConstruct 실행-
7. Bean 초기화 콜백-InitializingBean#afterPropertiesSet() 실행-
8. ApplicationContext 준비 완료finishRefresh() 호출Bean 사용 가능ContextRefreshedEvent 발생
9. 애플리케이션 실행 중요청 처리, 이벤트 처리Bean 정상 동작-
10. 애플리케이션 종료 요청 (close())ContextClosedEvent 발생@PreDestroy 실행-
11. Bean 소멸 콜백destroy() 호출DisposableBean#destroy() 실행-
12. ApplicationContext 종료모든 Bean 제거 후 종료--

@Autowired vs @PostConstruct

차이점@Autowired 생성자에서 초기화@PostConstruct에서 초기화
실행 시점객체 생성과 동시에 실행 (Spring이 Bean을 만들 때)Bean 초기화 직후 실행 (의존성 주입이 모두 완료된 후)
의존성 주입 보장clientDataLoader가 필수적으로 주입되어야 함clientDataLoader가 선택적(@Autowired(required=false))일 수도 있음
테스트 용이성생성자에서 초기화 → 단위 테스트 시 Mock 주입 필요@PostConstruct 사용 → Setter로 Mock 주입 가능
선택적 실행 가능 여부항상 실행됨 (Bean이 생성될 때마다 실행)필요 시 프로퍼티로 활성화/비활성화 가능
Super 클래스와의 관계부모 클래스의 생성자가 먼저 실행됨자식 클래스의 @PostConstruct가 먼저 실행됨
순환 참조 문제A → B → A 순환 참조 시 예외 발생 가능순환 참조를 늦출 수 있어 문제 완화 가능

예시: 특정 설정값에 따라 실행을 컨트롤해야 하는 경우 (@PostConstruct 활용)

** 시나리오:**

  • ClientStore가 실행될 때, application.properties의 값에 따라 초기 데이터를 로드할지 여부를 결정해야 한다.
  • 예를 들어 client.data.load.enabled=false라면 데이터를 로드하지 않고 빈 상태로 유지하고 싶다.
  • 이 경우 @Autowired 생성자에서는 실행을 컨트롤할 수 없지만, @PostConstruct에서는 가능하다.
This post is licensed under CC BY 4.0 by the author.