스코프 : 빈이 존재할 수 있는 범위
스프링 빈은 기본적으로 싱글톤 스코프로 생성된다. 스프링은 다양한 스코프 지원함.
컴포넌트 스캔 자동 등록
@Scope("prototype")
@Component
public class HelloBean {}
수동 등록
@Scope("prototype")
@Bean
PrototypeBean HelloBean() {
return new HelloBean();
}
싱글톤 스코프 빈 조회 : 항상 같은 인스턴스 빈 반환
프로토타입 스코프 : 항상 새로운 인스턴스 생성, 반환
싱글톤 빈 요청 과정
1. 스프링 컨테이너에 요청
2. 스프링 컨테이너가 스프링 빈 반환 (싱글톤 스코프 빈은 컨테이너 생성시 이미 생성되어있음)
3. 나중에 같은 요청 오면, 같은 인스턴스 반환
프로토타입 빈 요청
1. 프로토타입 스프링 빈을 컨테이너에 요청
2. 해당 시점에 프로토타입 빈 생성 & 의존관계 주입.
3. 생성한 프로토타입 빈을 클라이언트에 반환
4. 이후에 같은 요청 와도 항상 새로운 프로토타입 빈 생성
프로토타입 스코프 빈 경우 : 빈 생성 + DI + 초기화 까지만 처리, 이후 관리 X. 넘겨받은 클라이언트에 책임 넘어감.
@PreDestroy종료 메서드 호출 안됨.
프로토타입 빈은 늘 새로운 인스턴스 생성. 하지만 싱글톤 빈 같이 사용하면 의도한 대로 작동X.
프로토타입 빈 요청

상황 : clientBean(싱글톤)이 프로토타입 빈 주입받아 사용.

clientBean은 싱글톤 빈이기에 컨테이너 생성시 빈 생성 & DI 됨. DI 과정에서 프로토타입 빈 요청됨.clientBean에 반환.
문제 : 다른 사용자가 호출하더라도 싱글톤 빈이기에 같은 clientBean이 호출됨. 문제는 프로토타입 빈까지 동일한 것이 호출된다는 것. 왜냐하면 새로운 프로토타입 빈 생성이 아닌, 이전에 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;
}
}
지정한 프로토타입 빈을 컨테이너에서 찾아주는 DL 정도 기능만 가진게 있을까?
ObjectProvider : 지정한 빈을 컨테이너에서 대신 찾아주는 DL 제공.
ObjectFactory : 과거에 사용된 것. 추가기능 추가된 것이 ObjectProvider
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
특징
ObjectFactory : 단순한 기능. 별도 라이브러리 필요X. 스프링에 의존
ObjectProvider : ObjectFactory 상속. 옵션, 스트림 처리 등 기능 많음. 별도 라이브러리 필요X. 스프링 의존
@Autowired
private Provider<PrototypeBean> provider;
public int logic() {
PrototypeBean prototypeBean = provider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
특징
정리
ObjectProvider, JSR330 Provider 등은 프로토타입 뿐만 아니라 DL 필요한 경우 언제든 사용 가능.JSR vs ObjectProvider
ObjectProvider는 DL 위한 편의 기능 많이 제공하고, 스프링 외 별도 의존관계 추가 필요 없기 때문에 편리함. 만약 스프링 아닌 다른 컨테이너에서도 사용할 수 있어야 한다면 JSR-330 사용해야함.
자바 표준, 스프링 제공하는 기능 많이 겹침. 또한 대부분 스프링이 더 다양하고 편리한 기능 제공. 그렇기에 특별히 다른 컨테이너 사용 안한다면, 스프링이 제공하는 기능 사용하자.
웹 스코프란?
웹 스코프 종류
ServletContext)와 동일한 생명주기 가지는 스코프HTTP request 요청 당 각각 할당되는 request 스코프

request 스코프 예제 개발
동시에 여러 HTTP 요청 오면 정확시 어떤 요청이 남긴 로그인지 구분 어려움 -> request 스코프로 해결.
[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
위의 결과 내기 위한 코드
@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);
}
}
@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 추가
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final MyLogger myLogger;
public void logic(String id) {
myLogger.log("service id = " + id);
}
}
기대 출력
[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;
문제 : 스프링 애플리케이션 실행 시점에 싱글톤 빈은 생성&주입 동시에 되지만, request 스코프 빈은 생성 안됨. 요청이 와야 생성된다.
아까 배운 Provider 사용해보자.
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService;
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);
myLogger.log("controller test");
logDemoService.logic("testId");
return "OK";
}
}
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final ObjectProvider<MyLogger> myLoggerProvider;
public void logic(String id) {
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.log("service id = " + id);
}
}
브라우저에서 url 요청시 로그
[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
ObjectProvider 사용하여 request scope 빈 생성을 지연시킬 수 있게됨ObjectProvider.getObject() 호출 시점에는 HTTP 요청 진행중이므로 request scope 빈 생성 정상 처리됨.@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}
MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입해 둘 수 있음.
TARGET_CLASS, 인터페이스면 INTERFACES@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";
}
}
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final MyLogger myLogger;
public void logic(String id) {
myLogger.log("service id = " + id);
}
}
-> 문제없이 실행됨.
주입된 myLogger 확인
System.out.println("myLogger = " + myLogger.getClass());
출력 결과
myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$b68b726d
CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어 주입.
@Scope의 proxyMode = ScopedProxyMode.TARGET_CLASS를 설정하면 스프링 컨테이너는 CGLIB라는 바이트코드를 조작하는 라이브러리 사용하여, MyLogger를 상속받은 가짜 프록시 객체를 생성한다.MyLogger$$EnhancerBySpringCGLIB 라는 클래스로 만들어진 객체가 등록되어있음.ac.getBean("myLogger", MyLogger.class) 해도 프록시 객체가 조회됨.
프록시 객체 : 요청 오면 내부에서 진짜 빈을 요청하는 위임 로직을 가짐.
myLogger.log() 호출하면 가짜 프록시 객체의 메서드를 호출한 것myLogger.log() 호출동작 정리
특징 정리
주의점