9. 빈 스코프

1. 빈 스코프 - 빈 스코프란?

지금까지 사용한 스프링 빈은 모두 싱글톤 스코프로 생성된 빈이다.

스프링은 다음과 같은 다양한 스코프를 지원한다.

  • 싱글톤: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프이다.
  • 프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프이다.
  • 웹 관련 스코프
    • request: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프이다.
    • session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프이다.
    • application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프이다.

빈 스코프 지정

컴포넌트 스캔 방식

@Scope("prototype")
@Component
public class HelloBean {}

수동 등록

@Scope("prototype")
@Bean
public HelloBean helloBean {return new HelloBean();}

2. 빈 스코프 - 프로토타입 스코프

  • 싱글톤 스코프의 빈 조회: 항상 같은 인스턴스의 스프링 빈을 반환.
  • 프로토타입 스코프의 빈 조회: 항상 새로운 인스턴스를 생성해서 반환한다.

싱글톤 빈 요청

    1. 싱글톤 스코프의 빈을 스프링 컨테이너에 요청한다.
    1. 스프링 컨테이너는 본인이 관리하는 스프링 빈을 반환한다.
    1. 이후에 스프링 컨테이너에 같은 요청이 와도 같은 객체 인스턴스의 스프링 빈을 반환한다.

프로토타입 빈 요청

  • 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리한다
  • 클라이언트에 빈을 반환하고, 이후 스프링 컨테이너는 생성된 프로토타입 빈을 관리하지 않는다.
  • 프로토타입 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에 있다.
  • 따라서 @PreDestroy 같은 소멸 메서드가 호출되지 않는다.

    1. 프로토타입 스코프의 빈을 스프링 컨테이너에 요청한다.
    1. 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입한다.

    1. 스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환한다.
    1. 이후에 스프링 컨테이너에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환한다.

예제 코드

싱글톤 빈 Test

public class SingletonTest {
    @Test
    void singletonBeanFind(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class);

        SingletonBean singletonBean1 = ac.getBean(SingletonBean.class);
        SingletonBean singletonBean2 = ac.getBean(SingletonBean.class);
        System.out.println("singletonBean1 = " + singletonBean1);
        System.out.println("singletonBean2 = " + singletonBean2);
        assertThat(singletonBean1).isSameAs(singletonBean2);

        ac.close();
    }

    @Scope("singleton")
    static class SingletonBean{
        @PostConstruct
        public void init(){
            System.out.println("SingletonBean.init");
        }
        @PreDestroy
        public void destroy(){
            System.out.println("SingletonBean.destroy");
        }
    }
}

출력

SingletonBean.init
singletonBean1 = hello.core.scope.SingletonTest$SingletonBean@146dfe6
singletonBean2 = hello.core.scope.SingletonTest$SingletonBean@146dfe6
[main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@d5b810e
SingletonBean.destroy
    1. 빈 초기화 메서드를 실행한다
    1. 같은 인스턴스의 빈을 조회한다
    1. 종료 메소드까지 정상 호출 된다

프로토타입 빈

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);
        Assertions.assertThat(prototypeBean1).isNotSameAs(prototypeBean2);

        ac.close();
    }

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

출력

