1-1. 클라이언트 A는 스프링 컨테이너에 프로토타입 빈을 요청한다.
2-1. 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(x01)한다. 해당 빈(x01)의 count 필드값은 0이다.
3-1. 클라이언트는 조회한 프로토타입 빈(x01)에 accCount()를 호출하면서 count 필드를 +1한다.
결과적으로 프로토타입 빈(x01)의 count는 1이 된다.
1-2. 클라이언트 B도 스프링 컨테이너에 프로토타입 빈을 요청한다.
2-2. 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(x02)한다. 해당 빈(x02)의 count 필드 값은 0이다.
3-2. 클라이언트는 조회한 프로토타입 빈(x02)에 addCount를 호출하면서 count 필드를 +1한다.
결과적으로 프로토타입 빈(x02)의 count는 1이 된다.
클라이언트 B는 clientBean을 스프링 컨테이너에 요청해서 받는다. 싱글톤이므로 항상 같은 clientBean이 반환된다.
여기서 중요한 점은
clientBena이 내부에 갖고있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈이다.
즉, 주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성이 된 것이지, 사용할 때 마다 새로 생성되늰 것이 아니다.
참고로 의존관계를 외부에서 주입받는 방법을 DI라고 하고 위 사진처럼 직접 필요한 의존관계를 찾는 것을 DL(dependency look up/의존관계 조회(탐색))이라 한다.
DL은 조금 더 간결하게 스프링 컨테이너를 통해 해당 빈을 찾아서 반환하는 것이다.
그런데 이게 문제가 위 코드처럼 스프링의 application context 전체를 주입받게되면, 스프링 컨테이너에 종속적인 코드가 되고, 단위 테스트도 어려워진다.
따라서 우리가 필요한, 지정한 프로토타입 빈을 컨테이너에서 대신 찾아주는 DL 기능만 제공되면 되는 무언가가 있으면 된다.
@Scope("singleton")
static class ClientBean{
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic(){
ProtytypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = protytypeBean.getCount();
return count;
}
}
참고로 위에서는 그냥 편하게 필드에 바로 @Autowired를 해버렸는데 @requiredargsconstructor를 넣어도 되긴하다.
위 코드에서 prototypeBeanProvider 빈에서 getObject() 메서드를 호출하면 그때서야 스프링 컨테이너에서 프로토타입 빈을 찾아서 우리한테 반환해주는 것이다.
ObjectFactory와 ObjectProvider는 같은 기능을 제공한다. ObjectProvider는 ObjectFactory를 상속받아서 몇개의 기능을 추가한 것이다. (필요하면 찾아보자!)
ObjectProvider의 근본 기능은 DL을 제공하는 것이다.
내가 직접 스프링 컨테이너 전체(application context)를 DI해서 조회하는 것이 아니라 그 기능을 대신해주는 조회기능(DL)만 갖다가 쓰는 것이라 생각하면 된다.
이 프로바이더는 스프링에 의존적이지 않은 Provider이다.
설치는 아래처럼 다운로드할 코드를 작성하면 되고 직접찾아서 다운로드 해도 된다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// https://mvnrepository.com/artifact/jakarta.inject/jakarta.inject-api
implementation group: 'jakarta.inject', name: 'jakarta.inject-api', version: '2.0.1'
}
위 사진을 보면 get이외의 별도의 메서드는 존재하지 않는다.
다만 자바 표준이 좋은게 위에 4개의 li를 보면 알 수 있듯 설명이 굉장히 잘 되어있는데
lazy, optional retrieval를 사용할 때, 순환의존(A가B를의존, B가A를의존)할 때, 멀티플인스턴스가 필요할 때(프로토타입 빈)
물론 자바 표준이긴 하다 별도로 라이브러리를 다운 받아야한다는 단점이 있다.
그리고 자바 표준이라 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.
프로토타입 빈은 언제 사용할까?
매번 사용할 때 마다 의존관계 주입이 완료된 새로운 객체가 필요하면 사용하면 된다.
그런데 실무에서는 웹 어플리케이션을 개발하다보면 싱글톤 빈으로 대부분의 문제를 해결할 수 있기 때문에 프로토타입 빈을 직접적으로 사용하는 일은 매우 드물다.
@Looup
어노테이션을 사용하는 방법도 있지만, 이전 방법들로 충분하고, @Looup
을 사용할 때 고려해야할 내용이 많아서 따로 배우진 않았다.그럼 2개의 Provider를 배웠는데 뭘 사용하면 될까?
ObjectProvider는 DL을 위한 편의 기능을 많이 제공해주고 스프링 외에 별도의 의존관계 추가가 필요없기 때문에 편리하다.
반면 거의 발생하지 않는 경우이지만 코드를 스프링이 아닌 다른 컨테이너에서도 사용할 수 있어야한다면, 그때 jakarta꺼를 사용하면 된다.
그리고 비단 이 Provider 뿐만 아니라 다른 기능들도 자바 표준과 스프링이 제공하는 기능이 겹칠때가 많은데 이때 대부분 스프링이 더 다양하고 편리한 기능을 제공해주기 때문에 특별히 다른 컨테이너를 사용할 일이 없다면 스프링꺼를 사용하는 것을 추천한다.
다만, JPA를 사용할 때는 (JPA이전 Hivernate를 Java진영에서 먹었기 때문에) 자바 표준을 사용하는 것이 좋다.
그런데 기능이 제약이 많고 불편하다보니 스프링을 많이 사용하기도하도 depecto(사실상 기술 표준)이 되었는데 (AutoWired|@injection)
가끔 스프링에서도 표준을 쓰세요 권장하는게 있기도 하고 기능이 표준껄로도 충분하고한다면(constructor, predestory) 표준꺼를 쓰는게 좋다.
public int someMethod(){
int result = A+B;
return result
}
cmd + opt + N
Ctrl + Alt + N
을 하면 인라인 변수 라고 합쳐주는 간단한 단축키 꿀팁 이었다.