Java

[SpringFramework] Lazy Initialization vs. Eager Initialization, Bean Scopes

ride-dev 2023. 12. 17. 18:06

Lazy Initialization vs. Eager Initialization, Bean Scopes

1. 지연 초기화(Lazy)와 즉시 초기화(Eager)

지연 초기화는, Spring Bean이 사용직전에 초기화되도록 하는 것이고

즉시 초기화는, Application이 시작할 때 Spring Bean이 초기화되도록 하는 것입니다.

(Spring은 기본적으로 즉시 초기화로 설정되어 있습니다)

 

즉시 초기화를 사용하면 Spring 구성에 오류가 있을 경우,

애플리케이션이 시작할 때 오류를 바로 확인할 수 있습니다.

 

지연 초기화는

@Component와 @Bean이 사용되는 거의 모든 곳에서 사용할 수 있습니다.

지연-해결 프록시가 실제 의존성 대신 주입됩니다.

그리고 @Configuration 클래스에서도 사용할 수 있습니다.

@Configuration클래스에 Lazy를 적용하면 @Configuration클래스 내의 모든 @Bean메서드가 지연 초기화됩니다.

Lazy를 자주 사용할 필요는 없습니다.

복잡한 초기화 논리가 많고 시작 시 지연시키고 싶지 않은 상황이라면 Spring Bean의 지연 초기화를 고려해 볼 수 있습니다.

 

지연 초기화를 사용하는 경우 애플리케이션이 시작될 때 Spring 구성 오류는 발견되지 않고 런타임 오류가 발생합니다.

 

따라서 애플리케이션 시작 시 오류를 바로 확인할 수 있는 즉시 초기화를 권장합니다.

즉시초기화

@Component
class ClassA {

}

@Component
class ClassB {
    private ClassA classA;

    public ClassB(ClassA classA) {
        //Logic
        System.out.println("Some Initialization");
        this.classA = classA;
    }
    public void doSomething() {
        System.out.println("[doSomething] Do Something");
    }
}

@Configuration
@ComponentScan
public class LazyInitializationLauncherApplication {

    public static void main(String[] args) {
        try (
                var context =
                        new AnnotationConfigApplicationContext
                                (LazyInitializationLauncherApplication.class)
        ) {
            System.out.println("Initialization of context is completed");
            context.getBean(ClassB.class).doSomething();
        }
    }
}

위 코드의 출력문은 아래와 같습니다.

Some Initialization
Initialization of context is completed
[doSomething] Do Something

지연 초기화

@Component
class ClassA {

}

@Lazy // 지연초기화, 사용될 때 초기화하도록 설정
@Component
class ClassB {
    private ClassA classA;

    public ClassB(ClassA classA) {
        //Logic
        System.out.println("Some Initialization");
        this.classA = classA;
    }
    public void doSomething() {
        System.out.println("[doSomething] Do Something");
    }
}

@Configuration
@ComponentScan
public class LazyInitializationLauncherApplication {

    public static void main(String[] args) {
        try (
                var context =
                        new AnnotationConfigApplicationContext
                                (LazyInitializationLauncherApplication.class)
        ) {
            System.out.println("Initialization of context is completed");
            //ClassB에 @Lazy를 적용했기 때문에, 사용 직전 로드됨
            context.getBean(ClassB.class).doSomething();
        }
    }
}

위 코드의 출력문은 아래와 같습니다.

Initialization of context is completed
Some Initialization
[doSomething] Do Something

 

만약 ClassB가 호출되지 않는다면

...
@Configuration
@ComponentScan
public class LazyInitializationLauncherApplication {

    public static void main(String[] args) {
        try (
                var context =
                        new AnnotationConfigApplicationContext
                                (LazyInitializationLauncherApplication.class)
        ) {
            System.out.println("Initialization of context is completed");
            //ClassB에 @Lazy를 적용했기 때문에, 사용하지 않으면 로드되지 않음
        }
    }
}

위 코드의 출력문은 아래와 같습니다.

Initialization of context is completed

 

 

Lazy Initialization vs Eager Initialization

Heading Lazy Initialization
지연 초기화
Eager Initialization
즉시 초기화
Initialization time
초기화 시간
Bean initialized when it is first made use of in the application
애플리케이션에서 처음 사용될 때
Bean이 초기화 됨
Bean initialized at startup of the application
애플리케이션이 시작되거나
Spring context가 시작될 때
Bean이 초기화 됨
Default
기본값
NOT Default
아님
Default
맞음
Code Snippet
사용법
@Lazy OR
@Lazy(value = true)
@Lazy(value=false) OR
(Absence of @Lazy)
@Lazy 안쓰기
What happens if there are errors in initalizaing?
초기화하다가 오류가 발생하면?
Errors will result in runtime exceptions
런타임 예외 발생
Errors will prevent application from starting up
애플리케이션이 시작되지 않음
Usage
사용빈도
Rarely used
드물게
Very frequently used
자주
Memory Conmsumption
메모리 사용량
Less (until bean is initialized)
메모리 사용량이 상대적으로 적음
All beans are initialized at startup
시작할 때 모든 것이 로드됨
Recommended Scenario
권장 시나리오
Beans very rarely used in your app
Bean이 아주 드물게 사용될 때
Most of your beans
모든 Beran에 권장되는 설정

 

2. Spring Framework Bean Scopes

@Scope는 두 가지 설정을 할 수 있습니다.

SINGLETON(싱글톤 스코프) 혹은 PROTOTYPE(프로토타입 스코프)입니다.

기본적으로 Spring Framework에서 생성되는 모든 Bean은 싱글톤입니다.

따라서, 추가적인 설정을 하지 않고 Bean을 요청하면, 요청할 때마다 같은 인스턴스가 반환됩니다.

