🔹 실행시점 오류 발생
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 스코프 빈은 아직 생성되지 않은 상태이다. 실제 고객의 요청이 와야 생성된다.
🔹 ObjectProvider로 인해 ObjectProvider.getObject()를 호출하는 시점까지 request 스코프 빈의 생성을 지연할 수 있다.
🔹 ObjectProvider.getObject()를 호출하는 시점에는 HTTP 요청이 진행중이라서 request 스코프 빈의 생성이 정상 처리된다.
🔹 ObjectProvider.getObject()를 LogDemoController, LogDemoService에서 각각 따로 호출해도 같은 HTTP 요청이면 같은 스프링 빈이 반환된다.
@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);
}
}
🔹 proxyMode = ScopedProxyMode.TARGET_CLASS를 추가하는 방법
🔹 MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관없이 가짜 프록시 클래스를 다른 빈에 미리 주입해 둘 수 있다.
🔹 TARGET_CLASS: 적용 대상이 인터페이스가 아닌 클래스일 때
🔹 INTERFACES: 적용 대상이 인터페이스일 때
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}
🔹 주입된 myLogger
System.out.println("myLogger = " + myLogger.getClass());
myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$b68b726d
🔹 CGLIB 라이브러리로 내 클래스를 상속받은 가짜 프록시 객체를 만들어서 주입한다.
🔹 @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)를 설정하면 스프링 컨테이너는 CGLIB라는 바이트코드를 조작하는 라이브러리를 사용해서 MyLogger를 상속받은 가짜 프록시 객체를 생성한다.
🔹 순수한 MyLogger 클래스가 아닌 MyLogger를 상속받은 가짜 프록시 객체가 생성된다. 그리고 "myLogger"라는 이름으로 스프링 컨테이너에 등록한다.
🔹 의존관계 주입도 이 가짜 프록시 객체가 주입된다.
🔹 가짜 프록시 객체는 실제 요청이 오면 내부에서 실제 빈을 요청하는 위임 로직이 들어있다.
🔹 가짜 프록시 객체는 request 스코프와 관련이 없다. 내부에서 단순한 위임 로직만 있고 싱글톤처럼 동작한다.
🔹 가짜 프록시 객체는 원본 클래스를 상속받아 만들어졌기 때문에 이 객체를 사용하는 클라이언트 입장에서는 동일하게 사용할 수 있다.(다형성)
🔹 단지 어노테이션 변경만으로 원본 객체를 프록시 객체로 대체할 수 있다. 이런 점이 다형성과 DI 컨테이너가 가진 큰 장점이다.
핵심 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점이다. 프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯 편리하게 request 스코프를 사용할 수 있다. 그리고 꼭 웹 스코프가 아니어도 프록시를 사용할 수 있다.