본문 바로가기
Spring

스프링 핵심 원리 기본편 - 챕터 9. 빈 스코프

by mingutistory 2023. 4. 2.
728x90

빈 스코프란?

스코프: 빈이 존재할 수 있는 범위

 

다양한 스코프가 있음

싱글톤: 기본 스코프. 시작부터 종료까지 유지되는 가장 넓은 범위.

프로토타입: 빈의 생성과 의존관계 주입까지만 관여하고 그 이후는 관리하지 않는 스코프.

 

웹 관련 스코프

request: 웹 요청이 들어오고 나갈때 까지 유지

session: 세션이 생성되고 종료될 때까지 유지

application: 웹의 서블릿 컨텍스트와 같은 범위

 

 

프로토타입 스코프

싱글톤 스코프 빈 -> 항상 같은 인스턴스의 스프링 빈을 반환함.

프로토타입 스코프 빈 -> 요청 시점에 빈을 생성하고 의존 관계 주입함 -> 스프링 컨테이너는 빈을 반환했다가 같은 요청이 왔을 때 새로운 빈을 다시 생성해서 반환 함.

=> 생성된 프로토타입 빈을 관리하지 않음

=> 관리 책임이 클라이언트에게로 가게 되고, @PreDestroy 같은 종료 메서드가 호출되지 않음.

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

        @PreDestroy
        public void destroy() {
            System.out.println("PrototypeBean.destroy");
        }
    }
    
// 결과
find prototypeBean1
PrototypeBean.init
find prototypeBean2
PrototypeBean.init
bean1 = hello.demo.scope.PrototypeTest$PrototypeBean@306851c7
bean2 = hello.demo.scope.PrototypeTest$PrototypeBean@12bcd0c0

프로토타입 빈은 스프링에서 관리하지 않기 때문에 종료 메서드(destroy())가 ac.close()로 컨테이너가 종료됬을 때도 호출되지 않는 것을 확인 할 수 있음.

=> 이런 경우 직접 prototypeBean1.destory() 메소드 호출을 통해 클라이언트에서 직접 호출해줘야 한다.

 

 

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

    @Scope("singleton")
    static class ClientBean {
        private final PrototypeBean prototypeBean; // 생성시점에 주입됨

        @Autowired
        public ClientBean(PrototypeBean prototypeBean) {
            this.prototypeBean = prototypeBean;
        }

        public int logic() {
            prototypeBean.addCount();
            return prototypeBean.getCount();
        }
    }

생성 시점에서 PrototypeBean을 주입 받게 되면서 원하던 로직과는 다른 로직이 실행 되게 될 것임.

ClientBean1, ClientBean2가 생겨서 새로운 클래스에 각각 주입 받게 되면 새로운 Bean을 주인 받게 될 것임.

 

 

싱글톤 빈과 함께 사용시 Provider로 문제 해결

가장 간단한 방법 - 싱글톤 빈이 프로토타입 빈을 요청 시 마다 스프링 컨테이너에 요청하기 -> ApplicationContext를 주입 받아서 사용해버리기 (무식)

=> Dependency Lookup (DL) 의존관계 조회 관계

=> 스프링 컨테이너에 종속적인 코드. 단위 테스트가 어려워 짐.

 

ObjectFactory, ObjectProvider

    @Scope("singleton")
    static class ClientBean {
        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanObjectProvider;

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

ObjectFactory (인터페이스) - 기능이 단순, 별도 라이브러리 필요 없음. 스프링 의존.

ObjectProvider (상속 받음) + 관련 기능 추가 제공 - 스프링 컨테이너 조회를 도와주는 대리자. 스프링에 의존.

--> 패키지 자체가 springframework.. 

 

JSR-330 Provider

자바 표준 사용 -> 단위 테스트를 만들거나 mock 코드를 만들기 편리하고 스프링 컨테이너 이외에도 사용 가능.

gradle에 라이브러리 추가 필요.

 

 

프로토 타입빈은 언제 사용할까?

매번 사용시마다 의존관계 주입이 완료된 새로운 객체가 필요할 때 사용 필요 --> 웹 애플리케이션에서 개발해보면 싱글톤 빈으로 대부분 해결 가능함. 직접적으로 사용하는 일은 매우 드묾.

 

스프링 - 자바 표준에서 제공하는 기능이 겹칠 때: 스프링이 제공하는 기능을 사용하는 것이 편할 수 있음. (현재는..)

 

 

웹 스코프

싱글톤은 스프링 컨테이너의 시작과 끝까지 함께하는 매우 긴 스코프.

웹 환경에서만 동작

 

종류

request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프. 요청마다/ 별도의 빈 인스턴스 생성 됨.

session: HTTP Session과 동일

application: 서블릿 컨텍스트와 동일한 생명 주기

websocket: 웹 소켓과 동일한 생명주기

 

 

request 스코프 예제 만들기

웹 환경에서만 동작함으로 라이브러리 추가 필요함

spring-boot-starter-web (Tomcat 실행됨 - 웹 서버)

웹 관련 추가 설정과 환경들이 필요함으로 AnnotationConfigServletWebServerApplicationContext 기반으로 애플리케이션 구현 됨

 

@Scope(value = "request"): HTTP 요청 당 하나씩 생성, 끝나는 시점에서 소멸 됨.

스프링에서 관리하는 빈이기 때문에 생성(@PostContruct), 제거(@PreDestroy) 메소드 호출 됨.

@Controller
@RequiredArgsConstructor
public class LogDemoController {
    private final LogDemoService logDemoService;
    private final MyLogger myLogger;
    ...
 	// 생성자 주입을 통해서 자동으로 들어감 - 하지만 MyLogger는 scope가 request 임으로 오류 발생   
}

MyLogger가 스코프가 request이기 때문에 애초에 스프링이 띄우는 단계에서는 존재하지 않음으로 오류 발생.

 

사실 로그 남기는 동일한 부분 같은 경우는 스프링 인터셉터, 서블릿 필터와 같은 부분을 이용해서 공통처리 가능함. 

 

스코프와 Provider

@Service
@RequiredArgsConstructor
public class LogDemoService {
     private final ObjectProvider<MyLogger> myLoggerProvider;
     
     public void logic(String id) {
         MyLogger myLogger = myLoggerProvider.getObject();
         myLogger.log("service id = " + id);
     }
}

ObjectProvider.getObject()를 호출하는 시점까지 빈의 생성 지연 가능

Controller -> Service까지의 로직이 하나의 HTTP request이기 때문에 빈의 스코프가 동일한 것이 유지된다.

 

 

스코프와 프록시

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}

클래스: TARGET_CLASS

인터페이스: INTERFACES

가짜 프록시 클래스를 만들어 두고 클래스를 다른 빈에 미리 주입 할 수 있음.

클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입 함. - CGLIB 라이브러리

가짜 프록시 객체는 요청 왔을 때 내부에서 진짜 빈을 요청하는 로직이 들어있음.

 

애노테이션의 설정 변경만으로도 프록시 객체 대체 가능.

 

최소화해서 사용하자!

유지보수가 어려운 코드가 되어버림.

 

300x250

댓글