Spring/스프링 핵심 원리 - 기본편

[빈 스코프] 싱글톤 & 프로토타입 빈 함께 사용하기 : Provider

alsruds 2024. 1. 4. 16:25
🧐강의🧐
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

 

문제 상황

  • 스프링 컨테이너에 프로토타입 스코프 빈 요청 시, 항상 새로운 객체 인스턴스 생성 후 반환
  • 싱글톤 빈과 함께 사용 시 문제 발생 가능

 

case1) (정상 동작) 프로토타입 스코프 빈만 직접 요청할 경우

🅰️ 클라이언트 A 가 스프링 컨테이너에 프로토타입 빈 요청 시 새로운 객체 생성 (x01)

→ addCount() 메서드 호출 시 count 값 1 증가

→ x01 의 count 값 : 1

 

🅱️ 클라이언트 B 가 스프링 컨테이너에 프로토타입 빈 요청 시 새로운 객체 생성 (x02)

→ addCount() 메서드 호출 시 count 값 1 증가

→ x02 의 count 값 : 1

 

case2) (문제 상황) 싱글톤 빈에서 프로토타입 빈을 주입받아 사용할 경우

싱글톤인 'clientBean' 은 프로토타입 빈의 참조값을 내부 필드에 보관

🅰️ 클라이언트 A 가 clientBean.logic() 호출 시 프로토타입 빈의 addCount() 메서드 실행

→ count 값 : 1

 

🅱️ 클라이언트 B 가 clientBean.logic() 호출 시 프로토타입 빈의 addCount() 메서드 실행

→ clientBean 이 싱글톤이므로 클라이언트 A 가 호출했던 객체와 같은 객체가 반환됨

→ count 값 : 2

 


 

해결 방법

✅ 싱글톤 빈과 프로토타입 빈 함께 사용 시, 항상 새로운 프로토타입 빈을 생성할 수 있는 방법 필요

 

1️⃣ 스프링 컨테이너에 요청하기

- 싱글톤 빈에서 프로토타입 빈을 요청받을 때마다, 스프링 컨테이너에 새로 요청

- 가장 간단한 방법

 

· scope/SingletonWithPrototypeTest1

public class SingletonWithPrototypeTest1 {

    ...

    // 싱글톤에서 프로토타입 빈 사용 확인
    @Test
    void singletonClientUsePrototype() {
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.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("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"); }
    }

    // 싱글톤에서 프로토타입 빈 사용
    @Scope("singleton")
    static class ClientBean {

        @Autowired ApplicationContext applicationContext;

        public int logic() {
            prototypeBean.addCount();
            int count = prototypeBean.getCount();
            return count;
        }
    }
}

ac.getBean() 으로 항상 새로운 프로토타입 빈 생성 확인

 

✔️ DL (Dependency Lookup) 의존 관계 조회/탐색 : 직접 필요한 의존 관계 탐색 (cf. DI : 외부에서 의존 관계 주입)

✔️ 스프링 애플리케이션 컨텍스트 전체 주입받을 시, 컨테이너에 종속적인 코드가 되고, 단위 테스트가 어려워 짐

 

➡️ 지정한 프로토타입 빈을 컨테이너에서 대신 찾아주는 기능 (DL 기능) 을 제공하는 무언가가 필요하다!

 


 

2️⃣ ObjectProvider 사용하기

  • ObjectProvider : 지정한 빈을 컨테이너에서 대신 찾아주는 기능 제공 (DL 서비스)
  • ObjectProvider : ObjectFactory + 편의 기능
  • 별도의 라이브러리 필요 X, 스프링 의존

 

· scope/SingletonWithPrototypeTest1

public class SingletonWithPrototypeTest1 {

    ...

    // 싱글톤에서 프로토타입 빈 사용
    @Scope("singleton")
    static class ClientBean {

        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanProvider;

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

prototypeBeanProvider.getObject 로 항상 새로운 프로토타입 빈 생성

 

✔️ 스프링이 제공하는 기능을 사용하지만, 기능이 단순해 단위테스트나 mock 코드 만들기 편리

 

➡️ ObjectProvider 는 지금 필요한 정도의 DL 기능만 제공한다!

 


 

3️⃣ JSR-330 Provider 사용하기

  • javax.inject.Provider 자바 표준 (스프링 부트 3.0 : jakarta.inject.Provider)
  • get() 메서드 하나로 단순한 기능 구현 가능
  • 별도의 라이브러리 필요
  • 자바 표준 → 스프링이 아닌 다른 컨테이너에서도 사용 가능

· build.gradle 에 라이브러리 추가 & gradle refresh

...

dependencies {
	...

	implementation 'jakarta.inject:jakarta.inject-api:2.0.1' // JSR-330 Provider
}

...

 

· scope/SingletonWithPrototypeTest1

public class SingletonWithPrototypeTest1 {

    ...

    // 싱글톤에서 프로토타입 빈 사용
    @Scope("singleton")
    static class ClientBean {

        @Autowired
        private Provider<PrototypeBean> prototypeBeanProvider;

        public int logic() { ... }
    }
}

provider.get() 으로 항상 새로운 프로토타입 빈 생성

 

✔️ 자바 표준 & 단순한 기능으로 단위테스트나 mock 코드 만들기 쉬움

 

➡️ Provider 는 지금 필요한 정도의 DL 기능만을 제공한다!

 

참고
- ObjectProvider, JSR-330 Provider 등은 프로토타입 뿐만이 아니라 DL 이 필요한 경우라면 언제든지 사용 가능
- ObjectProvider : DL 을 위한 편의 기능 제공, 스프링 외 별도 라이브러리 필요 없음
- JSR-330 Provider : 코드를 스프링이 아닌 다른 컨테이너에서 사용해야 할 경우 사용
- 특별히 다른 컨테이너를 사용할 일이 없다면, 스프링이 제공하는 기능 사용하기