지연 초기화가 필요한 이유
@Component 어노테이션이나 @Bean 어노테이션을 통해 IOC Container에 Bean을 등록하면 스프링 애플리케이션 시작 시 등록된다.
예를 들어 다음과 같이 초기화 시 InitTestUseCase Initialized 를 출력하는 InitTestUseCase를 만들어보자.
@Component
class InitTestUseCase {
init {
println("InitTestUseCase initialized")
}
}
그런 다음 Configuration 파일을 다음과 같이 만든 후
@ComponentScan
@Configuration
class InitConfiguration
다음과 같이 컨테이너를 초기화 하고, "After container initialized" 을 출력한다.
fun main(args: Array<String>) {
val context = AnnotationConfigApplicationContext(InitConfiguration::class.java)
println("After container initialized")
}
그러면 이 코드를 실행하면 다음과 같은 결과가 나오는 것을 볼 수 있다.
컨테이너가 초기화 될 때 이미 InitTestUseCase가 만들어져 컨테이너에 등록되는 것을 볼 수 있다.
하지만, 스프링 애플리케이션을 실행할 때 매번 모든 Bean을 등록하면 시작 시간이 느려지고, 시작 시 메모리에 모든 Bean이 올라가기 때문에 시작이 무거워진다.
이런 문제를 해결하기 위해 스프링은 Bean이 필요할 때 초기화되도록 하는 지연 초기화 기능을 제공한다.
@Lazy 사용해 Bean 지연 초기화 설정하기
지연 초기화 기능을 설정하는 방법은 간단하다. 단순히 @Component나 @Bean과 함께 @Lazy 어노테이션을 붙이면 된다. 예를 들어 위에서 설정한 InitTestUseCase는 다음과 같이 변경되면 지연 초기화가 적용된다.
@Lazy
@Component
class InitTestUseCase {
init {
println("InitTestUseCase initialized")
}
}
이제 다시 컨테이너를 초기화하고 마지막 줄에 InitTestUseCase Bean을 가져오는 코드를 실행해보자.
fun main(args: Array<String>) {
val context = AnnotationConfigApplicationContext(InitConfiguration::class.java)
println("After container initialized")
context.getBean(InitTestUseCase::class.java)
}
그러면 결과는 다음과 같이 출력된다.
컨테이너가 초기화 된 다음 InitTestUseCase가 getBean 함수가 불리는 시점에 초기화 되는 것을 볼 수 있다.
지연 초기화의 장단점
지연 초기화는 시작 시간 최적화외 메모리를 필요할 때 소비한다는 점에서는 좋지만, Spring 애플리케이션 시작 시 의존성에 오류가 있더라도 오류가 발생되지 않는 문제가 있다. 이후 런타임에 오류가 발생하게 되면 인스턴스가 제대로 만들어지지 않게 된다. 예를 들어 InitTestUseCase에 String을 주입한 다음 이 타입의 Bean을 컨테이너에 제공하지 않는 경우를 생각해보자.
@Lazy
@Component
class InitTestUseCase(
private val string: String
) {
init {
println("InitTestUseCase initialized")
}
}
그러면 앱 시작 시에는 이 코드를 실행하더라도 오류가 발생하지 않는다.
fun main(args: Array<String>) {
val context = AnnotationConfigApplicationContext(InitConfiguration::class.java)
println("After container initialized")
}
다음은 위 코드를 실행한 결과이다.
하지만 이후 이 InitTestUseCase Bean을 가져오려고 하면 런타임에 오류가 생긴다.
fun main(args: Array<String>) {
val context = AnnotationConfigApplicationContext(InitConfiguration::class.java)
println("After container initialized")
context.getBean(InitTestUseCase::class.java)
}
다음은 이 코드를 실행한 결과이다.
즉, 지연 초기화는 메모리 최적화와 애플리케이션 시작 시간 최적화에는 좋지만, 애플리케이션 시작 시 오류가 발견되지 않을 수 있어 단점이 장점을 상쇄한다. 따라서 꼭 필요한 경우가 아니면 굳이 지연 초기화를 설정하지 않는 것이 좋을 수 있다.
전체 코드: GitHub
이 프로젝트가 도움이 되셨다면 저장소에 Star⭐️를 눌러주세요! Stargazers는 다음 페이지에서 확인할 수 있습니다.