예제 오류의 해결책
1. Provider
ObjectProvider
를 사용해본다.
- Controller와 Service에서 request 스코프를 사용하는 Logger에다 적용한다. Provider의
getObject()
로 myLogger를 가져오기만 하면 된다.
private final ObjectProvider<Logger> myLoggerProvider;
- getObject()를 호출하는 시점까지 request 스코프 빈의 생성을 지연시킬 수 있게 되었다.
- getObject()를 호출하는 시점에는 http 요청이 진행중이므로 request 스코프의 빈의 생성이 정상적으로 처리된다.
- getObject()를 컨트롤러와 서비스에서 각각 한번씩 따로 호출해도 같은 http 요청이면 같은 빈이 반환된다.
2. Proxy
@Component
@Scope(value = "request", proxyMode = ScopeProxyMode.TARGET_CLASS)
public class Logger{...}
- 기존 Logger 클래스에
proxyMode = ScopeProxyMode.TARGET_CLASS
를 추가
- 적용 대상이 class : TARGET_CLASS
- 적용 대상이 interface : INTERFACES
- Logger의 가짜 프록시 클래스를 만들어두고 http request와 상관없이 CGLIB 라이브러리로 클래스를 상속받은 가짜 프록시 클래스를 다른 빈에 미리 주입해 둘 수 있다. 스프링 빈 이름은 기존 클래스의 이름으로 등록된다. 의존관계 주입은 이 가짜 프록시 객체가 주입된다.
myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$fd0ce40e
가짜 프록시 객체는 요청이 들어오면 그 때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.
- 클라이언트가 myLogger.logic()을 호출하면 가짜 프록시의 객체를 호출한 것이다.
- 가짜 프록시 객체는 내부에 진짜 myLogger를 찾는 방법을 알고 있으며, request scope의 진짜 logic()을 호출한다.
- 가짜 프록시 객체는 원본 클래스를 상속받아 만들어졌으므로 이 객체를 사용하는 클라이언트 입장에서는 만들어진 클래스인지 아닌지 모르게 동일하게 사용할 수 있다. (
다형성
)
- 가짜 프록시 객체는 실제 request scope와 상관없고, 위임 로직만 있으며 싱글톤처럼 동작한다.
주의사항
- 싱글톤처럼 동작하는 것이지, 다르게 동작하기 때문에 주의해서 사용해야 한다.
- 필요한 곳에서만 최소화하여 사용해야 한다.(무분별한 사용시 유지보수가 어렵다)
정리
Provider나 Proxy의 핵심 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연처리를 할 수 있다는 점이다.
프록시는 웹 스코프가 아니여도 사용할 수 있다.
다형성과 DI 컨테이너의 강점으로 애노테이션 설정만 변경했는데, 원본 객체를 프록시 객체로 대체할 수 있다는 것이다.