빈이 존재할 수 있는 범위
스프링이 지원하는 스코프
웹 관련 스코프
싱글톤 스코프 빈 조회시 항상 같은 빈 ↔️ 프로토타입 스코프 조회시 항상 새로운 인스턴스 생성하여 반환
싱글톤 스코프

클라이언트 A, B, C는 같은 빈을 공유한다.
프로토타입 스코프
prototypeBean1.destory(); 이런식으로 직접 호출
싱글톤 빈 clientBean은 컨테이너 생성 시점에 생성되고, 의존관계 주입 발생
1. 의존 관계 주입에 사용할 프로토타입 빈을 컨테이너에 요청
2. 스프링 컨테이너에서 프로토타입 빈 생성하여 반환, clientBean은 내부에 프로토타입 빈 저장하여 보관함, count = 0
클라이언트 A가 로직 호출 -> 항상 같은 clientBean 반환 받음
clientBean은 count+1 함, count = 1
클라이언트 B가 로직 호출 -> 항상 같은 clientBean 반환 받음
clientBean은 count+1 함, count = 2
count값이 다른 클라이언트에 의해 변경된 상황
클라이언트 A, B는 같은 빈을 사용하고 (싱글톤이므로), 프로토타입 빈은 이미 과거에 주입된 빈으로 계속 같은 것을 사용함(로직 호출마다 프로토타입빈 새로 생성 X)
싱글톤 빈은 생성 시점에만 의존관계 주입을 받기 때문에, 프로토타입 빈이 새로 생성되긴 하지만 주입되지 않는다.
⭐️ 즉 제일 처음에 만들어진 프로토타입 빈이 싱글톤 빈과 함께 계속 유지된다.

1. 싱글톤 빈이 사용할 때마다 프로토타입 빈을 새로 요청하기
로직을 호출할 때마다, ac.getBean()을 통해서 새로운 프로토타입 빈이 생성됨
문제점) 스프링 컨테이너에 종속적인 코드가 되고, 단위별 테스트가 어려움
@Autowired
private ApplicationContext ac;
public int logic() {
PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
2. ObjectProvider 사용
지정한 프로토타입 빈만 컨테이너에서 대신 찾아주는 DL 기능을 제공함
Dependency Lookup (DL) 의존 관계
의존 관계를 외부 주입이 아니라, 직접 필요한 의존 관계를 찾는 방식
ObjectProvider의 getObject() == 스프링 컨테이너에서 해당 빈을 찾아 반환
static class ClientBean {
@Autowired private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
3. JSR-330 Provider 사용
자바 표준을 사용, 사용하기 위해서는 build.gradle에 라이브러리를 추가해야함
dependencies에 추가하기 (spring 3.0 이상)
implementation 'jakarta.inject:jakarta.inject-api:2.0.1'
static class ClientBean {
@Autowired private Provider<PrototypeBean> provider;
public int logic() {
PrototypeBean prototypeBean = provider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
}
매번 사용할 때 마다 의존관계 주입이 완료된 새로운 객체가 필요한 경우 사용 (매우 드물다)
실무에서는 싱글톤으로 거의 해결이 되기 때문에, 거의 사용하지 않음
spring 환경에서 사용 → ObjectProvider
spring 외 컨테이너 → JSR-330 Provider
웹 환경에서만 동작, 스코프의 종료시점까지 관리하여 종료 메서드 호출
웹 스코프 종류

프로토타입과 다르게, 각 클라이언트 전용 스코프가 제공이 됨
웹 스코프는 웹에서만 동작하므로 웹 라이브러리 추가 필요
implementation 'org.springframework.boot:spring-boot-starter-web
동시에 여러 HTTP 요청이 오면 어떤 요청이 남긴 로그인지 구분하기 어려움
→ 이럴 때 사용하기 좋은 것 == request 스코프
로그 출력을 위한 MyLogger
package hello.core.common;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.UUID;
@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 create:" + this);
}
@PreDestroy
public void close() {
System.out.println("[" + uuid + "] request scope bean close:" + this);
}
}
log 확인용 컨트롤러
아래 코드에서는 requestURL을 MyLogger에 저장하는데,
로직 공통처리가 가능한 스프링 인터셉터를 통해 구현하는것이 좋음
package hello.core.web;
import hello.core.common.MyLogger;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@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";
}
}
LogDemoService 서비스 계층에서 출력
⭐️서비스 계층에서는 웹 기술에 종속되지 않고 순수하게 유지하는 것이 좋음
웹 관련 정보는 컨트롤러까지 사용해야함
MyLogger의 멤버 접근을 통해서, 파라미터로 모든 정보를 서비스 계층에 넘기지 않고도 서비스 아이디 출력 가능
package hello.core.web;
import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final MyLogger myLogger;
public void logic(String id) {
myLogger.log("service id = " + id);
}
}
지금의 코드는 실제 요청이 들어와야 빈이 생성되기 때문에, 컨테이너에 빈 요청을 하는 것에서 오류가 발생
즉, 스프링 컨테이너에 빈 요청을 의존관계 주입 시점이 아닌 고객 요청 이후에 해야한다.
→ Provider로 이 문제를 해결할 수 있음
ObjectProvider 덕분에 ObjectProvider.getObject() 를 호출하는 시점까지 request scope 빈의 생성을 지연 ObjectProvider.getObject() 를 호출하시는 시점에는 HTTP 요청이 진행중이므로 request scope 빈 생성이 정상처리 됨service 코드 수정
public class LogDemoService {
private final ObjectProvider<MyLogger> myLoggerProvider;
public void logic(String id) {
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.log("service id = " + id);
}
}
controller 코드 수정
위와 비슷하게 ObjectProvider로 myLogger 만들고 getObject()
위 Provider처럼 컨테이너에 빈 요청을, request 이후로 할 수도 있지만
HTTP request 여부와 별도로 가짜 프록시를 미리 주입해서 해결 가능

MyLogger 코드 수정
프록시 방식을 적용하기 위해 수정
클라이언트 코드 수정 없이, 애노테이션 설정 변경만으로 프록시 객체로 대체 가능
== 스프링 컨테이너가 가진 장점
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
controller, service 코드는 이전 것으로 돌리기
⭐️ Provider, 프록시 사용의 핵심 아이디어 = 진짜 객체 조회를 필요한 시점까지 지연처리