Study/Spring

Spring. ObjectProvider

염몽이 2024. 9. 8. 16:27

ObjectProvider

ObjectProvider 를 공부하다가 의문이 생겨 정리할 겸 글을 작성한다.

아래의 코드는 prototype Bean 옵션을 사용하고 있다.

PrototypeBean 클래스를 사용하여 주입받는ClientBean 이 있다.

 

package hello.core.scope;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.inject.Provider;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;



import static org.assertj.core.api.Assertions.*;

public class SingletonWithPrototypeTest1 {

    @Test
    void prototypeFind() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);

        PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
        prototypeBean1.addCount();
        assertThat(prototypeBean1.getCount()).isEqualTo(1);

        PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
        prototypeBean2.addCount();
        assertThat(prototypeBean1.getCount()).isEqualTo(1);

    }

    @Test
    void singletonClientUsePrototype() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class, ClientBean.class);

        ClientBean clientBean1 = ac.getBean(ClientBean.class);
        int count1 = clientBean1.logic();
        assertThat(count1).isEqualTo(1);

        ClientBean clientBean2 = ac.getBean(ClientBean.class);
        int count2 = clientBean2.logic();
        assertThat(count2).isEqualTo(1);

    }

    @Scope("singleton")
    static class ClientBean {

        //private final PrototypeBean prototypeBean; //생성시점에 주입

        private ObjectProvider<PrototypeBean> prototypeBeanProvider; // spring 기능: 권장

        //private Provider<PrototypeBean> prototypeBeanProvider; // JSR-330 Provider: 스프링이 아닌 다른 컨테이너에서도 사용 가능

        public int logic() {
            //prototypeBean.addCount();
            //return prototypeBean.getCount();
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
            prototypeBean.addCount();
            return prototypeBean.getCount();
        }
    }

    @Scope("prototype")
    static class PrototypeBean {
        private int count = 0;

        public void addCount() {
            count++;
        }

        public int getCount() {
            return count;
        }

        @PostConstruct
        public void init() {
            System.out.println("PrototypeBean.init = " + this);
        }

        @PreDestroy
        public void destroy() {
            System.out.println("PrototypeBean.destroy");
        }
    }
}

 

ObjectProvider 생성 사이클:

-   1.  생성자 주입: `ClientBean`에 `ObjectProvider<PrototypeBean>`가 주입된다.  
        이때 `ObjectProvider`는 단순히 프로토타입 빈을 제공할 수 있는 객체로 주입될 뿐,  
        실제로 `PrototypeBean`의 인스턴스를 미리 생성해서 가지고 있지는 않는다.
-   2.  `getObject()` 호출: `ObjectProvider.getObject()`가 호출될 때마다
        스프링 컨테이너에서 새로운 프로토타입 빈을 요청하고 반환한다.
        따라서, 매번 `getObject()`를 호출할 때마다 새로운 `PrototypeBean` 인스턴스가 생성된다.

 

ObjectProvider의 주요 역할:

1.    지연된 빈 생성: 빈을 즉시 주입받는 것이 아니라, 필요할 때 getObject()를 호출하여 빈을 동적으로 가져온다.
2.    프로토타입 빈 사용: 특히 프로토타입 빈의 경우, 매번 getObject()를 호출할 때마다 새로운 인스턴스를 반환한다.
3.    유연성: 주입 시점에 빈을 결정하지 않고, 런타임에 필요할 때 빈을 가져오는 방식으로 더 유연하게 사용할 수 있다.

 

정리:
ObjectProvider는 조회 기능만 제공하는 클래스이다.

스프링 컨테이너에 등록된 빈을 동적으로 조회하며, 빈을 직접 생성하지 않는다.

 

프로토타입 빈은 조회할 때마다 새로 생성된다. ObjectProvider.getObject()를 호출할 때,

스프링 컨테이너가 해당 빈을 생성하여 반환하는 방식이다.

 

따라서 ObjectProvider는 빈을 조회하는 역할을 담당하고, 빈의 생성은 스프링 컨테이너가 관리한다.

 

실무에서는 Dipendeny Lookup (DL) 을 위한 편의 기능을 많이 제공해주고
스프링 외에 별도의 의존관계 추가가 필요 없기 때문에 ObjectProvider를 사용하면 된다.
만약 코드를 스프링이 아닌 다른 컨테이너에서도 사용할 수 있어야 한다면 JSR-330 Provider를 사용해야한다.

 

스프링이 제공하는 기능을 사용하면 된다.