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` : 웹 서블릿 컨텍스트와 같은 유지되는 스코프
+
+
+# 프로토타입 스코프
+- 지금까지 배운것은 싱글톤 스코프이니까 프로토타입 부터 알아보자.
+
+
+
+- 싱글톤 스코프는 요청 할 떄 마다 항상 같은 인스턴스를 반환했다.
+1. 싱글톤 스코프의 빈을 스프링 컨테이너에 요청한다.
+2. 스프링 컨테이너는 본인이 관리하는 스프링 빈을 반환한다.
+3. 이후에 스프링 컨테이너에 같은 요청이 와도 같은 객체 인스턴스의 스프링 빈을 반환한다.
+
+
+
+
+- 반면에 프로토타입 스코프는 요청 할 때 마다 새로운 인스턴스를 생성하여 반환한다.
+1. 프로토타입 스코프의 빈을 스프링 컨테이너에 요청한다.
+2. 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입한다.
+ - 조회하기 직전에 생성된다.
+
+
+
+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` 메서드가 호출되지 않는 것을 볼 수 있다.
+ - 즉, 프로토타입 빈은 스프링 컨테이너에서 관리하지 않는다는 것을 알 수 있다.
+
+
+## 프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점
+
+### 프로토타입 빈 요청
+
+
+- 사용자가 빈을 가져와서 로직을 실행해 내부 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이 나오는 것을 확인할 수 있다.
+
+## 싱글톤 빈에서 프로토타입 빈 사용
+
+
+
+- ClientBean 은 싱글톤이므로, 보통 컨테이너 시점에 생성되고, 의존관계 주입도 일어난다.
+- ClientBean 의존관계 주입 시점에 프로토타입 빈을 요청한다.
+- 프로토타입 빈을 생성해서 `clientBean` 에 반환한다. 프로토타입 빈의 count 필드 값은 0이다.
+- 이제 `clientBean` 은 프로토타입 빈을 내부 필드에 보관한다.
+
+
+
+- 여기서 문제는 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` : 웹 소켓과 동일한 생명주기를 가지는 스코프
+
+
+
+- 각각의 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)` 로 조회해도 프록시 객체가 조회되는 것을 확인할 수 있다.
+
+
+**가짜 프록시 객체는 요청이 오면 그때 내부에서 진짜 빈을 요청하는 위임 로직이 들어있다.**
+
+- 가짜 프록시 객체는 내부에 진짜 `myLogger` 를 찾는 방법을 알고 있다.
+- 클라이언트가 `myLogger.logic()` 을 호출하면 실제로는 가짜 프록시 객체의 메서드를 호출한 것이다.
+ - 메서드 호출을 하면 내부에서 진짜 프록시 객체를 찾아서 해당 메서드를 호출한다.
+- 가짜 프록시 객체는 원본 클래스를 상속 받아서 만들어졌기 때문에 이 객체를 사용하는 클라이언트 입장에서는 사실 원본인지 아닌지도 모르게, 동일하게 사용할 수 있다(**다형성**)
+
+### 특징정리
+- 프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯이 편리하게 request scope를 사용할 수 있다.
+- 사실 Provider를 사용하든, 프록시를 사용하든 핵심 아이디어는 **진짜 객체 조회를 꼭 필요한 시점까지 지연처리** 한다는 점이다.
+ - 요청이 올 때 까지는 가짜로 버티는 것이다.
+- 단지 애노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체할 수 있다.
+ - 이것이 바로 다형성과 DI 컨테이너가 가진 큰 강점이다.
+- 꼭 웹 스코프가 아니어도 프록시는 사용할 수 있다.
+
+### 주의점
+- 마치 싱글톤을 사용하는 것 같지만 다르게 동작하기 때문에 결국 주의해서 사용해야 한다.
+- 이런 특별한 scope는 꼭 필요한 곳에만 최소화해서 사용하자, 무분별하게 사용하면 유지보수하기 어려워진다.