이번 시간에는 스코프와 프록시에 대해서 알아보는 시간을 가졌다.
이번 시간에도 마찬가지로 request 스코프 관련 에러가 났을 때, 해결할 수 있는 방법 중 하나로,
프록시 방식을 알아보았다.
@Scope에서 값이 2개 이상 들어가기 때문에, 콤마 사이에 키 밸류 형태로 두개를 넣어준다.
ScopedProxyMode.TARGET_CLASS 이렇게 입력을 해주면 MyLogger 클래스의 가짜 클래스를 만들어준다.
적용 대상이 인터페이스가 아닌 클래스면 .TARGET_CLASS를 하고,
적용 대상이 인터페이스라면 .INTERFACES를 선택하면 된다.
이렇게 하면 MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관없이 가짜 프록시 클래스를 다른빈에 미리 주입해 둘 수 있다.
한번 직접 코드로 확인해보자.
LogDemoController에서 24번째 라인에 직접 myLogger를 찍어본다.
콘솔에 찍힌걸 보면 프록시 형태로 빈이 생성된 것을 확인 할 수 있다.
동작 원리를 살펴보면, 일단 스프링 컨테이너가 올라올 때, myLogger의 프록시 클래스, 쉽게 말해, 가짜 클래스를 생성해준다.
그리고 실제 myLogger 기능을 실제 호출하는 시점에서 진짜 myLogger를 찾아서 동작을 한다.
결국, CGLIB이라는 라이브러리로 내 클래스를 상속받은 가짜 프록시 객체를 만들어서 주입하는 것이다.
추가로 설명 하자면, @Scope의 ScopedProxyMode.TARGET_CLASS 를 설정을 하면,
스프링 컨테이너는 CGLIB라는 바이트코드를 조작하는 라이브러리를 사용해서, MyLogger를 상속받은 가짜 프록시 객체를 생성한다.
결과를 확인해보면, 등록했던 MyLogger 클래스가 아니라 CGLIB로 생성된 클래스가 대신 등록된 것을 확인할 수 있다.
그리고 스프링 컨테이너에 myLogger 라는 이름으로 진짜 대신에 이 가짜 프록시 객체를 등록한다.
그래서 ac.getbean("myLogger", MyLogger.class) 로 조회를 해도 진짜가 아닌 프록시 객체가 조회되고,
그렇기 때문에 의존관계 주입도 이 가짜 프록시 객체가 주입된다.
가짜 프록시 객체는 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.
그래서 이 가짜 프록시 빈은 내부에 실제 MyLogger를 찾아오는 방법을 갖고 있다.
먼저, 클라이언트가 myLogger.logic() 을 호출하면 사실은 가짜 프록시 객체의 메소드를 호출한 것이다.
그리고, 가짜 프록시 객체는 내부적으로 request 스코프의 진짜 myLogger.logic() 을 컨테이너에서 찾아와서 로직을 호출한다.
말 그대로, 프록시, 앞에서 요청을 받아서 대신 처리해주는 것을 생각하면 된다.
가짜 프록시 객체는 원본 클래스를 상속받아서 만들어졌기 때문에, 이 객체를 사용하는 클라이언트 입장에서는 원본인지 아닌지도 모르게, 동일하게 사용할 수 있다.(다형성)
정리를 해보자면, 프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯이 편리하게 request scope를 사용할 수 있다.
사실 Provider를 사용하든, 프록시를 사용하든, 핵심 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점이다.
즉, 진짜 http 요청이 들어올 때까지 가짜로 버티다가, 실제 요청하는 시점에서 진짜 메소드를 찾아서 호출해주는 것이다.
단지 어노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체할 수 있다. 이것이 바로 다형성과 DI 컨테이너가 가진 큰 강점이다.
또한, 꼭 웹 스코프가 아니더라도 프록시는 사용할 수 있다.
주의해야할 점은, 마치 싱글톤을 사용하는 것 같지만, 결국 요청마다 빈이 각각 따로 생기기 때문에 주의를 해서 사용해야한다.
그리고 이런 특별한 scope는 꼭 필요한 곳에서만 최소화해서 사용해야 한다. 무분별하게 사용하면 테스트하기도 까다롭기 때문에, 유지보수가 어렵게 되므로 주의하자.
댓글