find prototypeBean1
SingletonBean.init
find prototypeBean2
SingletonBean.init
prototypeBean1 = hello.core.scope.PrototypeTest$PrototypeBean@7c8c9a05
prototypeBean2 = hello.core.scope.PrototypeTest$PrototypeBean@d41f816
[main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@7c24b813
  • 프로토타입 스코프 빈은 스프링 컨테이너에서 빈을 조회할 때 생성되고, 초기화 메서드도 실행된다.
  • 프로토타입 빈을 2번 조회했으므로 완전히 다른 스프링 빈이 생성되고, 초기화도 2번 실행된 것을 확인할 수 있다.
  • 프로토타입 빈은 스프링 컨테이너가 생성과 의존관계 주입 그리고 초기화 까지만 관여하고, 더는 관리하지 않는다.
  • 따라서 프로토타입 빈은 스프링 컨테이너가 종료될 때 @PreDestroy 같은 종료 메서드가 전혀 실행되지 않는다.
prototypeBean1.destroy();
prototypeBean2.destroy();
  • 프로토타입 빈은 프로토타입 빈을 조회한 클라이언트가 관리해야 하며, 종료 메서드에 대한 호출도 클라이언트가 직접 해야한다.

3. 빈 스코프 - 프로토타입 스코프의 싱글톤 빈과 함께 사용 시 문제점

스프링 컨테이너에 프로토타입 스코프의 빈을 요청하면 항상 새로운 객체 인스턴스를 생성해서 반환한다. 하지만 싱글톤 빈과 함께 사용할 때는 의도한 대로 잘 동작하지 않으므로 주의해야 한다.

프로토타입 빈 직접 요청

프로토타입 빈요청 1

    1. 클라이언트A는 스프링 컨테이너에 프로토타입 빈을 요청한다.
    1. 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(x01)한다.
      해당 빈의 count 필드 값은 0이다.
    1. 클라이언트는 조회한 프로토타입 빈에 addCount() 를 호출하면서 count 필드를 +1 한다.

결과적으로 프로토타입 빈(x01)의 count는 1이 된다.

프로토타입 빈요청 2

    1. 클라이언트B는 스프링 컨테이너에 프로토타입 빈을 요청한다.
    1. 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(x02)한다.
      해당 빈의 count 필드 값은 0이다.
    1. 클라이언트는 조회한 프로토타입 빈에 addCount() 를 호출하면서 count 필드를 +1 한다.

결과적으로 프로토타입 빈(x02)의 count는 1이 된다

예제 코드

PrototypeBean 클래스

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

싱글톤 빈에서 프로토타입 빈 사용

clientBean 이라는 싱글톤 빈이 의존관계 주입을 통해서 프로토타입 빈을 주입받아서 사용하는 예를 보자.

싱글톤에서 프로토타입 빈 사용 1

  • clientBean 은 싱글톤이므로, 보통 스프링 컨테이너 생성 시점에 함께 생성되고, 의존관계 주입도 발생한다.
    1. clientBean 은 의존관계 자동 주입을 사용한다. 주입 시점에 스프링 컨테이너에 프로토타입 빈을 요청한다.
    1. 스프링 컨테이너는 프로토타입 빈을 생성해서 clientBean 에 반환한다.
      프로토타입 빈의 count 필드 값은 0이다.
  • 이제 clientBean 은 프로토타입 빈을 내부 필드에 보관한다. (정확히는 참조값을 보관한다.)

싱글톤에서 프로토타입 빈 사용 2

  • 클라이언트 A는 clientBean 을 스프링 컨테이너에 요청해서 받는다.싱글톤이므로 항상 같은 clientBean이 반환된다.
    1. 클라이언트 A는 clientBean.logic() 을 호출한다.
    1. clientBeanprototypeBeanaddCount() 를 호출해서 프로토타입 빈의 count를 증가한다.
      count값이 1이 된다.

싱글톤에서 프로토타입 빈 사용 3

  • 클라이언트 B는 싱글톤인 clientBean 을 스프링 컨테이너에 요청해서 받는다.
  • clientBean이 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈이다.
    1. 클라이언트 B는 clientBean.logic() 을 호출한다.
    1. clientBeanprototypeBeanaddCount() 를 호출해서 프로토타입 빈의 count를 증가한다.
      원래 count 값이 1이었으므로 2가 된다.

예제코드

ClientBean 클래스

@Scope("singleton")
static class ClientBean{
	private final PrototypeBean prototypeBean; 

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

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

정리

  • 싱글톤 빈이 프로토타입 빈을 사용하게 될 경우, 프로토타입 빈은 싱글톤 빈과 함께 계속 유지된다.
  • 싱글톤 빈은 생성시점에 의존관계를 주입받게 되는데, 생성 시점에 프로토타입 빈이 새로 생성되고 유지되는 것이다.
  • 만일 여러 빈에서 동일한 프로토타입 빈을 주입 받는다면, 각각의 빈에는 다른 프로토타입 빈이 주입되게된다.
  • 개발자가 프로토타입 빈을 사용하는 것은 사용할 때마다 새로운 빈이 생성되는것을 기대하는 것이다.
  • 따라서 프로토타입 빈이 싱글톤 빈 안에 계속 유지되는 것은 의도적인 상황이 아닐 것이다.

4. 빈 스코프 - 싱글톤 빈과 함께 사용 시 Provider 사용

스프링 컨테이너에 요청

ClientBean 클래스

static class ClientBean {
 	@Autowired
 	private ApplicationContext ac;
 	public int logic() {
 		PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
 		prototypeBean.addCount();
 		int count = prototypeBean.getCount();
 		return count;
 	}
    ...
}
  • ac.getBean()을 통해 logic()을 호출하면 항상 새로운 프로토타입 빈이 생성된다.
  • 직접 필요한 의존관계를 찾는 것을 Dependency Lookup (DL) 의존관계 조회(탐색) 이라한다.
  • ApplicationContext 전체를 주입받게 되면, 스프링 컨테이너에 종속적인 코드가 되고, 단위 테스트도 어려워진다.
  • 현재는 DL 정도의 기능만 제공해주는 무언가만 있으면 된다.

ObjectFactory, ObjectProvider

  • 지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공하는 것이 바로 ObjectProvider 이다
  • ObjectFactory에 편의 기능을 추가한 것이 ObjectProvider이다.
static class ClientBean {
	@Autowired
	private ObjectProvider<PrototypeBean> prototypeBeanProvider;
	public int logic() {
 		PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
 		prototypeBean.addCount();
 		int count = prototypeBean.getCount();
 		return count;
	}
    ...
}
  • ObjectProvider의 타입을 PrototypeBean클래스로 지정하게 되면, getObject() 를 통해 스프링 컨테이너에서 해당 빈을 찾아서 반환한다. (DL)
  • 스프링이 제공하는 기능을 사용하지만, 기능이 단순하므로 단위테스트를 만들기 훨씬 쉬워진다.
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.ObjectFactory;
  • ObjectProvider, ObjectFactory는 모두 스프링에 의존적이다.

JSR-330 Provider

  • javax.inject.Provider는 JSR-330 자바 표준을 사용하는 방법이다.
static class ClientBean {
@Autowired
private Provider<PrototypeBean> provider;
	public int logic() {
 		PrototypeBean prototypeBean = provider.get();
 prototypeBean.addCount();
 		int count = prototypeBean.getCount();
 		return count;
	}
    ...
}
  • Provider의 타입을 PrototypeBean클래스로 지정하게 되면, get() 을 통해 스프링 컨테이너에서 해당 빈을 찾아서 반환한다. (DL)
  • 자바 표준이고, 기능이 단순하므로 단위테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬워진다.
  • get() 메서드 하나로 기능이 매우 단순하다.
  • 별도의 라이브러리가 필요하다.
  • 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.

ObjectProvider , JSR330 Provider 등은 프로토타입 뿐만 아니라 DL이 필요한 경우는 언제든지 사용할 수 있다.

ObjectProvider vs JSR330 Provider

  • 코드를 스프링이 아닌 다른 컨테이너에서도 사용할 수 있어야 한다면 JSR-330 Provider를 사용해야한다.
  • 대부분 스프링이 더 다양하고 편리한 기능을 제공해주기 때문에, 특별히 다른 컨테이너를 사용할 일이 없다면, 스프링이 제공하는 기능을 사용하면 된다.

5. 빈 스코프 - 웹 스코프

웹 스코프의 특징

  • 웹 스코프는 웹 환경에서만 동작한다.
  • 웹 스코프는 프로토타입 스코프와 달리 스프링이 해당 스코프의 종료시점까지 관리한다.
  • 즉, 종료 메소드(@Predestroy)가 호출된다.

웹 스코프의 종류

  • request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리된다.
  • session: HTTP Session과 동일한 생명주기를 가지는 스코프
  • application: 서블릿 컨텍스트(ServletContext)와 동일한 생명주기를 가지는 스코프
  • websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프

request 스코프의 할당

  • 클라이언트A가 HTTP request 요청을 보낼 경우
    • 만일 Controller에서 request scope을 사용할 경우, A전용 인스턴스를 참조한다.
    • 그 후, Service에서 request scope을 사용할 경우, 동일한 A전용 인스턴스를 참조한다.
  • 클라이언트B가 HTTP request 요청을 보낼 경우
    • 만일 Controller에서 request scope을 사용할 경우, B전용 인스턴스를 참조한다.
    • 그 후, Service에서 request scope을 사용할 경우, 동일한 B전용 인스턴스를 참조한다.
  • 즉 HTTP request를 요청한 클라이언트 별로 빈을 관리한다.

6. 빈 스코프 - request 스코프 예제

동시에 여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵다. 이럴때 사용하기 좋은것이 바로 request 스코프다.

  • 각각의 HTTP 요청(Request)마다 빈이 존재하기 때문이다.

예제 코드

MyLogger 클래스

@Component
@Scope(value = "request")
public class MyLogger {
    private String uuid;
    private String requestURL;

    public void setRequestURL(String requestURL) {
        this.requestURL = requestURL;
    }

    public void log(String message) {
        System.out.println("["+uuid+"]"+"["+requestURL+"] "+message);
    }

    @PostConstruct
    public void init(){
        uuid = UUID.randomUUID().toString();
        System.out.println("["+uuid+"] request scope bean created: "+this);
    }

    @PreDestroy
    public void close(){
        System.out.println("["+uuid+"] request scope bean close: "+this);
    }
}
  • 로그를 출력하기 위한 클래스다.
  • @Scope설정을 request로 하였으므로, HTTP요청 하나당 하나의 빈이 생성되며, 요청이 끝나는 시점에 소멸된다.
  • @PostConstruct를 통해 빈 등록이 끝나면 UUID를 설정한 후, 빈이 생성되었다는 로그를 남긴다.
  • UUID를 통해 HTTP요청을 구별할 수 있다.
  • @Predestroy를 통해 빈의 소멸 직전에 소멸되었다는 로그를 남긴다.
  • 빈의 생성시점에 URL을 알 수 없으므로, 외부에서 setter를 통해 설정한다.

LogDemoController 클래스

@Controller
@RequiredArgsConstructor
public class LogDemoController {

    private final LogDemoService logDemoService;
    private final MyLogger myLogger;

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        String requestURL = request.getRequestURL().toString();
        myLogger.setRequestURL(requestURL);

        myLogger.log("controller test");
        logDemoService.logic("testId");
        return "OK";
    }
}
  • 외부에서 log-demo라는 경로로 접속하면, 해당 컨트롤러가 매핑되며 log-demo메소드가 호출된다.
  • getRequestURL()을 통해 접속한 URL정보를 저장한다.
  • 원래는 View를 통해 화면을 띄워주어야하지만, 간단하게 @Responsebody로 문자를 반환한다.
  • MyLogger는 HTTP요청마다 하나씩 생성되므로, 섞일일이 없다.
  • 컨트롤러에서 controller test라는 로그를 남긴다.

LogDemoService 클래스

@Service
@RequiredArgsConstructor
public class LogDemoService {
    private final MyLogger myLogger;

    public void logic(String id) {
        myLogger.log("service id = " + id);
    }
}
  • LogDemoController에서 호출하며 전달한 ID를 로그로 찍는다.
  • request scope를 사용하지 않고 파라미터로 이 모든 정보(URL, UUID)를 서비스 계층에 넘길 수 있다.
  • 문제는 웹과 관련된 정보가 웹과 관련없는 서비스 계층까지 넘어가게 된다.
    • 웹과 관련된 부분은 컨트롤러까지만 사용해야 한다.
  • 서비스 계층은 웹 기술에 종속되지 않고, 가급적 순수하게 유지하는 것이 유지보수 관점에서 좋다.
  • request scope의 MyLogger 덕분에 이런 부분을 파라미터로 넘기지 않고, MyLogger의 멤버변수에 저장해서 코드와 계층을 깔끔하게 유지할 수 있다.

출력

기대 출력

[d06b992f...] request scope bean create
[d06b992f...][http://localhost:8080/log-demo] controller test
[d06b992f...][http://localhost:8080/log-demo] service id = testId
[d06b992f...] request scope bean close

실제 출력

Error creating bean with name 'myLogger': Scope 'request' is not active for the 
current thread; consider defining a scoped proxy for this bean if you intend to 
refer to it from a singleton;
  • 위와 같은 오류가 나는 것은 당연하다.
  • 스프링 실행 시, LogDemoService클래스와 LogDemoController클래스는 싱글톤 빈으로 컨테이너에 등록된다.
  • 빈 생성 후, DI 시점에서 LogDemoController가 의존관계 주입을 위해 MyLogger를 요청하지만, MyLogger는 request scope으로 아직 생성되지 않았다.
  • 따라서 ScopeNotActiveException 오류가 발생한다.

7. 빈 스코프 - 스코프와 Provider

ObjectProvider는 지정한 빈을 getObject()를 호출한 시점에 스프링 컨테이너에 요청해서 제공해주는 기능이다.

수정 코드

LogDemoController 클래스

@Controller
@RequiredArgsConstructor
public class LogDemoController {

    ...
    private final ObjectProvider<MyLogger> myLoggerProvider;

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) {
        String requestURL = request.getRequestURL().toString();
        MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.setRequestURL(requestURL);
		...
    }
}

LogDemoService 클래스

@Service
@RequiredArgsConstructor
public class LogDemoService {
	private final ObjectProvider<MyLogger> myLoggerProvider;

    public void logic(String id) {
    	MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.log("service id = " + id);
    }
}
  • MyLogger에 대한 의존관계 주입을 바로 받지 않고, ObjectProvider를 통해 ObjectProvider.getObject() 를 호출하는 시점까지 request scope 빈의 생성을 지연하였다.
  • ObjectProvider.getObject() 를 호출하시는 시점에는 HTTP 요청이 진행중이므로 request scope 빈의 생성이 정상 처리된다.
[bf5723cc-5b03-4159-9028-02755f80a027] request scope bean created: hello.core.common.MyLogger@3beda921
[bf5723cc-5b03-4159-9028-02755f80a027][http://localhost:8080/log-demo] controller test
[bf5723cc-5b03-4159-9028-02755f80a027][http://localhost:8080/log-demo] service id = testId
[bf5723cc-5b03-4159-9028-02755f80a027] request scope bean close: hello.core.common.MyLogger@3beda921
  • ObjectProvider.getObject()LogDemoController , LogDemoService 에서 각각 한번씩 따로 호출해도 같은 HTTP 요청이면 같은 스프링 빈(MyLogger@3beda921)이 반환된다.

8. 빈 스코프 - 스코프와 프록시

코드

  • LogDemoController, LogDemoService 클래스는 Provider사용 이전의 코드와 동일하다.
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {...}
  • @Scope의 속성에 proxyMode = ScopedProxyMode.TARGET_CLASS를 추가하면된다.
    • 적용 대상이 인터페이스가 아닌 클래스면 TARGET_CLASS를 선택
    • 적용 대상이 인터페이스면 INTERFACES를 선택
  • 프록시 클래스를 사용할 경우, HTTP request와 상관 없이 가짜 프록시
    클래스를 다른 빈에 미리 주입해 둘 수 있다.

웹 스코프와 프록시 동작 원리

  • MyLogger의 클래스를 확인해보면, 순수 MyLogger클래스가 아닌, MyLogger$$EnhancerBySpringCGLIB이라는 객체가 대신 등록되어있다.
  • 이는 프록시 설정을 통해 스프링 컨테이너는 바이트코드 조작 라이브러리인 CGLIB을 이용하여, MyLogger를 상속받은 가짜 프록시 객체를 생성하여 컨테이너에 등록한다.
  • 즉, 이름은 myLogger이지만, MyLogger$$EnhancerBySpringCGLIB객체인 것이다.
  • 그 결과, 가짜 프록시 객체가 의존관계 주입에 사용된다.
  • 가짜 프록시 객체는 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.
  • 가짜 프록시 객체는 내부에 진짜 myLogger를 찾는 방법을 알고 있다.
  • 클라이언트가 myLogger.logic() 을 호출하면 사실은 가짜 프록시 객체의 메서드를 호출한 것이다.
  • 가짜 프록시 객체는 request 스코프의 진짜 myLogger.logic() 를 호출한다.
  • 가짜 프록시 객체는 원본 클래스를 상속 받아서 만들어졌기 때문에 이 객체를 사용하는 클라이언트 입장에서는 사실 원본인지 아닌지도 모르게, 동일하게 사용할 수 있다 \to 다형성의 장점

정리

  • CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다.
  • 가짜 프록시 객체는 실제 요청이 오면 그때 내부에서 실제 빈을 요청하는 위임 로직이 들어있다.
  • 가짜 프록시 객체는 실제 request scope와는 관계가 없으며, 싱글톤처럼 동작한다.
  • 프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯이 편리하게 request scope를 사용할 수 있다.
  • 프록시와 Provider의 핵심 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점이다.
  • 단지 어노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체할 수 있다.
    \to 다형성과 DI 컨테이너의 큰 강점
  • 꼭 웹 스코프가 아니어도 프록시는 사용할 수 있다.

주의점

  • 마치 싱글톤을 사용하는 것 같지만 다르게 동작하기 때문에 결국 주의해서 사용해야 한다.
  • 이런 특별한 scope는 꼭 필요한 곳에만 최소화해서 사용하자, 무분별하게 사용하면 유지보수하기 어려워진다.

출처: 인프런 스프링 핵심 원리 - 기본편 (김영한)
인프런 스프링 핵심 원리

0개의 댓글