빈 스코프

강한친구·2022년 4월 13일
0

Spring

목록 보기
7/27

빈의 생성

스프링 컨테이너 생성 동시에 빈이 생성되고 스프링 컨테이너 종료까지 그 생명을 유지한다.

스프링의 다양한 스코프

싱글톤 : 기본 스코프. 컨테이너와 수명을 공유하는 가장 긴 스코프.

프로토타입 : 프로토타입 빈의 생성과 DI까지만 관계하고, 그 뒤는 더 이상 관리하지 않는다.

웹 관련 스코프 :

  • request : 웹 요청이 들어오고 나갈 때 까지 유지된느 스코프
  • session : 웹 세션이 생성되고 종료될 때 가지 유지되는 스코프
  • application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

프토로타입 스코프

싱글톤 스코프는 항상 조회를 하면 같은 인스턴스의 스코프를 반환하는것이 보장된다.
하지만 프로토타입은 새로 만들어서 반환한다

  1. 프로토타입 스코프의 빈을 요청
  2. 빈을 생성하고 의존관계에 주입을 한다.
  3. 같은 요청이 오면 또 위의 과정을 거쳐서 다시 넣어준다.

핵심
클라이언트에 빈을 반환하고 난 이후, 컨테이너는 이 빈을 관리하지 않는다. 이 때, 호출한 클라이언트가 이를 책임져야하고 종료메서드를 호출하던가 해야한다. (PreDestroy같은게 작동하지 않는다)

정리

  • 싱글톤 빈은 스프링 컨테이너 생성 시점에 초기화 메서드가 실행 되지만, 프로토타입 스코프의 빈은 스프링 컨테이너에서 빈을 조회할 때 생성되고, 초기화 메서드도 실행된다.

  • 프로토타입 빈을 2번 조회했으므로 초기화도 2번 실행되고 다른 빈이 2개 나온다.

  • 싱글톤 빈은 스프링 컨테이너가 관리하기 때문에 스프링 컨테이너가 종료될 때 빈의 종료 메서드가 실행되지만, 프로토타입 빈은 스프링 컨테이너가 생성과 의존관계 주입 그리고 초기화 까지만 관여하고, 더는 관리하지 않는다. 따라서 종료메서드가 실행되지 않고, 직접 실행해야한다.

Find prototypeBean1
PrototypeBean.init // 호출 1
Find prototypeBean2
PrototypeBean.init // 호출 2
pb1 = hello.core.scope.PrototypeTest$PrototypeBean@33065d67
pb2 = hello.core.scope.PrototypeTest$PrototypeBean@712625fd
// 두 값이 다르다.
PrototypeBean.Destroy // 직접 닫아야한다
PrototypeBean.Destroy

싱글톤과 같이 쓰면 생기는 문제

프로토타입의 경우

  1. 클라이언트가 프로토타입을 요청
  2. 그 안에 count가 있다고하면, addCount시 1 올라간다
  3. 다른 클라이언트가 이를 호출하면 새로 만들어주기때문에, count는 2가 아닌 또 1이 나온다.

싱글톤의 경우

clientBean 은 싱글톤이므로, 보통 스프링 컨테이너 생성 시점에 함께 생성되고, 의존관계 주입도 발생한다.

  1. clientBean 은 의존관계 자동 주입을 사용한다. 주입 시점에 스프링 컨테이너에 프로토타입 빈을요청한다.

  2. 스프링 컨테이너는 프로토타입 빈을 생성해서 clientBean 에 반환한다. 프로토타입 빈의 count 값은 0이다.
    이제 clientBean 은 프로토타입 빈을 내부 필드에 보관한다.

  3. 클라이언트 A가 클라이언트 빈을 호출하면 클라이언트빈의 로직이 호출, addCount가 실행되고, count가 1증가한다.

  4. 다른 클라이언트가 이를 도 호출하면, 클라이언트빈이 가지고 있는 내부의 프로토타입빈은 이미 사용이 끝난것이기 때문에, 사용 할 때 마다 새로 생성되는것이 아니라 기존에 만들어진것을 쓴다. 따라서 새로 생성되는것이 아니라 count가 2가 된다.

하지만 이는 우리가 프로토타입을 쓸 때 의도한 결과가 아니다. 사용 할 때 마다 생성해서 쓰기를 원하는것이다.

