클라우드 낚시꾼

[Spring] 의존관계 조회, 탐색 (Dependency Lookup) / ObjectProvider / JSR-330 Provider 본문

BE Framework/SpringBasic

[Spring] 의존관계 조회, 탐색 (Dependency Lookup) / ObjectProvider / JSR-330 Provider

KanuBang 2024. 3. 26. 16:47
728x90

1. 의존관계 조회, 탐색 (Dependency Lookup)

Dependency Lookup

 

Dependency Lookup(DL)은 직접 필요한 의존관계를 찾는 것을 의미한다. 외부에서 의존관계 주입을 받는 Dependency Injection과는 다른 개념이다. 스프링 컨테이너에서 스프링 빈을 조회하는 대표적인 방법인 getBean()도 DL에 해당한다. 

 

DL은 왜 필요할까?

DI와 @Autowired를 사용하면 외부에서 의존관계를 자동으로 주입 받을 수 있다. 그래서 DL처럼 직접 필요한 의존관계를 찾는 것이 왜 필요한지 의문을 가질 수 있다. 아래의 문제로 DL이 어떤 상황에서 필요한 지 알아보자.


2. 싱글톤 빈이 프로토타입 빈을 DI 받는 상황

싱글톤 빈이 프로토타입 빈을 DI 받는 상황

 

상황 설명

위의 상황은 다음과 같다.

  • clientBean은 싱글톤
  • 싱글톤 clientBean의 필드 중 하나는 프로토타입 스코프 빈을 DI 받음
  • 클라이언트 A와 B는 싱글톤 clientBean을 사용
  • 클라이언트 A와 B는 싱글톤 빈의 메서드 logic을 통하여 프로토타입 스코프 빈의 내부 필드인 count 값을 1 증가시켜 count=1 로 만드는 것을 목표로 함

클라이언트 A와 B의 착각

클라이언트 A와 B는 서로 다른 프로토타입 빈을 사용한다고 생각하고 있다. 하지만, 각각의 클라이언트가 로직을 호출한 후, 프로토타입 빈의 필드인 count 값이 1이 되기를 기대했지만, 실제 확인해보니 2였다.

 

생성된 프로토타입 빈은 서로 다른 인스턴스이다.

일반적으로, 프로토타입 스코프 빈은 조회 시점에 생성되어 각각 서로 다른 인스턴스로 존재한다. 이런 특성 때문에, 클라이언트 A와 B는 프로토타입 빈을 사용할 때 서로 다를 것이라고 기대했을 것이다. 하지만, 싱글톤 clientBean이 생성 시점에 프로토타입 빈을 의존관계 주입을 받기 때문에, 프로토타입 빈이 새로 생성되기는 하지만, 싱글톤 빈과 함께 계속 같은 인스턴스로 유지된다. 그렇기에 결과적으로 클라이언트 A와 B는 서로 같은 프로토타입 빈을 사용하게 되었고, 같은 빈의 count 값을 각각 한 번씩 1 증가시켰기 때문에 count 값이 2가 된 것이었다. 


3. 싱글톤 빈 안에서 프로토타입 빈을 함께 사용할 때, DL로 새로운 프로토타입 빈 생성하기

싱글톤 빈 안에서 프로토타입 빈을 함께 사용할 때, Dependency Lookup을 이용하면 항상 새로운 프로토타입 빈을 생성할 수 있다. 이제 다양한 DL 방법을 알아보자. 

# DL 방법1: 싱글톤 빈이 프로토타입을 사용할 때 마다 스프링 컨테이너에 새로 요청

@Scope("singleton")
static class ClientBean {
    private ApplicationContext ac;
    public ClientBean(ApplicationContext ac) {
        this.ac = ac;
    }
    public int logic() {
        PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
        prototypeBean.addCount();
        int count = prototypeBean.getCount();
        return count;
    }
}

싱글톤 ClientBean이 스프링 컨테이너의 인스턴스인 ApplicationContext를 주입 받아 프로토타입 빈을 사용할 때 마다 해당 빈을 조회(getBean)하고 있다. 이 조회 시점에 항상 새로운 프로토타입 빈이 생성되게 된다. 그런데 이렇게 스프링의 Application Context 전체를 주입받게 되면, 스프링 컨테이너에 종속적인 코드가 되고, 단위 테스트도 어려워진다. 지금 필요한 기능은 지정한 프로토타입 빈을 컨테이너에서 대신 찾아주는 DL 정도의 기능만 제공하는 무언가이다.

