동시에 여러 요청이 와도 한 요청에 같은 빈을, 다른 요청엔 다른 빈을 사용한다.
테스트를 위해 spring-web-starter 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-web'
spring-web 을 추가하게 하고 main을 수행하면 톰캣으로 웹서버가 8080포트로 띄어지게 된다.
그때부터 AnnotationConfigApplicationContext
가 아닌 AnnotationCOnfigSevletWebServerApplicationContext
를 기반으로 어플리케이션이 구동된다.
여러 쓰레드가 요청을 처리하기 때문에 구분할 수 있게 리퀘스트 빈으로 로그를 찍어보자
@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 msg) {
System.out.println("[" + this.uuid + "]" + "[" + this.requestURL + "]" + msg);
}
@PostConstruct
public void init() {
uuid = UUID.randomUUID().toString();
System.out.println("[" + this.uuid + "] request scope bean created:"+this);
}
@PreDestroy
public void close() {
System.out.println("[" + this.uuid + "] reqeust scope bean closed:"+this);
}
}
웹과 관련된 부분은 컨트롤러까지만 사용해야 한다.
서비스 계층은 웹 기술에 종속되지 않고 가급적 순순하게 유지하는 것이 유지보수 관점에서 좋다.
그렇기 때문에 MyLogger라는 빈을 만들어 사용한다.
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService;
private final MyLogger myLogger;
}
다음과 같이 생성자 주입으로 request 스코프 빈을 주입한다면?
Error : Scope 'request' is not active for the current thread
싱글톤 빈 Controller는 스프링 컨테이너가 생성될 때 같이 생성이 되지만
request 빈은 요청이 들어와야 생성이 되기때문에 싱글톤이 생성될 때 reqeust 빈은 존재하지 않기 때문에 주입할 수 없다는 에러가 발생된다.
-> Provider를 이용해 DL을 활용하자
@Controller
@RequiredArgsConstructor
public class LogDemoController {
private final LogDemoService logDemoService;
private final Provider<MyLogger> loggerProvider;
}
[e1c3f4ad-ab43-43d9-a031-900f9a00419d] request scope bean created:hello.core.common.MyLogger@4e7ca21f
[e1c3f4ad-ab43-43d9-a031-900f9a00419d][/log-demo]controller test
[e1c3f4ad-ab43-43d9-a031-900f9a00419d][/log-demo]service testId
[e1c3f4ad-ab43-43d9-a031-900f9a00419d] reqeust scope bean closed:hello.core.common.MyLogger@4e7ca21f
위 로그처럼 한 요청엔 같은 request 빈을 DL한 것을 볼 수 있다.
Provider의 스프링 컨테이너 조회를 미루는 기능을 사용
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
...
myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$6a264ae
내가 만든 MyLogger가 아닌 MyLogger를 감싼 프록시를 반환한다.
CGLIB 라이브러리로 바이트코드를 조작해 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입하고, mylogger의 기능을 사용할 때 그 때 진짜 빈을 요청하는 위임 로직을 수행한다.
(가짜 프록시 빈은 내부에 실제 MyLogger의 찾는 방법을 알고 있다.)
그렇기에 request 빈도 싱글톤 빈처럼 사용할 수 있다.
Provider든 Proxy든 진짜 객체 조회를 꼭 필요한 시점까지 지연처리를 한다는 점이다.
이것이 다형성과 DI 컨테이너가 가진 큰 강점이다.
싱글톤 빈처럼 사용할 수 있다고 해도, 스코프에 대해 인지하고 있어야 한다.
싱글톤이 아닌 다른 스코프 빈은 꼭 필요할 때만 사용하도록 해야한다.