ObjectFactory, ObjectProvider

        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanProvider;

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

이런식으로 Provider를 하면, 항상 Provider가 새로운 prototype을 만들어준다.

Provider는 Factory를 상속한것이다. 하지만 이들은 스프링 기능이라 스프링에 의존적이다.

JSR-330 Provider

자바표준 프로바이더이다. 라이브러리를 추가하고 사용할 수 있다.

private final Provider<PrototypeBean> prototypeBeanProvider;

        @Autowired
        public ClientBean(Provider<PrototypeBean> prototypeBeanProvider) {
            this.prototypeBeanProvider = prototypeBeanProvider;
        }

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

장점 : 간단하다, 자바표준이라 다른 컨테이너 환경에서도 사용 가능하다.

단점 : get메서드 하나로 매우 단순하고, 자바표준이라 라이브러리 필요하다.

정리

  • 프로토타입은 사용할 때 마다 의존관계 주입이 완료된 새로운 객체가
    필요하면 사용하면 된다.

  • 싱글톤 빈으로 대부분의 문제를해결할 수 있기 때문에 프로토타입 빈을 직접적으로 사용하는 일은 매우 드물다.

  • ObjectProvider , JSR330 Provider 등은 DL이 필요한 경우는 언제든 사용한다.

웹 스코프

  • 웹 스코프는 웹 환경에서만 동작
  • 프로토타입과 다르게 스프링이 해당 스코프 종료 시점까지 관라.

웹 관련 스코프 :

  • request : 웹 요청이 들어오고 나갈 때 까지 유지되는 스코프. 요청마다 각자 다른 스코프를 생성해준다.
  • session : 웹 세션이 생성되고 종료될 때 가지 유지되는 스코프
  • application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

Request 예제

동시에 여러 요청이 오면 로그에 남기기가 어렵다. 이를 해결하기 위해서 리퀘스트 스코프를 사용한다.

리퀘스트 스코프 형태로 요청을 로그로 남기는 로거아 이를 테스트하기 위한 service Controller를 만들고 실행하면
오류가 난다.

이는 request 스코프는 리퀘스트가 들어와야 수행이 되는데, 컨트롤러 쪽에선 리퀘스트가 들어오기전에 이미 이 빈을 주입하려 시도하기 때문이다. 이를 해결하기 위해선 Provider를 써야한다.

Provider

    private final ObjectProvider<MyLogger> myLoggerObjectProvider;
   
   //메서드 안에서 
        MyLogger object = myLoggerObjectProvider.getObject();

이는 getObject할 때, request 스코프 빈을 provider가 만들어주고, 그 후 요청이 들어오면 이 빈에다가 정보를 집어넣어 주는것이다.

핵심은, 여러 요청이 오더라도 각각 기록을 해준다는것이다.

하지만, 이 provider를 안쓰고 할 수 있는 방법이 있다.

프록시

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

이거 한줄만 넣어주면 된다.

이러면 Provider가 없더라도 잘 된다.

proxyNode

대상을 구체적인 클래스, 혹은 인터페이스로 지정해줄 수 있다.
이떄, 가짜 Proxy Mylogger 클래스를 만들어서 주입시켜준다.

myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$a8998c3e

이런식으로 스프링이 조작한 프록시 MyLogger가 나와서 넣어주고, 진짜가 필요해지는 시점에 진짜를 찾아서 실행시켜준다.

이 역시 CGLIB을 쓰는 방식이고 이는 Config에서 Bean 등록시 싱글톤 유지를 위한 CGLIB 라이브러리를 사용한다

가짜 프로시 객체는 실제 리퀘스트 스코프에 위임하는 로직이 있고 싱글톤처럼 동작한다. 따라서 누구나 가져다 쓰더라도 진짜로 연결되게 되어있다.

이렇게 사용하면 리퀘스트 역시 싱글톤 쓰는것처럼 쓸 수 있다.

핵심
결국, 진자 Http 요청이 올때까지는 이 프록시로 버티는것이 핵심이다.
이것이 다형성과 DI컨테이너가 가진 장점이다.

이 방식은 꼭 request만 아니라 다른 곳에서도 사용할 수 있다. (AOP등)

0개의 댓글