당니의 개발자 스토리
스코프와 프록시 본문
스코프와 프록시
이제 스코프와 프록시에 대해서 말씀드리겠습니다. 이번에는 프록시 방식을 한번 사용해 볼게요.
코드 먼저 보여드리겠습니다.

이거를 그냥 "request" 라고만 해도 되는데, 이렇게 하면 proxyMode = 라는 걸 못 넣기 때문에,

이렇게 넣어야 합니다. 값이 한 개면 "request" 라고만 해도 되지만, 그게 아니기 때문에 이렇게 해야합니다.
이제 proxyMode 라는 옵션이 있는데,

ScopedProxyMode.TARGET_CLASS라고 지정을 해줄게요.
그러면 MyLogger를 가짜로 만드는 겁니다. 즉, 프록시를 만드는 거예요.
그리고 나서, 기존에 오류났던 코드로 다시 바꾸는 겁니다.

참 깔끔해졌죠? Provider에서 get 할 것도 없어요. 이렇게 하고

만약 이전처럼 이렇게 다시 돌린다면, 전에 났던 오류가 똑같이 나겠죠.
그 때는 request 스코프인데, 지금 HTTP 요청이 들어오지도 않았는데 request 스코프의 빈을 주입받으려고 하는데 없으니까 오류가 났던 거죠.
그런데

이렇게 ScopedProxyMode.TARGET_CLASS라고 지정하고, 다시 CoreApplication의 main 메서드를 돌려서 서버 띄우고, localhost:8080/log-demo 창을 새로고침하면,

잘 동작됩니다. 마치 Provider를 쓰는 것과 똑같이 동작합니다. '그냥 myLogger를 주입받은 것 같은데 왜 잘 되지???'
이제 설명드리겠습니다.

proxyMode = ScopedProxyMode.TARGET_CLASS를 추가하는 게 핵심입니다.
참고로, 적용 대상이 class면 TARGET_CLASS 를 선택하면 되고요. 인터페이스면 INTERFACES 를 선택하면 됩니다.
이렇게 하면 MyLogger의 가짜 프록시 클래스를 만들어서 주입시켜줘요. 진짜가 아닌 가짜를 주입해줘요. HTTP request와 상관 없이, 그러니까 생존 범위 상관없이 가짜 프록시 클래스를 다른 빈에 미리 주입해둘 수 있습니다.

미리 주입해두고 localhost:8080/log-demo로 HTTP 요청이 들어오면, 바로 사용하는 거죠.
이해가 잘 안되니까 진짜 주입되는지 한 번 찍어볼게요.
웹 스코프와 프록시 동작 원리를 보면,

soutv 해서 찍어보는 거예요.

이렇게 하고 돌려보면,

CGLIB 보이죠. @Configuration 했을 때 봤던 것처럼 내가 만든 클래스가 아니라, 스프링이 CGLIB라는 바이트코드를 조작한 라이브러리를 사용해서 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 거예요.

프록시가 마치 Provider 처럼 동작하는 거예요. 얘는 진짜 MyLogger가 아니예요. 껍데기 뿐인 가짜 MyLogger를 집어넣는 거예요.

그리고 실제 내가 setter처럼 myLogger의 어떤 기능을 실제 호출하는 시점이 있잖아요.
그럼 이때 진짜를 찾아서, 마치 Provider가 동작했던 것처럼 동작해요. 이게 프록시의 동작 원리입니다.

그래서 출력 결과로 가짜가 나온걸 보셨구요.
정리하면, CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입하는 거예요.


@Scope 의 proxyMode = ScopedProxyMode.TARGET_CLASS) 를 설정하면 스프링 컨테이너는 CGLIB 라는 바이트코드를 조작하는 라이브러리를 사용해서, MyLogger를 상속받은 가짜 프록시 객체를 생성합니다.
결과를 확인해보면, 우리가 등록한 순수한 MyLogger 클래스가 아니라, MyLogger$$EnhancerBySpringCGLIB이라는 클래스로 만들어진 객체가 대신 스프링 빈으로 등록된 것을 확인할 수 있죠.
그리고 스프링 컨테이너에 "myLogger"라는 이름으로 진짜 대신에 이 가짜 프록시 객체를 스프링이 등록해버려요.
그래서 스프링 컨테이너에 어떻게 등록하냐면, 스프링 컨테이너에 MyLogger.class로 조회해도 프록시가 조회되어 버립니다. 즉, ac.getBean("myLogger", MyLogger.class) 로 조회해도 프록시 객체가 조회되는 것을 확인할 수 있어요.
그래서 의존관계 주입도 이 가짜 프록시 객체가 주입이 되는 겁니다.


