당니의 개발자 스토리

스코프와 Provider 본문

스프링/스프링 핵심 원리 - 기본편

스코프와 Provider

clainy 2024. 1. 28. 02:15

스코프와 Provider

스코프와 Provider, 첫번째 해결방안은 Provider를 사용하는 건데요.

간단하게 ObjectProvider를 사용해볼게요.


LogDemoController에서,

이렇게 하면, MyLogger를 의존관계 주입받는 게 아니라, MyLogger 찾을 수 있는, 즉 dependency lookup으로 찾을 수 있는 기능을 가진 ObjectProvider가 주입이 되죠.

그러면 주입 시점에 주입을 받을 수 있으므로,

여기서 myLogger를 받는 거예요. 이렇게 하고 다시 돌려보면, 또 똑같은 오류가 발생합니다.

이제 LogDemoService로 들어가서,

여기서도 ObjectProvider를 주입받으면, 실제로 LogDemoController에 요청이 와서,

컨트롤러에 고객 요청이 왔다는 건 지금 HTTP가 살아있다는 소리죠. 그래서 request 스코프를 쓸 수 있는 상황이 되죠. HTTP Request가 들어온 상황이니까요.

그러면 logdemo에서 myLoggerProvider.getObject()를 호출하기 때문에 이제는 꺼낼 수 있습니다. 다시 돌려보면,

드디어 스프링이 떴습니다. 그리고 사이트도 들어가 보면 OK가 뜨는 걸 볼 수 있습니다.

생성과 소멸이 잘 나오고, 중요한 건 로거입니다. 앞에 uuid가 다 같은 걸 볼 수 있죠. 이 4개가 한명의 고객 요청입니다.

사이트를 여러번 새로고침하면,

새로운 uuid를 가진 애들이 또 출력되는 걸 볼 수 있습니다. 이 uuid로 구분하는 거예요.

그래서 코드를 보면서 처음부터 보면,

HTTP 요청이 오면서 처음으로 myLogger를 달라고 하면,

정확히 이 시점에 만들어집니다. getObject()를 최초로 하는 시점에 myLogger가 만들어지고요.

그 때, init() 메서드가 호출되면서,

나의 request랑 연결시키면서 uuid에 값을 넣어놓고,

setter로 requestURL을 담아놓고 그 다음에 log를 찍습니다.

그러면 이미 로그를 찍는 당시에는

uuidrequestURL이 다 있기 때문에 잘 찍히고요.

이 서비스의 logic()에서도 로그를 찍을 때 이미 정보가 세팅되어 있기 때문에

메시지만 추가해서 나가게 됩니다.

핵심은 동시에 여러 요청이 온다 하더라도, 요청마다 각각 객체를 따로 관리를 해준다는 게 핵심이에요. 혹시 또 의심이 있는 분들을 위해서 한번 thead.sleep 살짝 걸어보겠습니다.

서버를 끄고 다시 실행하면,

하나 요청하면 예쁘게 나오죠. 그런데 웹 페이지를 빠르게 새로고침하면,

1초씩 기다렸다가

고객 요청이 들어오면, MyLogger의 init()이랑 controller test까지 찍어요. 그리고 나서 sleep 때문에 service 로직은 나중에 찍겠죠. 그런데 그 사이에 다른 애들이 찍어버린 거예요.

그렇지만 나중에 찍는다고 하더라도 uuid 값은 유지가 되는 걸 확인할 수 있습니다.

그래서 요청이 막 섞여도 구분 할 수가 있고, 요청이 아무리 많아도 스프링이 빈을 요청마다 각각 따로 할당해줘요. 만약 요청이 3개 있으면 request 스코프는 3개를 다 각각 빈을 할당해줍니다.

그래서 멤버 변수를 request 단위로 활용할 수 있습니다.


자 이제 정리해 보면요.

main() 메서드로 스프링을 실행하고, 웹 브라우저에 http://localhost:8080/log-demo를 호출하면,

이렇게 나왔습니다.

ObjectProvider 덕분에 ObjectProvider.getObject() 를 호출하는 시점까지 request scope 빈(MyLogger)의 생성을 지연할 수 있었죠.

보다 정확한 표현은 스프링 컨테이너한테 빈을 요청하는 걸 지연할 수가 있었습니다. 그래서 그 시점에 스프링 빈이 나중에 생성되죠.

ObjectProvider.getObject()를 호출하시는 시점에는 HTTP 요청이 진행중이므로 request scope 빈 의 생성이 정상 처리됐고요.

ObjectProvider.getObject()를 LogDemoController , LogDemoService 에서 각각 한번씩 따로 호출해도 같은 HTTP 요청이면 같은 스프링 빈이 반환됩니다.

즉, DL을 하는데, 컨트롤러에서 하든, LogService에서 찾든 각각 한번씩 따로 호출해도 내가 현재 같은 HTTP request면, 같은 스프링 빈이 반환이 됩니다.

확인해봤죠. 같은 uuid(= 같은 request) 이면서 같은 참조값입니다.

내가 요청마다 직접 구분하고 ObjectProvider 같은 기능을 구현하려고 하면 엄청 어려워요. 스프링을 쓰면 진짜 너무 편하게 할 수 있는 거죠.

그래서 대략 어떻게 동작하는지는 이해가 되실 거고요.

그래서 위에 그림을 다시 보면,

클라이언트 A가 요청을 하고 클라이언트 B도 요청을 했어요. 클라이언트 A가 요청을 하면 동시에 요청이 와도 MyLoggerClient A 전용으로 생성해주고, 클라이언트 B의 요청에는 거기에 맞춰서 Client B 전용이 생성됩니다.

그리고 서비스에서 호출을 해도 Client A는 항상 같은 것을 보여야 되고요. Client B는 어디에서 요청을 해도 그 요청은 항상 같은 B 인스턴스에 접근하게 됩니다. 같은 스프링 빈에 접근하게 됩니다.

그런데 이 정도에서 끝내도 될 것 같지만 개발자들의 이 코드 몇 자를 더 줄이려는 욕심이 끝이 없어요.

Provider마저도 귀찮은 거에요.

이렇게 해도 훌륭하지만, Provider에서 꺼내고 귀찮잖아요.

그래서 처음 오류났던 코드처럼 그냥 이렇게 바로 주입받아서 할 수 있는 방법이 없을까? 고민을 한 거예요.

방법이 있습니다. 프록시를 사용하면 돼요.

다음 시간에 스코프와 프록시에서 말씀을 드리겠습니다.