: 빈이 존재할 수 있는 범위
@PreDestroy
와 같은 종료메서드가 호출되지 않는다.보통은, 이런 경우를 피하고 싶어서 프로토타입 빈을 사용한다. 따라서 의도와 다르게 동작할 가능성 ↑
이 경우, prototypeBean의 count는 2로 증가하게 된다 → 프로토타입 빈의 특성과 맞지 않는 결과!
Dependency Lookup(DL) : 의존관계 조회(탐색)
단, 이렇게 스프링 ApplicationContext 전체를 주입받는 것은 권장되지 않는다!
지정한 프로토타입 빈을 컨테이너에서 대신 찾아주는, DL 정도의 기능만 제공하는 무언가만 있으면 됨
원래는 ObjectFactory가 있었는데, 여기에 편의 기능을 추가해서 ObjectProvider가 만들어짐!
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
getObject()
메서드를 통해, 항상 새로운 프로토타입 빈을 생성함javax.inject.Provider
(jakarta.inject.Provider - Spring Boot 3.0 이상)
@Autowired
private Provider<PrototypeBean> provider;
public int logic() {
PrototypeBean prototypeBean = provider.get();
prototypeBean.addCount();
int count = prototypeBean.getCount();
return count;
}
get()
메서드 하나 - 기능이 매우 단순📌 하나 더!
- 스프링이 제공하는 메서드 중 @Lookup 애노테이션도 있음
- 단, 위의 방법들로도 충분함
- 고려해야 할 내용도 많음
👉🏻 참고 사항
- 다만, 실무에서 웹 애플리케이션을 개발해보면 싱글톤 빈만 사용해도 대부분의 문제를 해결할 수 있음 → 프로토타입 빈을 직접적으로 사용하는 일은 매우 드묾
💡 Tip!
- ObjectProvider, JSR330 Provider 등 : 프로토타입 뿐만 아니라 DL이 필요한 경우는 언제든지 사용 가능
- ex) 순환 참조 방지용
위의 방법 2와 3을 보면, 둘 중 어느 것을 사용할지 고민이 될 수 있다.
만약 (거의 없지만) 코드를 스프링이 아닌 다른 컨테이너에서도 사용할 수 있어야 한다면, 자바 표준을 써야 한다.
스프링은 이제 사실상 기술의 표준에 가까움
다만, 대부분의 경우 위와 같은 일이 엇고, 스프링이 대부분 더 다양하고 편리한 기능을 제공해주기 때문에 특별한 일이 없다면 스프링이 제공하는 기능을 사용해도 좋다.
📌 실습 시 참고!
spring-boot-starter-web 라이브러리를 추가하면 스프링 부트는 내장 톰켓 서버를 활용해서 웹
서버와 스프링을 함께 실행시킨다.
📌 실습 시 참고! 2
- 스프링 부트는 웹 라이브러리가 없으면 우리가 지금까지 학습한
AnnotationConfigApplicationContext
을 기반으로 애플리케이션을 구동한다.- 웹 라이브러리가 추가되면 웹과 관련된 추가 설정과 환경들이 필요하므로
AnnotationConfigServletWebServerApplicationContext
를 기반으로 애플리케이션을 구동하면 된다.
@Scope(value = "request")
📌 참고!
- requestURL 로그를 남기는 부분 등은 컨트롤러보다는 스프링 인터셉터나 서블릿 필터 등을 활용하는 것이 좋음
Controller
@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
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final ObjectProvider<MyLogger> myLoggerProvider;
public void logic(String id) {
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.log("service id = " + id);
}
}
ObjectProvider
을 사용하면?
ObjectProvider.getObject()
를 호출하기 전까지 request scope 빈의 생성 지연 가능
ObjectProvider.getObject()` 호출 시점에는 HTTP 요청이 진행 중이므로 request scope 빈의 생성이 정상 처리됨
ObjectProvider.getObject()
를 LogDemoController
, LogDemoService
에서 각각 한번씩 따로 호출해도, 같은 HTTP 요청이면 같은 스프링 빈이 반환됨
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}
System.out.println("myLogger = " + myLogger.getClass());
myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$b68b726d
MyLogger
를 상속받은 가짜 프록시 객체를 생성한다.myLogger
"라는 이름으로 진짜 대신에 이 가짜 프록시 객체를 등록한다myLogger
를 찾는 방법을 알고 있음myLogger.logic()
을 호출하면 사실은 가짜 프록시 객체의 메서드를 호출한 것 → 가짜 프록시 객체가 request scope를 가진 진짜 myLogger.logic()
를 호출한다.정리!
가짜 프록시 객체는 실제 request scope와는 관계가 없다. 그냥 가짜이고, 내부에 단순한 위임 로직만
있고, 싱글톤 처럼 동작한다.