[Spring] Bean 생성 순서와 Life Cycle

diense_kk·2025년 1월 2일
0

SpringBoot

목록 보기
11/11
post-thumbnail

Bean 생성 순서

Spring

설정 파일을 통해 등록된 Bean을 자동적으로 위에서 아래로 bean들을 스캔하여 생성한다.

Spring Boot

어노테이션을 이용해서 Bean을 등록하게 되면 패키지의 알파벳 순서대로 스캔하여 Bean을 생성한다. 이렇게 하면 생성 순서를 맞춰주지 못하는 문제가 생길 것 같지만, 특정 Bean을 생성하는 도중에 해당 Bean에 주입되는 새로운 Bean을 만나면 이 Bean부터 생성하기 떄문에 문제가 되지 않는다. 단, 생성자 주입 방식일 경우에만 해당된다.

컴포넌트 스캔 순서와 관계없이 의존 관계를 기반으로 빈을 생성하므로 문제가 되지 않습니다.

스프링 부트에서 컴포넌트 스캔방식(생성자 주입 방식인 경우)

Bean 생성자 호출 순서

Controller -> Service -> Repository (패키지 알파벳 순)

Bean 생성완료 순서

Repository -> Service -> Controller

Controller를 Bean으로 등록하기 위해 Controller의 생성자를 호출할 때 Controller의 생성자에 있는 파라미터(객체)를 먼저 Bean으로 생성해야 된다.

Bean Life Cycle

2번부터 5번이 Bean Life Cycle

1) 스프링 컨테이너 생성
SpringApplication.run()을 호출하여 실행된다. 애플리케이션 설정 정보(application.properties) 로드, @Configuration 클래스 처리 등을 함

2) 스프링 빈 생성 및 의존관계 주입
@Component, @Service, @Repository, @Controller 등 어노테이션을 가진 클래스들을 스캔하여 빈으로 등록한다.

3) 초기화 콜백 - 빈이 완전히 생성된 후 호출
애플리케이션 실행에 필요한 리소스를 준비한다. 주로 데이터베이스 연결 초기화나 외부 API나 설정값 확인 등을 함

4) 빈 사용
HTTP 요청이 들어오거나, 이벤트가 발생했을 때 컨트롤러나 서비스 빈이 호출된다.

5) 소멸 전 콜백 - 빈이 소멸되기 직전에 호출
데이터베이스 연결 종료, 외부 API와의 연결 해제 등을 함

6) 스프링 종료 - Bean 파괴는 생성의 역순
애플리케이션을 종료한다. 리소스 정리와 함께, 컨테이너 내 모든 리소스(캐시, 연결 풀 등)를 종료함

DI 시점과 생성자 기반의 DI

스프링에서는 생성자 주입 방식을 권장하는데, 생성자 주입 방식을 사용하면 실질적인 DI가 애플리케이션 부팅 시점에 이루어지기 때문이다.
즉, 생성자 기반의 DI를 사용하면 Spring IoC Container가 생성되면서 Bean들을 생성하고, Bean이 생성된 후에 해당 Bean과 관련된 의존성을 주입한다.

또한, 생성자 기반으로 의존성을 주입하면 final 키워드를 사용하여 의존관계를 갖는 객체를 불변으로 관리할 수 있다. 즉, 해당 객체의 상태를 불변으로 관리하여 멀트쓰레드 환경에서 공유하는 heap 영역의 Bean을 Thread-Safety하게 관리하고 잠재적 버그의 여지를 줄인다.

생성자 인자가 많은 객체는 많은 의존성을 가졌다는 것을 의미한다. 즉, 관심사의 분리가 필요한 객체이다.

DI를 사용하는 이유

유연성 확보

의존 관계 설정이 컴파일이 아닌 런타임 시에 이루어지도록 하여 모듈 간 결합도를 낮춘다.
특정 객체를 필요로 하는 클래스 내에서 직접 객체를 생성한다면, 클래스를 특정 객체에 확정 짖는 것이고, 이후에 해당 객체가 다른 객체로 대체된다면 클래스의 수정이 요구된다.
즉, 다른 객체를 필요로 하는 경우 클래스를 재사용 할 수 없다는 것이다.
만약 의존성을 외부로부터 주입받도록(DI) 하면 코드의 재사용성을 높이고, 모듈 간 결합도를 낮출 수 있다.

테스트 코드 작성 용이

특정 객체를 해당 클래스에서 직접 생성한다면 실제 객체를 모의 객체로 대체할 수 없기 때문에 클래스를 테스트 하기 어렵게 만든다.
생성자 기반 DI를 사용하면 테스트 시 생성자 주입을 통해서 목 객체를 쉽게 전달할 수 있다.
즉, Mock 객체를 이용해 생성자로 주입해주면 테스트할 클래스만을 테스트할 수 있게 된다.

Spring이 Singleton인 이유

결론부터 말하면, 대규모 트래픽을 처리하기 위함이다.
스프링은 엔터프라이즈급 애플리케이션을 목표로 만들어졌다. 엔터프라이즈급 애플리케이션에서 수 많은 요청이 들어올때 반복적으로 Bean 객체를 만드는 것은 매우 비효율적이다.
아무리 GC성능이 좋아졌어도 부하를 감당하기는 힘들 것이다.
따라서 스프링은 초기 로딩 시 시간이 걸리더라도 Singleton으로 Bean을 만든다.
Spring IoC Container 생성 시에 IoC Container는 모든 Bean들을 만들고, Bean 설정에 대한 예외처리를 초기에 검증한다.

Bean은 Thread-Safe한가?

클라이언트로부터 HTTP 요청이 오면, 서블릿 컨테이너는 쓰레드 풀에서 요청 당 쓰레드를 할당한다.
기본적으로 객체는 Heap 영역에 존재하므로 별다른 설정이 없으면 Thread-Safe 하지 않다.
그래서 Bean 자체를 불변으로 관리하는 것이 좋다. Bean을 불변으로 관리하기 위한 방법 중 하나로 필드에 final 키워드를 사용하는 것이고 이것이 Spring이 생성자 주입 방식을 권장하는 이유이다.

0개의 댓글