diff --git "a/section9/\355\203\200\354\271\270.md" "b/section9/\355\203\200\354\271\270.md" new file mode 100644 index 0000000..a824ba2 --- /dev/null +++ "b/section9/\355\203\200\354\271\270.md" @@ -0,0 +1,654 @@ +# 스프링 빈 +- 우리는 지금까지 스프링 빈은 스프링 컨테이너가 생성되고 종료도리 때 까지 관리된다고 알고 있었다. +- 이는 스프링 빈이 기본적으로 싱글톤 스코프로 생성되기 때문이다. +- 스코프는 번역 그대로 빈이 존재할 수 있는 범위를 나타낸다. + +## 스프링 빈 스코프 종류 +- 싱글톤 + - 기본 스코프, 스프링 컨테이너가 시작되고 종료될 때 까지 유지되는 가장 넓은 범위 +- 프로토타입 + - 스프링 컨테이너는 빈을 생성하고 의존관계 주입까지만 관여하는 범위 + - 매우 짧은 범위 +- 웹 관련 스코프 + - `request` : 웹 요청이 들어오고 나갈 떄 까지의 유지되는 스코프 + - `session` : 웹 세션이 생성되고 종료될 떄 까지의 유지되는 스코프 + - `application` : 웹 서블릿 컨텍스트와 같은 유지되는 스코프 + + +# 프로토타입 스코프 +- 지금까지 배운것은 싱글톤 스코프이니까 프로토타입 부터 알아보자. + +image + +- 싱글톤 스코프는 요청 할 떄 마다 항상 같은 인스턴스를 반환했다. +1. 싱글톤 스코프의 빈을 스프링 컨테이너에 요청한다. +2. 스프링 컨테이너는 본인이 관리하는 스프링 빈을 반환한다. +3. 이후에 스프링 컨테이너에 같은 요청이 와도 같은 객체 인스턴스의 스프링 빈을 반환한다. + + +image + +- 반면에 프로토타입 스코프는 요청 할 때 마다 새로운 인스턴스를 생성하여 반환한다. +1. 프로토타입 스코프의 빈을 스프링 컨테이너에 요청한다. +2. 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입한다. + - 조회하기 직전에 생성된다. + +image + +3. 스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환한다. +4. 이후에 스프링 컨테이너에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환한다. + +- 핵심은 **스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계를 주입하고, 초기화까지만 처리**한다는 것이다. +- 이후 스프링 컨테이너는 프로토타입 빈을 관리하지 않는다. +- **프로토타입 빈을 관리할 책임은 빈을 받은 클라이언트**가 가지게 된다. + +## 싱글톤 스코프 빈 테스트 + +```java +public class SingletonTest { + + @Test + void singletonFindTest() { + AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class); + + SingletonBean singletonBean1 = ac.getBean(SingletonBean.class); + SingletonBean singletonBean2 = ac.getBean(SingletonBean.class); + System.out.println("singletonBean1 = " + singletonBean1); + System.out.println("singletonBean2 = " + singletonBean2); + + assertThat(singletonBean1).isEqualTo(singletonBean2); + ac.close(); + } + + + @Scope("singleton") + static class SingletonBean { + + @PostConstruct + public void init() { + System.out.println("Singleton.init"); + } + + @PreDestroy + public void close() { + System.out.println("Singleton.close"); + } + } +} +``` +``` +Singleton.init +singletonBean1 = hello.core.scope.SingletonTest$SingletonBean@78fa769e +singletonBean2 = hello.core.scope.SingletonTest$SingletonBean@78fa769e +Singleton.close +``` + +- `new AnnotationConfigApplicationContext(SingletonBean.class);` + - 클래스 자체를 지정해주면 컴포넌트 스캔 대상처럼 동작하기 때문에 해당 객체가 스프링 빈으로 등록된다. + +## 프로토타입 빈 테스트 +```java +public class PrototypeTest { + + @Test + void prototypeBeanFind() { + AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class); + System.out.println("find prototypeBean1"); + PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class); + System.out.println("find prototypeBean2"); + PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class); + + System.out.println("prototypeBean1 = " + prototypeBean1); + System.out.println("prototypeBean2 = " + prototypeBean2); + assertThat(prototypeBean1).isNotSameAs(prototypeBean2); + ac.close(); + } + + + @Scope("prototype") + static class PrototypeBean { + @PostConstruct + void init() { + System.out.println("PrototypeBean.init"); + } + + @PreDestroy + void close() { + System.out.println("PrototypeBean.close"); + } + } +} +``` +``` +find prototypeBean1 +PrototypeBean.init +find prototypeBean2 +PrototypeBean.init +prototypeBean1 = hello.core.scope.PrototypeTest$PrototypeBean@78fa769e +prototypeBean2 = hello.core.scope.PrototypeTest$PrototypeBean@16612a51 +``` +- 조회하는 시점에 init 이 호출되는 것을 출력으로 확인할 수 있다. +- 서로 다른 인스턴스인 것을 확인할 수 있다. + - 프로토타입 빈은 조회할 때 마다 새로운 인스턴스를 반환한다는 것을 알 수 있다. +- `ac.close()` 로 스프링 컨테이너를 종료했지만 `close` 메서드가 호출되지 않는 것을 볼 수 있다. + - 즉, 프로토타입 빈은 스프링 컨테이너에서 관리하지 않는다는 것을 알 수 있다. + + +## 프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점 + +### 프로토타입 빈 요청 +image + +- 사용자가 빈을 가져와서 로직을 실행해 내부 count 값을 1 증가 시킨다. +- 프로토타입 빈은 사용자 요청 시 마다, 인스턴스를 생성하기 떄문에 각각 사용자 마다 내부 로직 실행 결과는 1이 된다. +- 해당 작업을 코드로 확인해보면 아래와 같다. + +```java +public class SingletonWithPrototypeTest1 { + + @Test + void prototypeFIndTest() { + AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class); + + PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class); + prototypeBean1.addCount(); + assertThat(prototypeBean1.getCount()).isEqualTo(1); + + PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class); + prototypeBean2.addCount(); + assertThat(prototypeBean2.getCount()).isEqualTo(1); + } + + @Scope("prototype") + static class PrototypeBean { + private int count = 0; + + public void addCount() { + count++; + } + + public int getCount() { + return count; + } + + @PostConstruct + public void init() { + System.out.println("PrototypeBean.init " + this); + } + + @PreDestroy + public void close() { + System.out.println("PrototypeBean.close " + this); // 호출안될듯 + } + } +} +``` + +- 사용자마다 count 값이 1이 나오는 것을 확인할 수 있다. + +## 싱글톤 빈에서 프로토타입 빈 사용 + +image + +- ClientBean 은 싱글톤이므로, 보통 컨테이너 시점에 생성되고, 의존관계 주입도 일어난다. +- ClientBean 의존관계 주입 시점에 프로토타입 빈을 요청한다. +- 프로토타입 빈을 생성해서 `clientBean` 에 반환한다. 프로토타입 빈의 count 필드 값은 0이다. +- 이제 `clientBean` 은 프로토타입 빈을 내부 필드에 보관한다. + +image + +- 여기서 문제는 ClientBean 은 싱글톤이므로 여러 사용자가 공유하게 되는데 ClientBean 내부에 있는 프로토타입이 공유된다는 것이다. +- 주입 시점에 요청을 하여 프로토타입 빈이 생성된 것이지, 요청마다 프로토타입 빈이 새롭게 생성되는 것이 아니다. +- 즉, 사용자들이 공통된 프로토타입을 사용하게 된다. + - 클라이언트 A 가 로직을 실행해 count 가 1이 된다. + - 이후, 클라이언트 B 가 로직을 실행하면 count 가 2가 된다. + - 즉, 의도한대로 동작하지 않는다. +- 코드로 확인해보면 아래와 같다. + +```java +public class SingletonWithPrototypeTest1 { +... + @Test + void singletonClientUsePrototype() { + AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, + PrototypeBean.class); + ClientBean clientBean1 = ac.getBean(ClientBean.class); + assertThat(clientBean1.logic()).isEqualTo(1); + + ClientBean clientBean2 = ac.getBean(ClientBean.class); + assertThat(clientBean2.logic()).isEqualTo(2); + } + + @Scope + @RequiredArgsConstructor + static class ClientBean { + + private final PrototypeBean prototypeBean; + + public int logic() { + prototypeBean.addCount(); + return prototypeBean.getCount(); + } + } + + @Scope("prototype") + static class PrototypeBean { + ... + } +} +``` + +- 싱글톤 빈이 프로토타입 빈을 사용하는 것이다. +- 그런데 싱글톤 빈의 생성 시점에만 주입 받기 떄문에 싱글톤 빈 생성 시점에 프로토타입 빈이 새롭게 생성된다. +- 하지만 싱글톤 빈과 함께 유지되는 것이 문제이다. +- 프로토타입 빈을 사용한다면, 사용될 때 마다 객체가 새롭게 생성되는 것을 원할 것이다. + +## 싱글톤 빈과 함께 사용시 Provider로 문제 해결 + +- 가장 간단하면서 무식한 방법으로는 싱글톤 빈이 프로토타입 빈을 사용할 때 마다 컨테이너에 새롭게 요청하는 방식이다. + +```java +@Test +void singletonClientUsePrototype() { + AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, + PrototypeBean.class); + ClientBean clientBean1 = ac.getBean(ClientBean.class); + assertThat(clientBean1.logic()).isEqualTo(1); + + ClientBean clientBean2 = ac.getBean(ClientBean.class); + assertThat(clientBean2.logic()).isEqualTo(1); +} + +@Scope +@RequiredArgsConstructor +static class ClientBean { + + private final ApplicationContext ac; + + public int logic() { + PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class); // 이 부분이 변경 + prototypeBean.addCount(); + return prototypeBean.getCount(); + } +} + +@Scope("prototype") +static class PrototypeBean { + ... +} +``` + +- `ac.getBean` 메서드를 호출하여 컨테이너에 프로토타입 객체를 요청하는 방식이다. + - 의존관계를 주입 받는 것이 아니라 이렇게 직접 필요한 의존관계를 찾는 것을 의존관계 조회(DL : Dependency Loockup)이라고 한다. +- 그런데 이러면 스프링에 종속적인 코드가 되면서 단위 테스트하기 불편해진다. +- 지금 필요한 기능은 컨테이너에서 빈을 찾아서 주는 기능, 딱 DL 기능만 해주면 된다. + - 스프링에서 이런 기능을 제공해준다. + +## ObjectFactory, ObjectProvider +- 지정된 빈을 찾아서 제공해주는 DL 서비스를 제공해주는 것이 있는데 `ObjectProvider` 이다. +- 과거에는 `ObjectFactory` 를 사용했는데 여기에 편의적인 기능이 더해진 것이 `ObjectProvider` 이다. + +```java +@Test +void singletonClientUsePrototype() { + AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, + PrototypeBean.class); + ClientBean clientBean1 = ac.getBean(ClientBean.class); + assertThat(clientBean1.logic()).isEqualTo(1); + + ClientBean clientBean2 = ac.getBean(ClientBean.class); + assertThat(clientBean2.logic()).isEqualTo(1); +} + +@Scope +@RequiredArgsConstructor +static class ClientBean { + + private final ObjectProvider prototypeBeanProvider; + + public int logic() { + PrototypeBean prototypeBean = prototypeBeanProvider.getObject(); + prototypeBean.addCount(); + return prototypeBean.getCount(); + } +} +``` + +- `ObjectProvider` 를 사용하면 ApplicationContext 를 통해 빈을 찾는 것이 아니라 간단히 컨테이너에서 해당 빈을 요청해서 반환해준다. + - 우리가 스프링 기능을 전부 사용하는 것이 아니라 단순히 DL 기능만 사용하는 것이다. +- 찾아주는 과정을 간단하게 해주는 것이다. +- 스프링이 제공하는 기능이지만, 기능이 단순하므로 단위테스트를 만들거나 mock 코드를 만드는 것이 쉬워진다. +- `ObjectProvider` 는 딱 DL 기능 정도만 해준다. + - `new AnnotationConfigApplicationContext(ClientBean.class)` 와 같이 빈을 등록하는 코드가 없는데, `ObjectProvider` 가 빈 등록도 같이 해준다. + +### 특징 +- ObjectFactory: 기능이 단순, 별도의 라이브러리 필요 없음, 스프링에 의존 +- ObjectProvider: ObjectFactory 상속, 옵션, 스트림 처리등 편의 기능이 많고, 별도의 라이브러리 필요 없음, 스프링에 의존 +- 둘다 스프링에 의존하는 문제가 있음 + +## JSR-330 Provider + +``` +implementation 'jakarta.inject:jakarta.inject-api:2.0.1' //gradle 추가 필수 +``` + +```java +@Scope +@RequiredArgsConstructor +static class ClientBean { + + private final Provider prototypeBeanProvider; + + public int logic() { + PrototypeBean prototypeBean = prototypeBeanProvider.get(); + prototypeBean.addCount(); + return prototypeBean.getCount(); + } +} +``` + +- `provider.get()` 으로 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있다. +- `get` 메서드를 호출하면 스프링 컨테이너가 해당 빈을 찾아서 반환한다. (DL) +- 자바 표준이고, 기능이 단순해서 단위테스트를 만들거나 mock 코드를 만들기가 훨씬 편하다. +- Provider 지금 딱 원하는 기능 만큼만 제공한다. + +### 특징 +- `get()` 메서드 하나로, 매우 단순하다. +- 별도의 라이브러리가 필요하다. +- 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용이 가능하다. + +### 정리 +- 프로토타입 빈을 언제 사용하면 좋을까? +- 요청마다 의존관계가 주입된 새로운 객체가 필요할 떄 사용하면 된다. + - 그런데 잘 필요하지 않는다. (매우 드물다) + - 대부분 싱글톤으로도 문제를 해결할 수 있다. +- `ObjectProvider` , `JSR-330 Provider` 는 프로토타입 뿐만 아니라 DL 을 사용할 때 사용된다. + - 지연이나 순환 참조에서 활용할 수 있는 것 같다. + +- 참고 : 그래서 어떤걸 사용해야할까? + - 스프링이 제공하는 기능을 사용하자. + - 특별히 스프링이 아닌 다른 컨테이너를 사용하는 경우 JSR 을 활용하자. + + +# 웹 스코프 + +## 특징 +- 웹 스코프는 웹 환경에서만 동작 +- 프로토타입 스코프와 다르게 스프링이 스코프 종료시점까지 관리해준다. + - 따라서 종료 메서드가 호출됨 + +## 종류 +- `request` : HTTP 요청이 들어오고, 나갈 때 까지 유지되는 스코프이다. 각각의 요청마다 별도의 빈 인스턴스가 생성된다. +- `session` : HTTP Session과 동일한 생명주기를 가지는 스코프 +- `application` : 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프 +- `websocket` : 웹 소켓과 동일한 생명주기를 가지는 스코프 + +image + +- 각각의 HTTP 요청마다 별도의 인스턴스가 생성된다. +- 이후 라이프사이클 동안 전용 빈으로 관리된다. + - 서비스에서 호출 시, 해당 요청을 보낸 고객의 빈을 조회한다. + +## 예제 + +``` +implementation 'org.springframework.boot:spring-boot-starter-web' +``` +- 웹 라이브러리 추가 + + +- 고객 요청이 동시적으로 여러곳에서 오면 구분하기 힘들다. +- 그래서 우리는 다음과 같이 로그를 남기는 기능을 reqeust 스코프를 활용해서 만들어볼 것이다. +``` +[d06b992f...] request scope bean create +[d06b992f...][http://localhost:8080/log-demo] controller test +[d06b992f...][http://localhost:8080/log-demo] service id = testId +[d06b992f...] request scope bean close +``` + +- 기대하는 공통 포멧: [UUID][requestURL] {message} +- UUID 를 사용해서 HTTP 요청을 구분하자 +- requestURL 정보도 추가로 넣어서 어떤 URL을 요청해서 남은 로그인지 확인하자. + + +**MyLogger.java** +```java +@Component +@Scope("request") +public class MyLogger { + + private String uuid; + private String requestURL; + + public void setRequestURL(String requestURL) { + this.requestURL = requestURL; + } + + public void log(String message) { + System.out.println("[" + uuid + "][" + requestURL + "] " + message); + } + + @PostConstruct + public void init() { + uuid = UUID.randomUUID().toString(); + System.out.println("[" + uuid + "] request scope bean create: " + this); + } + + @PreDestroy + public void close() { + System.out.println("[" + uuid + "] request scope close: " + this); + } +} +``` + +- 로그를 출력하기 위한 클래스 `MyLogger` +- 생성될 때, 고유의 UUID 를 얻게 된다. +- 스코프 범위가 `request` 이므로, 요청이 발생하는 시점에 생성된다. +- `requestURL` 은 빈이 생성되는 시점에 알 수 없기 때문에 외부에서 `setRequestURL` 로 주입 받는다. + +**LogDemoController.java** +```java +@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"; + } +} +``` + +- requestURL 값 : `http://localhost:8080/log-demo` +- 해당 URL로 요청하면 위 메서드가 실행된다. +- 이렇게 받은 URL 은 myLogger 에 저장된다. + - myLogger 는 HTTP 요청 마다 각각 구분되므로 다른 HTTP 요청과 값이 섞일 걱정은 하지 않아도 된다. + +**LogDemoService.java** +```java +@Service +@RequiredArgsConstructor +public class LogDemoService { + + private final MyLogger myLogger; + + public void logic(String id) { + myLogger.log("service id = " + id); + } +} + +``` + +- request scope 덕분에 서비스 계층에는 웹과 관련된 정보들을 주지 않아도 된다. + - 서비스 계층은 웹 기술에 종속되지 않고 가급적 순수하게 유지하는 것이 유지보수 관점에 좋기 때문 + + +### 실행 +**기대하는 출력** +``` +[d06b992f...] request scope bean create +[d06b992f...][http://localhost:8080/log-demo] controller test +[d06b992f...][http://localhost:8080/log-demo] service id = testId +[d06b992f...] request scope bean close +``` + +**실제는 기대와 다르게 오류 발생** +``` +Error creating bean with name 'myLogger': Scope 'request' is not active for the +current thread; consider defining a scoped proxy for this bean if you intend to +refer to it from a singleton; +``` + +- 오류가 발생하는 이유는 다음과 같다. +- 스프링 컨테이너가 뜰때, 컨트롤러에서 `MyLogger` 를 주입하려고 시도한다. + - `@Autowired` 되어 있기 때문 (생성자가 하나이므로 위 코드에서는 생략되어 있을 뿐) +- 이때, 스프링 컨테이너한테 `MyLogger` 를 달라고 요청하는데, **`MyLogger` 는 스코프가 `request` 이기 때문에 HTTP 요청 전에는 존재하지 않는다**. + - `MyLogger` 의 생명 주기는 HTTP 요청이 들어올 때부터인데, 스프링 컨테이너가 뜰 당시에는 HTTP 요청 전이기 떄문에 존재하지 않는 것이다. +- 이를 해결하기 위해선 이전에 배운 `Provider` 를 활용하면 된다. + +# 스코프와 Provider + +**LogDemoController.java** +```java +@Controller +@RequiredArgsConstructor +public class LogDemoController { + + private final LogDemoService logDemoService; + private final ObjectProvider myLoggerProvider; + + @RequestMapping("log-demo") + @ResponseBody + public String logDemo(HttpServletRequest request) { + String requestURL = request.getRequestURL().toString(); + MyLogger myLogger = myLoggerProvider.getObject(); + myLogger.setRequestURL(requestURL); + ... + } +} +``` +- `myLoggerProvider.getObject();` 와 같이 getObject 를 최초로 호출한 시점에 myLogger 가 처음 만들어진다. + +**LogDemoService.java** +```java +@Service +@RequiredArgsConstructor +public class LogDemoService { + + private final ObjectProvider myLoggerProvider; + + public void logic(String id) { + myLoggerProvider.getObject().log("service id = " + id); + } +} + +``` + +- `http://localhost:8080/log-demo` 다시 요청하면 잘 동작한다. + +- `ObjectProvider` 덕분에 `ObjectProvider.getObject()` 를 호출하는 시점까지 request scope **빈의 생성을 지연**할 수 있다. + - 더 정확하게 말하자면 생성을 지연하기 보단 **스프링 컨테이너에 요청하는 것을 지연**하는 것이다. +- `ObjectProvider.getObject()` 를 호출하는 시점에는 HTTP 요청이 진행중이므로 request scope 빈의 생성을 정상적으로 처리할 수 있다. +- `ObjectProvider.getObject()` 를 `LogDemoController` , `LogDemoService` 에서 **각각 따로 호출하여도 같은 HTTP 요청이면 같은 스프링 빈이 반환된다.** + - 개발자가 직접 구현하려고 하면 너무 힘들것이다. +- 여기서 끝내도 될 것 같은데, 인간의 욕심은 끝이 없다. + +# 스코프와 프록시 + +```java +@Component +@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) +public class MyLogger { + ... +} +``` +- 여기가 핵심이다. proxyMode = ScopedProxyMode.TARGET_CLASS 를 추가해주자. + - 적용 대상이 클래스이면 TARGET_CLASS + - 적용 대상이 인터페이스면 INTERFACES 를 선택하면 된다. +- 이렇게 하면 `MyLogger` 의 가짜 프록시 클래스를 만들어두고 HTTP request 와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 만들어 주입해 둘 수 있다. + - 마치 Provider 주입하듯이 진짜가 아니라 가짜를 주입한다. + - 쉽게 말하면 진짜 `MyLogger` 가 아니라 껍데기 `MyLogger` 를 주입해둔다. + - 그리고 실제 `MyLogger` 의 기능을 호출하는 시점에 진짜를 찾아서 동작한다 + +```java +@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"; + } +} +``` + +```java +@Service +@RequiredArgsConstructor +public class LogDemoService { + + private final MyLogger myLogger; + + public void logic(String id) { + myLogger.log("service id = " + id); + } +} +``` +- Controller, Service 는 이전의 Provider 를 사용하지 않았던 시점으로 바꾼다. + +### 웹 스코프와 프록시 동작 원리 + +먼저 주입된 myLogger를 확인해보자. + +```java +System.out.println("myLogger = " + myLogger.getClass()); +``` + +**출력결과** + +``` +myLogger = class hello.core.common.MyLogger$$EnhancerBySpringCGLIB$$b68b726d +``` + +- **CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다.** +- Scope 의 `proxyMode = ScopedProxyMode.TARGET_CLASS)` 를 설정하면 이전에 배운 AppConfig CGLIB 라는 바이트 코드 조작 라이브러리가 헤딩 겍체를 상속 받는 가짜 프록시 객체를 생성한다. +- 그리고 스프링 컨테이너에는 `myLogger` 라는 이름 대신 이 가짜 프록시 객체를 등록한다. +- 그리고 `ac.getBean("myLogger", MyLogger.class)` 로 조회해도 프록시 객체가 조회되는 것을 확인할 수 있다. +image + +**가짜 프록시 객체는 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.** + +- 가짜 프록시 객체는 내부에 진짜 `myLogger` 를 찾는 방법을 알고 있다. +- 클라이언트가 `myLogger.logic()` 을 호출하면 실제로는 가짜 프록시 객체의 메서드를 호출한 것이다. + - 메서드 호출을 하면 내부에서 진짜 프록시 객체를 찾아서 해당 메서드를 호출한다. +- 가짜 프록시 객체는 원본 클래스를 상속 받아서 만들어졌기 때문에 이 객체를 사용하는 클라이언트 입장에서는 사실 원본인지 아닌지도 모르게, 동일하게 사용할 수 있다(**다형성**) + +### 특징정리 +- 프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯이 편리하게 request scope를 사용할 수 있다. +- 사실 Provider를 사용하든, 프록시를 사용하든 핵심 아이디어는 **진짜 객체 조회를 꼭 필요한 시점까지 지연처리** 한다는 점이다. + - 요청이 올 때 까지는 가짜로 버티는 것이다. +- 단지 애노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체할 수 있다. + - 이것이 바로 다형성과 DI 컨테이너가 가진 큰 강점이다. +- 꼭 웹 스코프가 아니어도 프록시는 사용할 수 있다. + +### 주의점 +- 마치 싱글톤을 사용하는 것 같지만 다르게 동작하기 때문에 결국 주의해서 사용해야 한다. +- 이런 특별한 scope는 꼭 필요한 곳에만 최소화해서 사용하자, 무분별하게 사용하면 유지보수하기 어려워진다.