Dependency LookUp


DL(Dependency LookUp)


DL이란?

Dependecy LookUp은 DI와 반대로 스프링 컨테이너에 등록된 빈을 찾는 것을 말한다.

ApplicationContext에서 제공해주는 getBean() 메소드를 통해서 DL을 할 수 있다.


예시 코드

AnnotationConfigApplicationContext ac =
            new AnnotationConfigApplicationContext(ClientBean.class, ProtoTypeBean.class);

ClientBean clientBean1 = ac.getBean(ClientBean.class);


출력결과
com.SingletonWithPrototype$ClientBean@3c7c886c


Scope


용어 정리

스코프 : 존재할 수 있는 범위를 가리키는 말이다.

빈 스코프 : 빈 오브젝트가 만들어져 존재할 수 있는 범위이다.


Scope의 종류

스코프설명
싱글톤기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
프로토타입스프링 컨테이너가 프로토타입 빈의 생성과 DI 까지만 관여하고 그 이후는 관리하지 않는 스코프
요청Http 요청마다 별도의 빈 인스턴스가 생성되고, 관리된다.
세션Http Session과 동일한 생명주기를 가지는 스코프
어플리케이션ServletContext와 동일한 생명주기를 가지는 스코프
웹소켓웹 소켓과 동일한 생명주기를 가지는 스코프


프로토타입 스코프

[ 프로토타입 스코프란? ]

프로토타입 스코프의 경우에는 스프링 컨테이너가 빈을 생성하고 DI 해주는 것 까지만 관리한다.

그 후로는 더 이상 관리를 하지 않는다. 여기서 더 이상 컨테이너가 관리하지 않는다. 라는 의미는

새로운 요청이 들어올 때 마다 프로토타입 스코프의 경우에는 매번 새로운 인스턴스를 반환하게 된다.

예제 코드
public class PrototypeTest {

    @Test
    void prototypeBeanFind() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
        System.out.println("find prototypeBean1");
        PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);

        System.out.println("find prototypeBean2");
        PrototypeBean prototypeBean2= ac.getBean(PrototypeBean.class);

        System.out.println("prototypeBean1 = " + prototypeBean1);
        System.out.println("prototypeBean2 = " + prototypeBean2);

        assertThat(prototypeBean1).isNotSameAs(prototypeBean2);
        ac.close();
    }

    @Scope("prototype")
    static class PrototypeBean {
        @PostConstruct
        public void init() {
            System.out.println("prototype Init");
        }

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


출력결과


출력 결과를 보면 DI든 DL이든 새로운 인스턴스를 반환하는 것을 알 수 있다.


[ 프로토타입 빈의 용도 ]

프로토타입 빈은 코드에서 new로 오브젝트를 생성하는 것을 대신하기 위해 사용된다.

사용자의 요청별로 독립적인 정보나 작업 상태를 저장해둘 오브젝트를 만들 필요가 있다.

이 경우에는 대부분 new 키워드나 팩토리(factory)를 이용해 코드 내에 직접 오브젝트를

만들면 된다. 하지만 DI가 필요한 경우라면 프로토타입 빈을 사용해야한다.


[ 프로토타입 빈의 생명주기와 종속성 ]

프로토타입 빈은 컨테이너가 초기 생성 시에만 관여하고 DI 한 후에는 더 이상 신경쓰지 않기

때문에 빈 오브젝의 관리는 전적으로 DI 받은 오브젝트에 달려있다. 따라서 프로토타입 빈은

이 빈을 주입받은 오브젝트에 종속적일 수밖에 없다.

만약 프로토타입을 주입 받은 빈이 싱글톤이라면, 이 프로토타입 빈의 생명주기는 싱글톤 생명주기를

따를것이다. 반대로 프로토타입보다 더 짧은 생명주기를 갖는 빈에 이 프로토타입빈이 주입되었다면,

더 짧은 생명주기를 따라가게 된다.


[ 싱글톤 빈과 함께 사용시 문제점 ]

위에서 프로토타입 빈이 싱글톤 빈에 주입이 되면, 생명주기가 더 긴 싱글톤 빈의 생명주기를

따른다고 했다. 이번엔 이 경우에 생길 문제에 대해서 정리해보았다.


⒈ 싱글톤 빈 S가 프로토타입 빈인 P를 주입 받는다.

P의 생명주기는 S의 생명주기를 따른다.

손님1이 치킨을 1마리 장바구니에 넣었다. (P에 카운트 로직이 있다고 가정.)

손님2도 치킨을 1마리 장바구니에 넣었다.

손님1이 결제를 하려고 보니 치킨이 2마리가 된것을 확인했다.


위의 시나리오에서 생긴 문제의 이유는 프로토타입 빈이 싱글톤 빈의 생명주기를 따르기 때문이다.


[ 프로토타입 빈의 DL 전략 ]

⒈ getBean

에제 코드
@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);
}


위의 예제 코드처럼 getBean() 메서드로 직접 필요한 의존관계를 DL을 하면된다.

하지만 이렇게 스프링의 ApplicationContext를 전체를 주입받게 되면, 스프링 컨테이너에

종속적인 코드가 되고, 단위 테스트도 어려워진다.


⒉ ObjectFactory, ObjectProvider

에제 코드
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;

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


직접 Application Context를 사용하지 않으려면 중간에 컨텍스트에서 getBean()을 호출해주는

역할을 맡은 오브젝트를 두면 된다. 이렇게 되면 ApplicationContext의 getBean()처럼 너무

로우 레벨의 API를 사용하지 않기 때문에 코드가 깔끔할뿐더러, 테스트 코드 작성에도 유리하다.


⒊ JSR-330 Provider

에제 코드
//implementation 'javax.inject:javax.inject:1' gradle 추가 필수 @Autowired
private Provider<PrototypeBean> provider;

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


Provider는 자바 표준이며 기능이 단순하므로 단위 테스트를 만들거나 mock코드를 만들기 훨씬 쉽다.


웹 스코프


요청 스코프

[ 요청 스코프 빈이란? ]

요청 스코프는 하나의 웹 요청 안엥서 만들어지고 해당 요청이 끝날 때 제거된다.

각 요청별로 독립적인 빈이 만들어지기 때문에 빈 오브젝트 내에 상태 값을 저장해둬도

안전하다.


[ 웹 스코프빈 DL ]

요청 스코프 빈은 컨텍스트 초기화 시점에서는 만들어 질 수조차 없다. 왜냐면 아무런

웹 요청이 들어오지 않았기 때문이다. 따라서 요청 스코프 빈을 싱글톤 빈에 그냥 DI하면

컨텍스트 초기화 중 에러가 발생할 것이다.

결국 요청 스코프 빈은 프로토타입 빈과 마찬가지로 Provider나 ObjectFactory 같은 DL 방식을

사용해야 한다.


[ 웹 스코프와 프록시 ]

컨텍스트 초기화 시점에 직접 스코프 빈을 DI하는 대신 스코프 빈에 대한 프록시를 DI 해주는 것이다.

@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)