이렇게 해서 이 가짜 프록시 객체는 요청이 오면, 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어가 있어요.
실제로 얘를 사용하는 시점이 되면 그때 진짜를 스프링 컨테이너에서 찾아오는 거죠.
그래서 가짜 프록시 객체는 내부에 진짜 myLogger를 찾는 방법을 알고 있어요.
그래서 클라이언트가 myLogger.log() 을 호출하면 사실은 가짜 프록시 객체의 메서드를 호출한 거예요. 진짜를 호출한 게 아니에요.
myLogger.log() 을 호출하면 가짜 프록시 객체는 request 스코프의 진짜 myLogger.log() 를 호출합니다. 가짜가 내부적으로 진짜를 컨테이너에서 찾아와서 myLogger의 진짜 logic()을 호출해주는 겁니다.
그래서 프록시 라고 하는 거거든요. 프록시가 앞에서 요청을 받아서 대신 처리해주는 거잖아요. 뒤에 위임하는 역할을 하죠. 간단한 조작을 하고, 그래서 이제 프록시 역할을 제대로 하고 있는 거고요.
가짜 프록시 객체는 원본 클래스를 상속 받아서 만들어졌기 때문에, 이 객체를 사용하는 클라이언트 입장에서는 사실 원본인지 아닌지도 모르게, 동일하게 사용할 수 있어요. 이게 다형성의 힘이죠.
그래서 동작 원리는

CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입해준다고 이해하면 되고, 어쨌든 가짜이기 때문에 가짜는 request scope 이런게 필요가 없어요. 그냥 마치 싱글톤처럼 되는 거예요. 가짜이기 때문에 다 같이 공유해도 되고 그냥 껍데기만 있는거죠.
그래서 myLogger가 주입되는 시점에 어차피 가짜를 주입하니까 request scope 같은 게 필요가 없어요.
그리고 나서, 이 가짜 프록시 객체는 localhost:8080/log-demo로 실제 HTTP 요청이 오면, 그때 프록시 객체 내부에 실제 빈을 요청하는 위임 로직이 들어있습니다.
가짜 프록시 객체는 실제 request scope와는 전혀 관계가 없어요. 가짜는 그냥 아무데나 주입할 수 있어요.
그냥 가짜이고, 내부에 단순한 위임 로직만 있고, 싱글톤처럼 동작한다고 보면 됩니다.
이렇게 해서 특징을 정리해보면,


프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯이 편리하게 request scope를 사용할 수 있죠.

코드를 보면, 마치 싱글톤 쓰는 것처럼 쓰잖아요.
사실 Provider를 사용하든, 프록시를 사용하든 핵심 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 점이에요.
어쨌든 프록시를 사용하면, 내가 진짜 HTTP 요청이 들어올 때까지는 가짜로 버티는 거예요. 버티다가 진짜 요청이 들어와서, HTTP request가 요청하면 그제서야 진짜를 찾아가지고, 진짜가 가진 메서드를 호출해주는 거죠.
단지 애노테이션 설정(옵션) 변경만으로 원본 객체를 프록시 객체로 대체할 수 있어요. 진짜 대단한 거예요. 이것이 바로 다형성과 DI 컨테이너가 가진 큰 강점입니다.
그리고 꼭 웹 스코프가 아니어도 프록시는 사용할 수 있습니다.
뒤에서 배울건데 스프링의 AOP도 딱 이렇게 동작합니다. 조금 차이는 있지만 이 원리로 돌아간다고 이해하면 돼요. 가짜를 만들고 돌아간다는 거죠.
그런데 중요한 게 있어요. 클라이언트 코드를 전혀 고치지 않은 거예요. 이게 스프링 컨테이너가 가진 어마어마한 큰 장점이죠.
예를 들어서, 여러분이 어떤 URL로 들어온 거를 전체 고객 요청당 몇 초가 걸렸는지 다 찍고 싶어요. 그럼 스프링 AOP랑 이런 프록시 기술을 같이 활용을 하면 정말 편리하게 그걸 사용하고, 코드를 고칠 필요 없이 그게 가능하거든요. 그런 게 다 이런 원리로 동작합니다.
이게 진짜 스프링 컨테이너가 가진 강점이죠.
이제 주의점을 보면,

마치 싱글톤을 사용하는 것 같지만, 그래도 다르게 동작하기 때문에 결국 주의해서 사용해야 합니다.

누가 이걸 보고 '싱글톤이네~' 하고 사용했다가는 결과적으로 내부에서는 HTTP 요청별로 myLogger가 각각 따로 생성되는 건데 싱글톤으로만 봤다가는 위험하죠.
그리고 이런 특별한 scope는 꼭 필요한 곳에만 최소화해서 사용해야 돼요. 무분별하게 사용하면 유지보수하기 어려워집니다.
특히 이런 싱글톤이 아닌, request나 다른 스코프들은 사실 이렇게 대놓고 사용한다기 보다는 약간 백그라운드에서 사용하는 경우가 많습니다. 그리고 이런 스코프들은 또 테스트 하기도 되게 까다로워져요.
그래서 정말 꼭 필요한 데만 사용하는 것을 권장을 드리겠습니다.
그렇게 해서 빈 스코프는 여기서 마무리 하겠습니다.
'스프링 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
| 다음으로 (0) | 2024.01.29 |
|---|---|
| 스코프와 Provider (0) | 2024.01.28 |
| request 스코프 예제 만들기 (0) | 2024.01.28 |
| 웹 스코프 (0) | 2024.01.28 |
| 프로토타입 스코프 - 싱글톤 빈과 함께 사용시 Provider로 문제 해결 (0) | 2024.01.28 |