Study/SpringBoot

Spring Been 객체와 프록시 패턴

kanado 2025. 3. 10. 16:41

빈(Been)은 스프링 IoC/DI 컨테이너의 핵심 관리 대상이며, 주로 의존성 주입 (DI) 을 통해 빈을 프레임워크로부터 주입받아 사용하는 것이 일반적이다.

사용 이유

  1. 객체 관리의 효율성 : 스프링 컨테이너가 빈의 생성, 소멸, 의존성 관리를 자동으로 처리해주므로 개발자는 객체 관리에 대한 부담을 덜고 비즈니스 로직에 집중할 수 있다.
  2. 컴포넌트 재사용성 및 유지보수성 향상: 빈은 독립적인 단위로 설계되어 재사용성이 높고, 설정과 코드가 분리되어 유지보수가 용이하다.

빈의 스코프(Scope)

스프링 빈은 생성과 관리의 범위를 지정할 수 있는 스코프가 있다. 스코프에 따라 빈이 생성되고 유지되는 방식이 달라지며, 이를 통해 애플리케이션 요구사항에 맞는 객체 관리를 수행할 수 있다.

  1. Singleton (기본 스코프)
    • 애플리케이션 전체에서 단 하나의 객체 인스턴스만을 유지
    • 스프링 컨테이너가 구동될 때 빈을 한 번만 생성하고, 이후에는 동일한 객체를 반환
    • 가장 일반적인 스코프이며, 스프링 Bean의 기본 스코프
  2. Prototype
    • 빈을 요청할 때마다 새로운 객체 인스턴스를 생성
    • 상태를 가지는 객체를 매번 새롭게 필요로 하는 경우에 사용
    • 요청 시점마다 새 객체가 생성되므로, 소멸 시점 관리는 컨테이너가 아닌 사용자가 직접 담당해야 하는 경우가 많습니다. 여전히 GC 대상에 포함은 된다.
  3. Request (@Scope("request"))
    • 웹 애플리케이션 환경에서, HTTP 요청 당 하나의 빈 인스턴스가 생성되고 요청이 끝나면 소멸.
    • WebApplicationContext에서만 동작. ApplicationContext에서 동작 X
    • 프록시 사용이 필요하다.
    • 예) 클라이언트가 /submitForm 요청을 보냄 → 새로운 RequestScopedBean 인스턴스 생성됨.
  4. Session (@Scope("session"))
    • 각 사용자(Session)마다 하나의 빈 인스턴스를 생성하고, 세션이 유지되는 동안 계속 사용됨.
    • 사용자별 세션 범위로 객체를 관리해야 할 때 사용, 사용자별 데이터를 유지
    • 프록시 사용이 필요하다.
  5. GlobalSession
    • 포털(Portal)처럼 전역 세션 범위를 가지는 환경에서 사용되는 스코프. (일반적인 Web 애플리케이션에서는 자주 사용되지 않음.)

Request 스코프와 Session 스코프에서 프록시 패턴이 필요한 이유

Spring 컨테이너가 @Autowired나 @Inject 등을 이용해 의존성 주입을 수행할 때, 싱글톤 스코프의 Bean들이 먼저 초기화됨. 하지만 request나 session 스코프의 Bean들은 사용자의 HTTP 요청이 발생할 때 생성되므로, 싱글톤 Bean이 이러한 스코프 Bean을 주입받으려고 할 때 아직 생성되지 않은 Bean을 참조하려는 문제가 발생할 수 있다.

예시 코드)

@Component
@Scope("request") // 요청(Request)마다 새로 생성되는 Bean
public class RequestScopedBean {
    public RequestScopedBean() {
        System.out.println("RequestScopedBean 생성됨! " + this);
    }
}

@Service
public class SingletonService {
    private final RequestScopedBean requestScopedBean;

    @Autowired
    public SingletonService(RequestScopedBean requestScopedBean) {
        this.requestScopedBean = requestScopedBean;
    }

    public void process() {
        System.out.println("사용 중인 RequestScopedBean: " + requestScopedBean);
    }
}

문제 발생!

  • SingletonService는 @Service이므로 기본적으로 싱글톤(Singleton) 스코프이다.
  • 그러나 RequestScopedBean은 요청(Request)마다 새로 생성되는데,SingletonService가 처음 의존성 주입을 받을 때 아직 요청이 없는 상태이므로 RequestScopedBean이 생성되지 않음.
  • 따라서 Spring 컨테이너가 RequestScopedBean을 찾을 수 없다는 오류가 발생한다. → 해결하려면 프록시 패턴을 사용해야 함.

프록시 패턴이란?

프록시(Proxy) 패턴은 실제 객체 대신 가짜(Proxy) 객체를 먼저 주입하고, 실제 객체가 필요할 때 프록시를 통해 접근하는 디자인 패턴이다.

이 방식은 객체의 생성 시점을 지연(Lazy Initialization)하거나, 요청마다 다른 객체를 제공해야 할 때 유용하게 활용된다.

예시 코드 수정 후)

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedBean {
    public RequestScopedBean() {
        System.out.println("RequestScopedBean 생성됨! " + this);
    }
}

@Service
public class SingletonService {
    private final RequestScopedBean requestScopedBean;

    @Autowired
    public SingletonService(RequestScopedBean requestScopedBean) {
        this.requestScopedBean = requestScopedBean;
    }

    public void process() {
        System.out.println("사용 중인 RequestScopedBean: " + requestScopedBean);
    }
}

프록시가 동작하는 방식

  1. Spring 컨테이너가 애플리케이션을 시작할 때
    • RequestScopedBean을 직접 주입하는 대신, 프록시 객체(가짜 객체)를 먼저 생성하여 주입함.
  2. HTTP 요청이 들어오면
    • 프록시 객체가 실제 RequestScopedBean을 찾아서 반환함.
  3. 프록시 객체는 요청마다 새롭게 실제 객체를 반환하므로, 요청별로 다른 인스턴스를 사용하게 됨.