만약, Bean을 참조할 때마다 매번 다른 인스턴스를 생성하고 싶은 경우에는 프로토타입을 사용합니다.

 

아래는 그 예제입니다.

@Component
class NormalClass {

}

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
class PrototypeClass {

}

@Configuration
@ComponentScan
public class BeanScopesLauncherApplication {

    public static void main(String[] args) {
        try (
                var context =
                        new AnnotationConfigApplicationContext
                                (BeanScopesLauncherApplication.class)
        ) {
            // 해시코드 값이 동일함
            System.out.println(context.getBean(NormalClass.class));
            System.out.println(context.getBean(NormalClass.class));

            // 호출할 때마다 다른 해시코드 값 나타남
            System.out.println(context.getBean(PrototypeClass.class));
            System.out.println(context.getBean(PrototypeClass.class));
            System.out.println(context.getBean(PrototypeClass.class));

        }
    }
}

위 코드의 출력문은 아래와 같습니다.

com.ride.learnspringframework2.example.e1.NormalClass@40cb8df7
com.ride.learnspringframework2.example.e1.NormalClass@40cb8df7
com.ride.learnspringframework2.example.e1.PrototypeClass@13b13b5d
com.ride.learnspringframework2.example.e1.PrototypeClass@2892dae4
com.ride.learnspringframework2.example.e1.PrototypeClass@355ce81c

 

NormalClass의 경우, Scope를 설정하지 않았기 때문에 스프링의 기본설정인 싱글톤 스코프로 되어

매 호출마다 동일한 해시코드(인스턴스)가 반환됩니다.

Scope를 설정한 ProtoTypeClass의 경우, 프로토타입 스코프로 설정했기 때문에

매 호출마다 다른 해시코드(인스턴스)가 반환됩니다.

 

Singleton 싱글톤

Spring IoC 컨테이너당 객체 인스턴스가 단 하나입니다.

Prototype 프로토타입

Spring IoC컨테이너당 객체 인스턴스가 여러 개일 수 있습니다.

따라서, Spring 컨테이너에서 Bean을 요청할 때마다 특정한 Bean의 새 인스턴스를 생성합니다.

싱글톤, 프로토타입 이외에도
웹 애플리케이션에서만 특정하게 적용되는
(web-aware ApplicationContext에서 사용하는)
스코프가 있습니다.

그 스코프들은 아래와 같습니다.

Request 리퀘스트

웹 애플리케이션에는 HTTP 요청이라는 게 있습니다. 리퀘스트 스코프의 경우 HTTP 요청당 객체 인스턴스 하나가 생성됩니다.

Session 세션

세션은 보통 사용자와 관련되어 있습니다.

동일한 사용자에게 속하는 여러 번의 요청이 같은 세션에 속해 있을 수 있습니다.

세션 스코프의 경우 사용자 HTTP세션당 객체 인스턴스 하나가 생성됩니다.

Application 애플리케이션

구동중인 웹 애플리케이션에 객체 인스턴스가 하나입니다.

Websocket 웹소켓

웹소켓 인스턴스당 객체 인스턴스가 하나입니다.

Java Singleton (GOF Gang of Four) 디자인 패턴 vs Spring Singleton

보통 Singleton은 동일한 의미로 사용됩니다.

 

Spring Singleton의 경우 Spring IoC컨테이너당 객체 인스턴스가 하나지만,

Java Singletone에서는 JVM(Java Virtual Machine 자바 가상 머신) 하나당 객체 인스턴스가 하나입니다. JVM 전체에서 객체가 하나인 겁니다.

JVM에 Spring IoC컨테이너를 하나만 실행한다면 Spring Singleton과 Java Singleton의 차이는 없다고 볼 수 있습니다.

하지만 JVM에 Sping IoC 컨테이너 여러 개를 실행하면 어떨까요?

Spring Singleton은 하나의 JVM에서 실행중인 Spring IoC컨테이너당 객체 인스턴스가 매치되지만,

Java Singleton은 하나의 JVM에서 Spring IoC컨테이너가 얼마나 실행되든 객체 인스턴스가 하나입니다.

 

많은 경우 하나의 JVM에 Spring IoC컨테이너 여러 개를 실행하지는 않지만,

이 둘의 차이를 유의할 필요가 있습니다.

Prototype vs Singleton

Heading Prototype
프로토타입
Singleton
싱글톤
Instances
인스턴스
Possibly Many per Spring IoC Container
Spring IoC 컨테이너 하나당 객체 인스턴스 여러 개
많은 인스턴스를 요청할 경우,
Bean에 여러 번 요청을 보내면
Spring IoC 컨테이너는 많은 인스턴스르 생성해 반환 
One per Spring IoC Container
Spring IoC 컨테이너 하나당 객체 인스턴스 하나
Beans
New Bean instance created every time the bean is referred to
Bean을 참조할 때마다 새로운 Bean 인스턴스가 생김
Same bean instance reused
동일한 Bean 인스턴스를 재사용함
Default
기본값
NOT Default
아님
Default
맞음
Code Snippet
코드 조각
@Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Scope(value=ConfigurableBeanFactory.SCOPE_SINGLETON)
OR Default
작성하지 않아도 됨
Usage
사용빈도
Rarely used
아주 드물게
Very frequently used
대부분
Recommended Scenario
권장되는 상황
Stateful beans
사용자 정보가 유지되는 Bean을 만들 때,
프로토타입으로 사용자마다 Bean을 별도로 생성
Stateless beans
사용자 정보가 없거나,
일반적 애플리케이션 전체에서 사용할 수 있다면
인스턴스를 하나만 만들어 애플리케이션 전체에서 사용
728x90