(인프런) 김영한님의 스프링 핵심 원리-기본편을 공부하고 리뷰한 글입니다.
7. 스코프와 Provider
첫번째 해결방안은 Provider를 사용하는 것으로 여기서는 간단히 ObjectProvider를 사용한다.
1. ObjectProvider를 사용하도록 코드 수정
생성되지 않은 myLogger 의존관계 주입으로 문제가 되었던 LogDemoController 와 LogDemoService의 코드를 ObjectProvider<MyLogger> 를 주입받도록 코드를 수정한다.
1) LogDemoController 코드 수정
package hello.core.web;
import hello.core.common.MyLogger;
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;
import javax.servlet.http.HttpServletRequest;
// MyLogger 가 잘 작동하는지 확인하는 테스트용 컨트롤러
@Controller
@RequiredArgsConstructor // 자동 생성자 주입
public class LogDemoController {
private final LogDemoService logDemoService;
private final ObjectProvider<MyLogger> myLoggerProvider; // myLogger 를 찾을 수 있는 ObjectProvider 주입
// log-demo 요청이 오면 웹 브라우저에 데이터를 문자로 반환
@RequestMapping("log-demo")
@ResponseBody
public String logDemo(HttpServletRequest request) throws InterruptedException {
// HttpServletRequest: 클라이언트 요청 정보
String requestURL = request.getRequestURL().toString(); // 클라이언트가 요청한 URL 반환
// ObjectProvider로 myLogger 조회(DL)
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.setRequestURL(requestURL); // myLogger 의 requestURL 에 값 저장
//myLogger.log("LogDemoController-MyLogger : " + myLogger);
myLogger.log("controller test");
//Thread.sleep(1000);
logDemoService.logic("testId");
return "OK";
}
}
2) LogDemoService 코드 수정
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 {
private final ObjectProvider<MyLogger> myLoggerProvider; // myLogger 를 찾을 수 있는 ObjectProvider 주입
public void logic(String id) {
// ObjectProvider로 myLogger 조회(DL)
MyLogger myLogger = myLoggerProvider.getObject();
myLogger.log("service id = " + id);
//myLogger.log("LogDemoService-MyLogger : " + myLogger);
}
}
2. CoreApplication main() 메서드 실행 → 웹 브라우저에 localhost:8080/log-demo 입력 결과
CoreApplication의 main() 메서드를 실행한다.
1) 웹 브라우저 localhost:8080/log-demo 입력
아래와 같이 @ResponseBody로 보낸 문자 "OK"가 출력된 것을 확인할 수 있다.
2) 콘솔 출력 결과
localhost:8080/log-demo 입력을 하고 새로고침을 하면 총 Http Request가 2번 들어오므로 아래와 같이 uuid가 2개로 구분되어 로그가 출력되는 것을 확인할 수 있다.
3) 의도적인 sleep()을 걸었을 때 콘솔 출력 결과
주석처리된 부분을 해제하고 main() 메서드를 재 실행하고 localhost:8080/log-demo 가 입력된 웹 브라우저를 빠르게 새로고침을 여러 번했을 때, 이전처럼 같은 uuid가 연속되어 나타나지 않는 것을 볼 수 있다.
실제로 동시에 Http 요청이 많이 들어오게 되면, 같은 Http 요청이더라도 Controller, Service의 작업이 연속적으로 일어나지 않을 수있다. 하지만 Http Request 마다 myLogger 스코프 빈을 생성해서 사용하기 때문에, 다른 Http 요청과 순서가 섞였다고 하더라도 UUID를 보고 같은 Http Request라는 것을 알 수 있다.
<정리>
1) ObjectProvider 덕분에 ObjectProvider.getObject()를 호출하는 시점까지 스프링 컨테이너에 request 스코프 빈을 요청하는 것을 지연할 수 있다. (요청이 지연되므로 생성 역시 지연됨)
2) ObjectProvider.getObject()를 LogDemoController, LogDemoService 에서 각각 한번씩 따로 호출해도 같은 HTTP 요청이면 같은 스프링 빈(MyLogger)가 반환된다.
- 실제로 코드로 확인해보면, LogDemoController, LogDemoService에서 ObjectProvider.getObject()를 호출하여 조회한 MyLogger가 같은 인스턴스인 것을 확인할 수 있다.
9-7. 스코프와 Provider 질문 정리
Q. Controller에서 Service의 logic()을 호출했을 때, Service에서 getObject()로 MyLogger를 조회하면 자동으로 request 별로 구별되어 같은 request 빈으로 반환되는 원리는?
자바 웹 서버는 클라이언트 HTTP 요청마다 별도의 쓰레드를 할당한다.
자바의 ThreadLocal 객체를 내부에서 사용해서 쓰레드를 구분할 수 있다. (쓰레드 id로 구분)
[출처] https://www.inflearn.com/questions/103515
Q. MyLogger는 request 스코프 빈으로 Http 요청이 들어올 때 객체가 생성된다. 그럼 컴포넌트 스캔 대상이 아니니 @Component 애노테이션을 붙이지 않아도 되는 것이 아닌가?
A. 그렇다할지라도 결국 MyLogger도 생명주기 차이만 있을 뿐, 스프링이 관리하는 빈이다. 그러므로 @Component 애노테이션을 붙여줘야 한다. provider는 스프링 컨테이너에서 빈을 찾아주는 기능만 한다.
[출처] https://www.inflearn.com/questions/131240
Q. request 스코프 빈이 생성되는 시점은 정확히 언제인가?
A. HTTP Reuqest(요청)이 들어오고 provider.getObject()를 호출하는 시점에 request 스코프 빈이 생성된다. (단, request 스코프 빈이 있으면 그 빈을 바로 반환)
<request 스코프 빈 생성 과정>
HTTP request(요청) -> provider.getObject() 호출 -> request 스코프 빈이 없으면 request 스코프 빈 생성 -> 생성한 request 빈 참조값 반환
[참고] https://www.inflearn.com/questions/143150
[참고] https://www.inflearn.com/questions/179312
Q. Controller 부분에 @RequsetMapping("log-demo")로 요청받은 url을 알 수 있는데, @GetMapping을 사용해도 차이는 없는 것인가?
A. 차이는 없다. http 메서드를 지정하지 않은 @RequestMapping 은 value에 해당하는 모든 http 메서드(GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE)를 처리한다.
Q. MyLogger에 @Component 를 붙여 컴포넌트 스캔 대상이되도록 하였는데, @ComponentScan 이 진행되는 부분은 어디인가?
우리는 CoreApplication의 main() 메서드를 실행한다. Ctrl + B 로 CoreApplication에 붙어있는 @SpringBootApplication 애노테이션의 정보를 보면, @ComponentScan 애노테이션이 붙어있는 것을 확인할 수 있다.
package hello.core;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CoreApplication {
public static void main(String[] args) {
SpringApplication.run(CoreApplication.class, args);
}
}
[출처] https://www.inflearn.com/questions/289044
Q.
[참고] https://www.inflearn.com/questions/403508
'Spring > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
[스프링 핵심 원리] 09. 빈 스코프 - 스코프와 프록시 (0) | 2022.05.24 |
---|---|
[스프링 핵심 원리] 09. 빈 스코프 - 웹 스코프 & request 스코프 예제 만들기 (0) | 2022.05.24 |
[스프링 핵심 원리] 09. 빈 스코프 - 프로토타입 스코프(싱글톤 빈과 함께 사용시 Provider로 문제 해결) (0) | 2022.05.24 |
[스프링 핵심 원리] 09. 빈 스코프 - 프로토타입 스코프(싱글톤 빈과 함께 사용시 문제점) (0) | 2022.05.23 |
[스프링 핵심 원리] 9. 빈 스코프 - 빈 스코프란? & 프로토타입 스코프 (0) | 2022.05.23 |