on
[Spring] 빈 스코프 - 웹 스코프, Request 스코프
[Spring] 빈 스코프 - 웹 스코프, Request 스코프
웹 스코프
웹 환경에서만 동작
웹스코프는 프로토타입과 다르게 스프링이 해당 스코프의 종료시점까지 관리한다. 따라서 종료 메서드가 호출된다.
종류
request: HTTP 요청 하나가 들어오고 나갈때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리된다.
session: HTTP Session과 동일한 생명주기를 가지는 스코프
application: 서블릿 컨텍스느와 동일한 생명주기를 가지는 스코프
websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프
동시에 요청할 경우 각가 다른 Bean이 생성되고 관리된다.
Request 스코프 예제만들기
build.gradle에 추가
implementation 'org.springframework.boot:spring-boot-starter-web'
위 라이브러리를 추가하면 톰캣 포트가 추가되며 웹 어플리케이션이 작동한다.
웹 라이브러리를 추가하면 AnnotationConfigApplicationContext를 기반으로 애플리케이션을 구동하지않고
AnnotationConfigServletWebServerAplicationContext를 기반으로 애플리케이션을 구동한다.
여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵기 때문에 Request스코프를 사용해서 구분
기대하는 공통포맷: [UUID][RequestUrl]{Message}
UUID를 사용해서 HTTP 요청을 구분
reqeustUrl 정보도 추가로 넣어서 어떤 URL을 요청해서 남은 로그인지 확인
@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() { String 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); } }
url은 생성되는 시점에서 알 수 없으므로 외부에서 setter로 지정해준다.
@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.getRequestURI().toString(); myLogger.setRequestURL(requestURL); myLogger.log("controller test"); logDemoService.logic("testId"); return "OK"; } }
Controller를 작성하고 실행시키게 되면 오류가 발생한다
MyLogger는 request 스코프로 지정된 빈인데 웹 관련 요청이 없어서 생성 자체가 안되기 떄문에 어플리케이션
실행되면서 오류가 발생한다. MyLogger를 빈으로 등록하려면 어플리케이션 실행할때는 생성하지 않다가 실질적으로 요청이 들어왔을때 빈을 조회 및 생성해야한다.
스코프와 Provider
@RequestMapping("log-demo") @ResponseBody public String logDemo(HttpServletRequest request) { String requestURL = request.getRequestURI().toString(); MyLogger myLogger = myLoggerProvider.getObject(); myLogger.setRequestURL(requestURL); myLogger.log("controller test"); logDemoService.logic("testId"); return "OK"; } // [b0969b6e-1287-42b1-bc5b-688715830a78] request scope bean create: hello.core.common.MyLogger@1f7962b6 // [b0969b6e-1287-42b1-bc5b-688715830a78] [/log-demo]controller test // [b0969b6e-1287-42b1-bc5b-688715830a78] [/log-demo]service id: testId // [b0969b6e-1287-42b1-bc5b-688715830a78] request scope bean close: hello.core.common.MyLogger@1f7962b6
myLogger를 ObjectProvider를 사용하여 request가 왔을때에만 DL하여 로그를 출력할 수 있다.
스코프와 프록시
@Scope(value="request", proxyMode = ScopedProxyMode.TARGET_CLASS) public class MyLogger {
@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.getRequestURI().toString(); myLogger.setRequestURL(requestURL); myLogger.log("controller test"); logDemoService.logic("testId");
@Service @RequiredArgsConstructor public class LogDemoService { private final MyLogger myLogger; public void logic(String id) { myLogger.log("service id: " + id); } }
위 예제와 달리 proxyMode = ScopedProxyMode.TARGET_CLASS를 @Scope 애노테이션 내에 지정해주면
Provider를 사용하지 않아도 잘 동작한다.
로그를 찍어보면
myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$24131e0b와 같이 나오는데 여기의 MyLogger는 스프링이 조작해서 생성한 myLogger를 반환함을 알 수 있다.
이런 경우 MyLogger의 가짜 프록시 클래스를 만들어 두고 실제 기능호출을 할 경우 진짜 빈을 생성해서 가져온다.
Provider든 프록시든 핵심은 진짜 객체를 조회하기 전까지 지연처리하는것
단지 애노테이션 설정 변경만으로 원본객체를 프록시 객체로 대체할 수 있는 것이다.
대상이 Class 일 경우: ScopedProxyMode.TARGET_CLASS
대상이 Interface 일 경우 : ScopedProxyMode.INTERFACES
from http://ju-bong.tistory.com/41 by ccl(A) rewrite - 2021-12-23 22:00:53