오봉이와 함께하는 개발 블로그
Spring - 빈 스코프(스코프와 Provider, 스코프와 프록시) 본문
728x90
스코프와 Provider
간단하게 ObjectProvider를 사용해서 해결하자
ObjectProvider
@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);
}
}
[586c19f0-3bf2-4f9b-ac55-6234609edea7] request scope bean create:hello.core.common.MyLogger@5b557e4b
[586c19f0-3bf2-4f9b-ac55-6234609edea7][http://localhost:8080/log-demo] controller test
[586c19f0-3bf2-4f9b-ac55-6234609edea7][http://localhost:8080/log-demo] service id = testID
[586c19f0-3bf2-4f9b-ac55-6234609edea7] request scope bean close:hello.core.common.MyLogger@5b557e4b
[160b0f55-7d77-4628-9ad4-d90c3db78695] request scope bean create:hello.core.common.MyLogger@33082260
[160b0f55-7d77-4628-9ad4-d90c3db78695][http://localhost:8080/log-demo] controller test
[160b0f55-7d77-4628-9ad4-d90c3db78695][http://localhost:8080/log-demo] service id = testID
[160b0f55-7d77-4628-9ad4-d90c3db78695] request scope bean close:hello.core.common.MyLogger@33082260
- ObjectProvider 덕분에 ObjectProvier.getObject()를 호출하는 시점까지 request scope 빈의 생성을 지연할 수 있었다.
- ObjectProvider.getObject()를 호출하는 시점에 HTTP요청이 진행중이므로 request scope빈의 생성이 정상 처리된다.
- ObjectProvider.getObject()를 LogDemoController, LogDemoService에서 각각 한번씩 따로 호출해도 같은 HTTP 요청이면 같은 스프링 빈이 반환된다.
스코프와 프록시
Provider 방식 말고도 프록시를 사용하는 방법도 있다.
이번에는 프록시를 사용해보자!
코드
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
private String uuid;
private String requestURL;
/*
...
*/
}
- proxyMode = ScopedProxyMode.TARGET_CLASS를 추가해주자.
- 클래스면 TARGET_CLASS
- 인터페이스면 INTERFACES
- 이렇게 하면 MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관 없이 가짜 프록시 클래스를 빈에 미리 주입해 둘 수 있다.
나머지 코드는 원상태로 돌려주자
@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";
}
}
@Service
@RequiredArgsConstructor
public class LogDemoService {
private final MyLogger myLogger;
public void logic(String id) {
myLogger.log("service id = " + id);
}
}
원리
먼저 주입된 myLogger를 확인하고 결과를 보자!
System.out.println("myLogger = " + myLogger.getClass());
myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$3886b12a
CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다.
- @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)로 설정하면 스프링 컨테이너는 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 MyLogger를 상속받은 가짜 프록시 객체를 생성한다.
- 결과를 보면 MyLogger$$EnhancerBySpringCGLIB$$3886b12a라는 클래스로 만들어진 객체가 대신 등록된 것을 확인할 수 있다.
- 이 가짜 프록시 객체를 스프링 컨테이너에 myLogger라는 이름으로 등록한다.
- ac.getBean("myLogger", MyLogger.class);로 조회해도 프록시 객체가 조회된다.
- DI도 가짜 프록시 객체가 주입된다.
가짜 프록시 객체는 요청이 오면 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.
- 가짜 프록시 객체느 내부에 진짜 myLogger를 찾는 방법을 알고 있다.
- 클라이언트가 myLogger.logic()을 호출하면 사실 가짜 프록시 객체의 메소드를 호출한 것이다.
- 가짜 프록시 객체는 request scope의 진짜 myLogger.logic()를 호출한다.
- 가짜 프록시 객체는 원본 클래스를 상속 받아서 만들어졌다.
- 그렇기 때문에 이 객체를 사용하는 클라이언트 입장에서 동일하게 사용할 수 있다.(다형성)
동작
- CGLIB 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입
- 가짜 프록시 객체는 실제 요청이 오면 그때 내부에서 실제 빈을 요청하는 위임 로직이 들어있음
- 가짜 프록시 객체는 실제 request scope와는 관계가 없다. 그냥 가짜이고, 내부에 단순한 위임 로직만 있고, 싱글톤 처럼 동작한다.
특징
- 프록시 객체 덕분에 클라이언트는 싱글톤 빈을 사용하듯 편하게 request scope를 사용할 수 있다.
- Provider를 사용하든 프록시를 사용하든 중요한 점은 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점!
- 다형성과 DI 컨테이너가 가진 큰 강점 덕분에 어노테이션 설정 변경만으로 코드를 고치지 않고 원본 객체를 프록시 객체로 대체할 수 있다.
- 웹 스코프가 아니어도 프록시는 사용할 수 있다!
주의
- 싱글톤을 사용하는 것 같지만 다르게 동작하기 때문에 주의해서 사용해야 한다.
- 특별한 scope는 꼭 필요한 곳에만 사용하자.
- 무분별하게 사용하면 유지보수하기 어렵다.
출처 : 인프런 김영한 지식공유자님의 스프링 완전 정복 로드맵 강의
728x90
'BE > Spring' 카테고리의 다른 글
Spring - 테스트 코드 작성시 NPE (0) | 2022.07.22 |
---|---|
Spring - SpringSecurity 사용해서 패스워드 암호화 하기 (0) | 2022.07.19 |
Spring - 빈 스코프(웹 스코프, request 스코프 예제 만들기) (0) | 2022.06.13 |
Spring - 빈 스코프(프로토타입 스코프 - 싱글톤 빈과 함께 사용시 Provider로 문제 해결) (0) | 2022.06.13 |
Spring - 빈 스코프(프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점) (0) | 2022.06.13 |
Comments