
ServletContext)와 동일한 생명주기를 가지는 스코프
각 클라이언트에 각각 할당되는 request 스코프가 있다.
implementation 'org.springframework.boot:spring-boot-starter-web' 추가
CoreApplication 실행Description:
The bean 'fixedDiscountPolicy', defined in class path resource [hello/core/DiscountConfig.class], could not be registered. A bean with that name has already been defined in file [C:\Users\Dani\Desktop\core\out\production\classes\hello\core\Discount\FixedDiscountPolicy.class] and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
💾 application.properties에 다음을 추가
👉 spring.main.allow-bean-definition-overriding=true
overriding의 default 값이 false로 나와서 생기는 오류

서버가 8080포트로 정상 작동한다.
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;
// 빈이 생성되는 시점에는 requestURL을 알 수 없으므로 수정자 주입으로 받는다.
public void setRequestURL(String requestURL) {
this.requestURL = requestURL;
}
public void log(String msg){
System.out.println("[" + uuid + "]" + "[" + requestURL + " ] " + msg);
}
// 초기화
@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);
}
}
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";
}
}
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);
}
}
CoreApplication 실행Caused by: org.springframework.beans.factory.support.ScopeNotActiveException: 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 사용
✅ 프록시 방법 사용 사용
Provider 사용package hello.core.web;
import hello.core.common.MyLogger;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
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;
// ObjectProvider 추가
private final ObjectProvider<MyLogger> myLoggerObjectProvider;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request){
String requestURL = request.getRequestURL().toString();
MyLogger myLogger = myLoggerObjectProvider.getObject();
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
logDemoService.logic("testId");
return "OK";
}
}
package hello.core.web;
import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class LogDemoService {
// ObjectProvider 추가
private final ObjectProvider<MyLogger> myLoggerObjectProvider;
public void logic(String id){
MyLogger myLogger = myLoggerObjectProvider.getObject();
myLogger.log("service ID = "+ id);
}
}
CoreApplication 실행

ObjectProvider덕분에ObjectProvider.getObject()를 호출하는 시점까지 request scope 빈의 생성을 지연할 수 있다.
서버를 한 번 더 호출하면 다른 인스턴스가 생성된다. request는 한 번의 요청에 한 번의 인스턴스가 각각 생성된다.
package hello.core.web;
import hello.core.common.MyLogger;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
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;
// ObjectProvider 추가
private final ObjectProvider<MyLogger> myLoggerObjectProvider;
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) throws InterruptedException {
String requestURL = request.getRequestURL().toString();
MyLogger myLogger = myLoggerObjectProvider.getObject();
myLogger.setRequestURL(requestURL);
myLogger.log("controller test");
// 서비스로직 사이에 대기시간 추가
Thread.sleep(2000);
logDemoService.logic("testId");
return "OK";
}
}

여러 번 요청이 와도 기존에 요청했던 인스턴스가 유지된 채로 서비스 로직까지 생성된다.
💾 LogDemoController 에 추가
// myLogger클래스를 출력해보자!
System.out.println(myLogger.getClass());
💾 LogDemoService는 Provider이전으로 수정한다!
package hello.core.common;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
// 프록시 모드를 추가한다.
// 적용 대상이 인터페이스일 경우 타켓변경
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
private String uuid;
private String requestURL;
// 빈이 생성되는 시점에는 requestURL을 알 수 없으므로 수정자 주입으로 받는다.
public void setRequestURL(String requestURL) {
this.requestURL = requestURL;
}
public void log(String msg){
System.out.println("[" + uuid + "]" + "[" + requestURL + " ] " + msg);
}
// 초기화
@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);
}
}

CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입해준다.
@Scope에 proxyMode = ScopedProxyMode.TARGET_CLASS를 설정하면 스프링 컨테이너는 CGLIB라는 바이트코드를 조작하는 라이브러리를 사용해서, MyLogger를 상속받은 가짜 프록시 객체를 생성한다.

가짜 프록시 객체는 원본 클래스를 상속받아 만들어졌기 때문에 사용하는 클라이언트 입장에서는 원본인지 아닌지 모르게 동일하게 사용된다(다형성)
가짜 프록시 객체는 내부에 진짜 myLogger를 찾는 방법을 알고있다.
클라이언트가 myLogger.log()를 호출하면 가짜 프록시 객체를 호출하는 것(CGLIB)이고, 가짜 프록시 객체는 request 스코프의 진짜 myLogger.log()fmf ghcnfgksek.
가짜 프록시 객체는 실제 request scope와는 관계가 없고, 단순한 위임 로직만 있으며 싱글톤처럼 동작한다.
애너테이션 변경만으로 프록시 객체를 생성할 수 있다. (다형성, DI 컨테이너가 가진 장점)