✅ 스프링 빈 스코프 정리

💡 빈 스코프란?

스프링 컨테이너에서 빈이 생성되고 존재하며 소멸되는 범위(Lifecycle) 를 의미합니다.

  • 기본 스코프는 singleton
  • 빈의 생명주기, 공유 범위, 관리 방식 조절 가능



🔹 주요 빈 스코프 종류

1. 📌 Singleton (기본값)

  • 컨테이너 시작 ~ 종료까지 하나의 인스턴스 유지
  • 모든 요청에 같은 인스턴스 반환
  • stateless하게 설계 권장
@Component
@Scope("singleton") // 생략 가능
public class MySingletonBean {
}



2. 📌 Prototype

  • 요청할 때마다 새로운 인스턴스 생성
  • 초기화까지만 스프링이 관리, 소멸은 관리 안함
  • @PreDestroy 호출 ❌
@Component
@Scope("prototype")
public class MyPrototypeBean {
    @PostConstruct
    public void init() {
        System.out.println("초기화");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("소멸"); // 호출되지 않음
    }
}



3. 📌 웹 스코프 (웹 환경에서만 사용 가능)

스코프 이름 유지 범위
request HTTP 요청 1건 처리 동안
session HTTP 세션 유지되는 동안
application 서블릿 컨텍스트와 생명주기 동일
websocket WebSocket 연결 유지되는 동안
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyRequestBean {
}

💡 프록시를 사용하면 싱글톤 빈에서도 안전하게 request 스코프 빈을 사용할 수 있음




🔸 빈 스코프 지정 방법

방식 예시 코드
컴포넌트 스캔 @Scope("prototype")
수동 등록 (설정) @Bean 메서드에 @Scope("...") 지정
@Configuration
public class AppConfig {
    @Bean
    @Scope("prototype")
    public MyPrototypeBean prototypeBean() {
        return new MyPrototypeBean();
    }
}



⚠️ 싱글톤 빈에서 프로토타입 빈을 사용할 때 주의점

  • 프로토타입 빈을 필드 주입하면 한 번만 생성되어 재사용됨
  • 프로토타입의 의도(매번 새로운 인스턴스)와 맞지 않음

❌ 잘못된 예

@Component
public class SingletonClient {
    private final MyPrototypeBean prototype;

    public SingletonClient(MyPrototypeBean prototype) {
        this.prototype = prototype;
    }

    public void logic() {
        prototype.doSomething(); // 항상 같은 인스턴스
    }
}



✅ 해결 방법

1. ObjectProvider 사용 (Spring 전용)

@Component
public class SingletonClient {
    @Autowired
    private ObjectProvider<MyPrototypeBean> provider;

    public void logic() {
        MyPrototypeBean prototype = provider.getObject(); // 매번 새로운 인스턴스
        prototype.doSomething();
    }
}

2. javax.inject.Provider 사용 (자바 표준, 외부 의존성 필요)

@Component
public class SingletonClient {
    @Inject
    private Provider<MyPrototypeBean> provider;

    public void logic() {
        MyPrototypeBean prototype = provider.get();
    }
}



⚠️ 웹 스코프 빈을 싱글톤 빈에서 사용할 때 주의점

  • request, session 스코프 빈을 직접 주입하면 오류 발생
    • 이유: 싱글톤 빈은 애플리케이션 시작 시 생성되지만, 웹 스코프 빈은 요청이 들어올 때 생성되기 때문
    • 즉, 아직 존재하지 않는 빈을 주입하려 하므로 예외 발생



✅ 프록시 방식으로 해결

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyRequestBean {
}
@Component
public class MyService {
    @Autowired
    private MyRequestBean requestBean; // 프록시 객체 주입
}



🔍 프록시(Proxy)란?

  • 스프링이 진짜 웹 스코프 빈 대신에 가짜 프록시 객체를 먼저 주입해줌

  • 이 프록시 객체는 실제 요청이 올 때까지 기다렸다가, 그 시점에 진짜 빈을 찾아서 동작을 위임함

  • requestBean.doSomething() 호출 시
    👉 프록시가 현재 요청 스코프에 해당하는 실제 MyRequestBean을 찾아서 대신 실행




📌 프록시의 장점

  • 싱글톤 빈 안에서도 안전하게 웹 스코프 빈 사용 가능
  • 클라이언트 코드는 마치 일반 빈처럼 사용할 수 있음 (프록시인지 신경 쓸 필요 없음)
  • 스코프의 생명주기를 신경쓰지 않아도 됨



⚠️ 주의할 점

  • 프록시는 실제 객체가 아니므로, == 비교나 instanceof 검사 시 주의
  • @Scope(..., proxyMode = ScopedProxyMode.TARGET_CLASS)CGLIB 기반 프록시 (클래스 기반)
    • 인터페이스 기반 프록시가 필요하면 ScopedProxyMode.INTERFACES 사용



✅ 프록시 요약 정리

항목 설명
프록시 역할 실제 스코프 빈을 대신해 먼저 주입되는 가짜 객체
생성 시점 애플리케이션 초기 (싱글톤 빈 생성 시점)
실제 객체 호출 시점 실제 요청(request 등)이 발생한 후
설정 방법 @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)



🧾 마무리 요약

  • 싱글톤: 애플리케이션 전역, 상태 없이 사용
  • 프로토타입: 매번 새 인스턴스 필요할 때
  • 웹 스코프: 요청/세션/앱별 빈 관리
  • Provider, 프록시 사용으로 스코프 조합 문제 해결