이 포스트는 인프런 사이트에 김영한 - 스프링 핵심원리 강의를 보고 정리한 포스터입니다.
스프링 핵심 원리 - 기본편 강의 | 김영한 - 인프런
김영한 | , 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢 수강 전 확인해주세요! 본 강의는 자바 스프링 완전 정복 시리즈의 두 번째 강의입니다. 우아한형제들 최연
www.inflearn.com
스프링에서는 빈(Bean)의 생성과 유지되는 범위(Scope)를 설정할 수 있습니다.
기본적으로 모든 스프링 빈은 싱글톤(Singleton) 스코프를 갖지만, 특정 요구사항에 따라 다른 스코프를 사용할 수도 있습니다.
이번 포스트에서는 빈 스코프의 개념, 프로토타입 스코프 및 싱글톤 빈과 함께 사용할 때의 문제점, Provider를 활용한 해결 방법, 웹 스코프(request, session, application, websocket), 그리고 Provider 및 프록시(Proxy)를 활용한 스코프 관리 방법을 상세하게 정리하겠습니다.
🔆 빈 스코프란 ?
지금까지 학습한 스프링 빈은 스프링 컨테이너가 시작될 때 생성되고, 컨테이너가 종료될 때까지 유지됩니다.
이것은 스프링 빈의 기본 스코프가 싱글톤(Singleton)으로 설정되어 있기 때문입니다.
스코프(Scope)란,
빈이 존재할 수 있는 범위를 뜻합니다.
✅ 스프링이 제공하는 다양한 스코프
스코프 | 설명 |
singleton (기본값) | 하나의 빈 인스턴스만 생성하여 모든 요청에서 공유 |
prototype | 요청할 때마다 새로운 빈을 생성 (컨테이너가 관리하지 않음) |
request | HTTP 요청이 들어올 때 생성되고, 요청이 끝나면 소멸 |
session | HTTP 세션이 시작될 때 생성되고, 세션이 종료되면 소멸 |
application | 웹 애플리케이션이 시작될 때 생성되고, 종료될 때 소멸 |
websocket | 웹 소켓 연결이 시작될 때 생성되고, 연결이 끊어질 때 소멸 |
싱글톤 스코프는 가장 넓은 범위를 가지며, 웹 관련 스코프(request, session 등)는 웹 환경에서만 동작합니다.
✅ 빈 스코의 지정 방법
1. 컴포넌트 스캔 자동 등록
@Scope("prototype")
@Component
public class HelloBean {}
2. 컴포넌트 스캔 수동 등록
@Scope("prototype")
@Bean
PrototypeBean HelloBean() {
return new HelloBean();
}
🔆 프로토타입 스코프(Prototype Scope)
프로토타입 스코프를 사용하면 스프링 컨테이너가 빈을 요청할 때마다 새로운 인스턴스를 생성하여 반환합니다.
✏️ 싱글톤 빈 요청
1. 싱글톤 스코프의 빈을 스프링 컨테이너에 요청
2. 스프링 컨테이너는 본인이 관리하는 스프링 빈을 반환
3. 이후에 스프링 컨테이너에 같은 요청이 와도 같은 객체 인스턴스의 스프링 빈을 반환
✏️ 프로토타입 빈 요청 (1)
1. 프로토타입 스코프의 빈을 스프링 컨테이너에 요청
2. 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입
✏️ 프로토타입 빈 요청 (2)
3. 스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환
4. 이후에 스프링 컨테이너에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환
🎈 정리
여기서 핵심은 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리한다는 것입니다.
클라이언트에 빈을 반환하고, 이후 스프링 컨테이너는 생성된 프로토타입 빈을 관리하지 않습니다.
프로토타입 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에 있습니다.
그래서 `@PreDestroy` 같은 종료 메서드가 호출되지 않습니다.
✅ 프로토타입 스코프 빈 등록 방법
@Scope("prototype")
@Component
public class PrototypeBean {
@PostConstruct
public void init() {
System.out.println("PrototypeBean.init");
}
@PreDestroy
public void destroy() {
System.out.println("PrototypeBean.destroy");
}
}
✔ @Scope("prototype")을 사용하면 프로토타입 스코프로 설정됩니다.
✔ @PostConstruct는 실행되지만, @PreDestroy는 실행되지 않습니다.
✔ 빈을 관리할 책임은 클라이언트에게 있습니다.
✅ 실행 결과
PrototypeBean.init // 빈 생성 시 초기화 메서드 실행
🚨 하지만 컨테이너가 종료될 때 @PreDestroy가 호출되지 않습니다!
🎈프로토타입 빈의 특징 정리
스프링 컨테이너에 요청할 때 마다 새로 생성됩니다.
스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입 그리고 초기화까지만 관여합니다.
종료 메서드가 호출되지 않습니다.
그래서 프로토타입 빈은 프로토타입 빈을 조회한 클라이언트가 관리해야 한다. 종료 메서드에 대한 호출도 클라이언트가 직접 해야합니다.
🔆 프로토타입 스코프 - 싱글톤 빈과 함께 사용 시 문제점
🚨 문제 상황
싱글톤 빈이 프로토타입 빈을 주입받아 내부에서 사용하면,
싱글톤 빈이 한 번 생성될 때 프로토타입 빈도 함께 주입되므로, 매번 같은 프로토타입 빈을 사용하게 됩니다.
✅ 문제를 재현하는 코드
@Component
@Scope("singleton")
public class SingletonBean {
private final PrototypeBean prototypeBean;
@Autowired
public SingletonBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
public void usePrototype() {
prototypeBean.addCount();
System.out.println("Prototype count: " + prototypeBean.getCount());
}
}
클라이언트 A와 B가 각각 usePrototype()을 호출할 경우, 같은 프로토타입 빈을 사용하게 됩니다.
🔆 프로토타입 스코프 - 싱글톤 빈과 함께 사용 시 Provider로 문제 해결
✅ ApplicationContext를 사용하여 해결하는 방법
@Component
public class SingletonBean {
@Autowired
private ApplicationContext ac;
public void usePrototype() {
PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
prototypeBean.addCount();
System.out.println("Prototype count: " + prototypeBean.getCount());
}
}
✔ ApplicationContext에서 매번 새로운 빈을 요청하여 해결할 수 있습니다.
❌ 하지만, ApplicationContext를 주입받으면 스프링 컨테이너에 의존적인 코드가 됩니다.
✅ ObjectProvider 사용 (권장)
@Component
public class SingletonBean {
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public void usePrototype() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
System.out.println("Prototype count: " + prototypeBean.getCount());
}
}
✔ getObject()를 호출할 때마다 새로운 프로토타입 빈이 생성됩니다.
✔ 스프링 컨테이너에 대한 의존성을 줄일 수 있습니다.
🔆 웹 스코프(Web Scope)와 Request 스코프
🌀 웹 스코프의 특징
웹 스코프는 웹 환경에서만 동작합니다.
웹 스코프는 프로토타입과 다르게 스프링이 해당 스코프의 종료시점까지 관리해서 종료 메서드가 호출됩니다.
🌀 웹 스코프 종류
request | HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리된다. |
session | HTTP Session과 동일한 생명주기를 가지는 스코프 |
application | 서블릿 컨텍스트(`ServletContext` )와 동일한 생명주기를 가지는 스코프 |
websocket | 웹 소켓과 동일한 생명주기를 가지는 스코프 |
🌀 HTTP request 요청 당 각각 할당되는 request 스코프
동시에 여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵습니다.
이럴때 사용하기 딱 좋은것이 바로 request 스코프입니다.
✅ Request 스코프 빈 등록
@Component
@Scope(value = "request")
public class RequestBean {
@PostConstruct
public void init() {
System.out.println("RequestBean.init: " + this);
}
@PreDestroy
public void destroy() {
System.out.println("RequestBean.destroy: " + this);
}
}
✔ HTTP 요청이 들어오면 생성되고, 요청이 끝나면 소멸됩니다.
🔆 스코프와 Provider
Request 스코프의 빈은 싱글톤 빈에 주입할 수 없습니다.
이를 해결하려면 ObjectProvider를 활용하여 필요할 때 빈을 조회해야 합니다.
@Component
public class LogController {
private final ObjectProvider<RequestBean> requestBeanProvider;
@Autowired
public LogController(ObjectProvider<RequestBean> requestBeanProvider) {
this.requestBeanProvider = requestBeanProvider;
}
public void log() {
RequestBean requestBean = requestBeanProvider.getObject();
requestBean.log();
}
}
✔ 필요할 때 getObject()를 호출하여 Request 빈을 생성합니다.
✔ 싱글톤 빈에서도 안전하게 사용 가능합니다.
🔆 스코프와 프록시(Proxy)활용
✅ 프록시 적용 방식
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestBean {
...
}
✔ CGLIB 프록시 객체를 생성하여 싱글톤처럼 주입할 수 있습니다.
✔ 실제 요청이 발생하기 전까지 가짜 프록시 객체가 동작하며,
✔ HTTP 요청이 발생하는 순간 실제 Request 빈이 생성됩니다.
✏️ 가짜 프록시 객체는 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있습니다.
가짜 프록시 객체는 내부에 진짜 `myLogger` 를 찾는 방법을 알고 있습니다.
클라이언트가 `myLogger.log()` 을 호출하면 사실은 가짜 프록시 객체의 메서드를 호출한 것입니다.
가짜 프록시 객체는 request 스코프의 진짜 `myLogger.log()` 를 호출합니다.
가짜 프록시 객체는 원본 클래스를 상속 받아서 만들어졌기 때문에
이 객체를 사용하는 클라이언트 입장에서는 사실 원본인지 아닌지도 모르게, 동일하게 사용할 수 있습니다(다형성)
✏️ 동작 정리
CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입합니다.
이 가짜 프록시 객체는 실제 요청이 오면 그때 내부에서 실제 빈을 요청하는 위임 로직이 들어있습니다.
가짜 프록시 객체는 실제 request scope와는 관계가 없습니다.
그냥 가짜이고, 내부에 단순한 위임 로직만 있고, 싱글톤 처럼 동작합니다.
✏️ 특징 정리
프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯이 편리하게 request scope를 사용할 수 있습니다.
사실 Provider를 사용하든, 프록시를 사용하든 핵심 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점입니다.
단지 애노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체할 수 있습니다.
이것이 바로 다형성과 DI 컨테이너가 가진 큰 강점입니다.
꼭 웹 스코프가 아니어도 프록시는 사용할 수 있습니다.
✏️ 주의점
마치 싱글톤을 사용하는 것 같지만 다르게 동작하기 때문에 결국 주의해서 사용해야 합니다.
이런 특별한 scope는 꼭 필요한 곳에만 최소화해서 사용해아합니다.
📌 결론
✔ 기본적으로 싱글톤 스코프를 사용하지만, 필요에 따라 프로토타입 및 웹 스코프를 활용할 수 있습니다.
✔ 싱글톤 빈에서 프로토타입 빈을 사용할 때는 ObjectProvider를 활용하는 것이 좋습니다.
✔ 웹 스코프를 사용할 때는 Provider 또는 프록시를 활용하여 안전하게 주입할 수 있습니다.
'개발일지 > Spring' 카테고리의 다른 글
빈 생명주기 콜백 (0) | 2025.02.20 |
---|---|
의존관계 자동 주입 2편 (0) | 2025.02.19 |
의존관계 자동 주입 1편 (0) | 2025.02.19 |
컴포넌트 스캔 (0) | 2025.02.18 |
싱글톤 패턴, 싱글톤 컨테이너 (0) | 2025.02.18 |