Spring

[Spring] @Lookup - DL(의존관계 조회/탐색)

hail2y 2024. 8. 25. 20:27

Dependency Lookup; 의존관계 조회/탐색

  • 의존관계를 외부에서 주입받는 게 아니라 직접 필요한 의존관계를 찾는 것
import org.springframework.beans.factory.annotation.Lookup;

@Scope("singleton")
    static class ClientBean {

        @Lookup
        public PrototypeBean getPrototypeBean() {
            return null; // 실제 구현에서는 무시
        }
        
        public int logic() {
            PrototypeBean prototypeBean = getPrototypeBean();
            prototypeBean.addCount();
            int count = prototypeBean.getCount();
            return count;
        }
    }

 

⇒ @Lookup 애너테이션을 통해 매번 새로운 PrototypeBean 인스턴스를 주입받을 수 있다. 

 

* 참고 상황

 Suppose singleton bean A needs to use non-singleton (prototype) bean B, perhaps on each method invocation on A. The container creates the singleton bean A only once, and thus only gets one opportunity to set the properties. The container cannot provide bean A with a new instance of bean B every time one is needed.

 

싱글톤 빈 A가 아마도 A에 대한 각 메서드 호출에 싱글톤이 아닌 (프로토타입) 빈 B를 사용해야 한다고 가정하자. 컨테이너는 싱글톤 빈 A를 단 한 번만 생성하므로 속성을 설정할 수 있는 기회를 단 한 번만 얻는다. 컨테이너는 필요할 때마다 빈 A에게 빈 B의 새로운 인스턴스를 제공할 수 없다. [-네이버 파파고]

 

싱글톤 빈은 생성 시점에서만 의존관계를 주입받기 때문에 프로토타입 빈이 새로 생겨도 싱글톤 빈과 함께 유지된다. 

@Lookup

  • Spring 프레임워크에서 사용되는 애너테이션
  • 주로 @Scope("singleton")으로 설정된 빈이 @Scope("prototype")으로 설정된 빈을 사용해야 할 때 활용
  • 즉, 싱글톤 빈 안에서 프로토타입 빈을 주입받을 때 사용
  • 스프링 컨테이너에 있는 싱글톤 빈을 주입하는 대신, 메서드 호출 시점마다 해당 빈을 새로 생성하여 주입

- 애너테이션 설명 및 사용 방법

@Lookup

위에도 많은 설명이 있지만 간단하게 이 부분을 먼저 참고하면, 필드나 생성자에 붙이는 게 아닌 메서드에 붙여 사용하는 것을 알 수 있다.  그리고 초록색 주석을 보면, 이 애너테이션의 역할은 타겟 빈(의 이름)을 조회해 주는 것이라고 한다. (= getBean(class), getBean(String); 프로토타입 빈은 이 과정에서 매번 새로운 빈이 생성된다) 앞에서부터 계속 프로토타입 빈을 언급했지만 사실 이 애너테이션 자체는 특정 스코프에 제한되지는 않는다. 싱글톤 빈도 조회된다는 사실! 그리고 디폴트가 ""로 따로 명시되지 않은 경우에는 타겟 빈을 찾을 때 애너테이션이 붙은 메서드의 반환 타입을 기반으로 찾는다.  

 

The lookup typically involves a prototype bean, as in the scenario described in the preceding section. The Spring Framework implements this method injection by using bytecode generation from the CGLIB library to dynamically generate a subclass that overrides the method.

 

위에 따르면, CGLIB의 바이트코드 조작 라이브러리를 사용해 메서드를 오버라이드하는 서브클래스를 만든다고 한다. CGLIB은 proxy 객체를 생성해 주는데, 이 proxy 객체가 @Lookup이 붙은 메서드를 호출할 때마다 Spring 컨테이너에서 해당 메서드에 대응하는 빈을 찾아서 반환하는 역할을 한다. (proxy 객체에서 해당 메서드 호출을 가로채고 Spring이 자동으로 오버라이드한 메서드를 실행한다 - chatGPT) 그렇기 때문에 return null;이 의미없는 것이다.

 

tip.

Another way of accessing differently scoped target beans is an ObjectFactory/ Provider injection point. See Scoped Beans as Dependencies.

 

인프런 김영한 님의 강의 - 스프링 핵심원리 기본편에서는 사실 이 방법보다  ObjectFactory/ObjectProvider(springframework), Provider(jakarta)의 방법을 더 추천하셨다! 써 놓고보니 @Lookup보다 이들을 다뤘다면 더 좋았을 것 같다는 생각이 들지만... 이 기회에  자세히 알아봐서 유익했다. 

 

이들은 모두 스프링 컨테이너에서 대신 찾아주는 기능만 제공한다. 내가 직접 조회해 보는 것이 아니라 provider를 통해 대신 조회해 본다고 생각하면 된다. 조금 전의 @Lookup처럼 특정 스코프에 제한되지 않고 DL이 필요한 경우에 자유롭게 사용할 수 있다. 

 

1. springframework의 ObjectProvider

  • 스프링에 의존
  • ObjectFactory를 상속하는 인터페이스
  • ObjectFactory보다 더 많은 편의기능들을 제공
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

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

public class SingletonWithPrototypeTest1 {

    @Test
    void prototypeFind() {
        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("singleton")
    static class ClientBean {

        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanProvider;

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

    @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");
        }
    }
}

 

2. springframework의 ObjectFactory

  • 스프링에 의존
  • getObject() 메서드만 있음
  • ObjectProvider보다 더 상위의 인터페이스
import org.springframework.beans.factory.ObjectFactory;

@Scope("singleton") // 생략 가능
    static class ClientBean {

        @Autowired
        private ObjectFactory<PrototypeBean> prototypeBeanProvider;

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

 

3. javax/jakarta의 Provider 

  • 별도의 라이브러리 필요
  • 자바 표준이므로 스프링이 아닌 다른 컨테이너에도 사용 가능
  • 기능이 단순 - get() 메서드만 있음
 import jakarta.inject.Provider;

 @Scope("singleton") // 생략 가능
    static class ClientBean {

        @Autowired
        private Provider<PrototypeBean> prototypeBeanProvider;

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

 

https://docs.spring.io/springframework/docs/current/javadocapi/org/springframework/beans/factory/annotation/Lookup.html

 

Lookup (Spring Framework 6.1.12 API)

An annotation that indicates 'lookup' methods, to be overridden by the container to redirect them back to the BeanFactory for a getBean call. This is essentially an annotation-based version of the XML lookup-method attribute, resulting in the same runtime

docs.spring.io

 

https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-method-injection.html#

 

Method Injection :: Spring Framework

In most application scenarios, most beans in the container are singletons. When a singleton bean needs to collaborate with another singleton bean or a non-singleton bean needs to collaborate with another non-singleton bean, you typically handle the depende

docs.spring.io