# DL 방법2: ObjectFactory, ObjectProvider

@Scope("singleton")
static class ClientBean {
    private ObjectProvider<PrototypeBean> prototypeBeanObjectProvider;
    public ClientBean(ObjectProvider<PrototypeBean> prototypeBeanObjectProvider) {
        this.prototypeBeanObjectProvider = prototypeBeanObjectProvider;
    }
    public int logic() {
        PrototypeBean prototypeBean = prototypeBeanObjectProvider.getObject();
        prototypeBean.addCount();
        int count = prototypeBean.getCount();
        return count;
    }
}

지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공하는 것이 바로 ObjectProvider이다. 참고로 과거에는 ObjectFactory가 있었는데, 여기에 편의 기능을 추가한 것이 ObjectProvider이ObjectProvider는 Application Context에 의존하지 않고 DL 기능을 단순하게 제공하기에 단위 테스트 하기에 더 쉽다. 하지만, ObjectProvider 역시 스프링이 제공하는 기능이기 때문에 스프링에 의존적인 문제가 남아있다.

# DL 방법3: JSR-330 Provider

JSR-330 Provider는 get 메서드로 DL 기능을 단순하게 제공하는 자바 표준 기술이다. ObjectProvider는 스프링 기술이지만 JSR-330 Provider는 자바 표준 기술이라는 점에서 범용성은 JSR-330 Provider가 더 좋다. 하지만, JSR-330 Provider는 get 메서드로 DL 기능만 제공하기 때문에 기능이 매우 단순하다는 단점이 있으나 ObjectProvider는 상대적으로 더 다양한 DL 편의 기능을 제공한다. 또한, JSR-330 Provider을 사용하기 위해서는 별도의 라이브러리를 추가해야 한다는 점에서 약간의 불편한 점이 있다. 다음은 JSR-330 Provider을 사용하는 예시 코드이다.

 

먼저, 라이브러리를 gradle에 추가해야 한다. (gradle refresh를 잊지 말자.)

// 스프링부트 3.0 미만
implementation 'javax.inject:javax.inject:1'

// 스프링부트 3.0 이상
implementation 'jakarta.inject:jakarta.inject-api:2.0.1'

 

JSR-330 Provider 예시 코드

import jakarta.inject.Provider;

@Scope("singleton")
static class ClientBean {
    private Provider<PrototypeBean> prototypeBeanObjectProvider;
    public ClientBean(Provider<PrototypeBean> prototypeBeanObjectProvider) {
        this.prototypeBeanObjectProvider = prototypeBeanObjectProvider;
    }
    public int logic() {
        PrototypeBean prototypeBean = prototypeBeanObjectProvider.get();
        prototypeBean.addCount();
        int count = prototypeBean.getCount();
        return count;
    }
}

 

ObjectProvider나 JSR-330 Provider나 메서드 명만 약간 다를 뿐 사용하는 방법은 거의 같은 것을 확인할 수 있다.

ObjectProvider VS JSR-330 Provider 

두 기능 모두 DL을 위해 사용된다. 대부분 상대적으로 더 많은 DL 관련 기능을 제공하는 ObjectProvider을 사용한다. 하지만, 스프링 컨테이너가 아닌 다른 곳에서 사용해야 한다면 JSR-330 Provider을 사용하자.


4. 세 줄 요약 (+출처)

  1. DL은 객체가 직접 의존관계를 찾는 것을 의미한다. DI는 객체가 의존관계를 외부에서 주입 받는 것을 의미한다. 
    • DL: 애기가 스스로 밥을 먹는다.
    • DI: 엄마가 애기에게 밥을 먹여준다.
  2. DL의 방법에는 스프링 ApplicationContext의 getBean 조회 / ObjectProvider 조회 / JSR-330 Provider 조회 방법이 있다.
  3. 스프링에서 더 다양한 DL 관련 기능을 제공하는 ObjectProvider을 사용한다. 하지만, 스프링 컨테이너 외의 다른 곳에서 DL을 사용한다면 단순 DL 기능만 제공하는 자바 표준 JSR-330 Provider을 사용하자.
 

스프링 핵심 원리 - 기본편 강의 - 인프런

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢

www.inflearn.com

 

728x90