[Spring] Bean Scope

2021. 10. 10. 08:00Back-End 작업실/Spring Framework

728x90
반응형

📋 목차


01. [Spring] 스프링 핵심 원리
02. [Spring] 좋은 객체 지향 설계의 5가지 원칙 (SOLID)
03.
[Spring] 객체 지향 설계와 스프링   
04. [Spring] OCP와 DIP 고려하기
05. [Spring] 객체 지향 원리 적용 - 새로운 할인 정책 개발
06.
[Spring] AppConfig 리팩터링    
07. [Spring] Ioc, DI Container  
08. [Spring] 스프링 컨테이너 생성  
09. [Spring] 싱글톤 컨테이너  
10. [Spring] Component Scan And Auto wired Start
11.
[Spring] 의존관계 주입 방법    
12. [Spring] Bean Life Cycle Call Back
13.
[Spring] Bean Scope

 

 

 

 

📌 빈 스코프


스프링 빈이 스프링 컨테이너의 시작과 함께 생성되어 스프링 컨테이너가 종료될 때까지 유지되는 것이에요.

이것은 스프링 빈이 기본적으로 싱글톤 스코프로 생성되었기 때문인데, 스코프는 빈이 존재할 수 있는 범위를 뜻하는 것이에요.

 

    📍 스프링의 지원 스코프

  1. 싱글톤 : 기본 스코프, 스프링 컨테이너의 시작과 종료될 때까지 유지되는 가장 넓은 범위의 스코프
  2. 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존 관계 주입까지만 관여하고, 더 이상 관여하지 않는다.
                     즉, 생성을 하고, 요청자에게 응답으로 던져주기만 하고, 신경쓰지 않는다.
  3. 웹 관련 스코프 :
    • request : Web 요청이 들어오고, 응답으로 나갈 때까지 유지되는 스코프
    • session : Web Session이 생성되고, 종료될 때 까지 유지되는 스코프
    • application : Web Sevlet Context와 같은 범위로 유지되는 스코프

 

빈 스코프는 다음과 같이 지정할 수 있는 것이에요.

 

Component Scan Auto Registration

 

Component Scan Manual Registration

 

 

 

    📍 Prototype Scope

싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스의 스프링 빈을 반환하는 것이에요. 반면 프로토타입 스코프는 스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환하는 것이에요.

 

 

         👉 싱글톤 빈 요청

 

먼저 싱글톤 스코프의 빈을 스프링 컨테이너에 요청을 하면 스프링 컨테이너는 자신이 관리하는 스프링 빈을 반환하는 것이에요. 그런 뒤 스프링 컨테이너에 같은 요청이 와도 같은 객체 인스턴스를 반환 한답니다!

 

 

         👉 프로토타입 빈 요청 1

최초 프로토타입 스코프의 빈을 스프링 컨테이너에 요청하면 스프링 컨테이너는 그 시점에 프로토타입 빈을 만들고, 필요한 의존 관계를 주입하는 것이에요.

 

 

         👉 프로토타입 빈 요청 2

스프링 컨테이너가 생성한 프로토타입 빈을 클라이언트에게 반환하면 스프링 컨테이너에 같은 요청이 왔을 때, 항상 새로운 프로토타입 빈을 만들어서 반환하는 것이에요.

 

마치 싱글톤 스코프는 어떤 숙박 업소에 손님 얼굴을 잘 기억하는 주인분께서 손님이 오시면 "어머! 또 오셨네요! 401호로 준비 해 두었습니다!"라고 하는 것과 같은데, 프로토타입의 경우 주인분께서 안면인식 장애가 있으셔가지구 1년 내내 오는대도 "어서오세요! 처음 뵙겠습니다!" 하면서 다른 방을 주는 것이 연상되는 것이에요.

 

정리하자면

 

여기서 핵심은 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존 관계 주입, 초기화까지만 처리하다는 것이에요.

클라이언트에 빈을 반환하고, 그 뒤에 스프링 컨테이너는 생성된 프로토타입 빈을 관리하지 않는 것이에요.프로토타입 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에게 있기 때문에 [ @PreDestroy ] 같은 종료 Method가 호출되지 않는 것이에요.

 

         👉 싱글톤 스코프 빈 테스트

 

싱글톤 스코프의 빈을 조회하는 singletonBeanFind()를 실행하면 결과는 아래와 같은 것이에요.

 

 

  1. Bean 초기화 Method 호출(실행)
  2. 같은 인스턴스의 Bean 조회
  3. 종료 Method 정상 호출

 

 

프로토타입 스코프의 빈을 조회하는 prototypeBeanFind()를 불러볼게요!

 

 

  1. 싱글톤 빈은 스프링 컨테이너 생성 시점에 초기화 Method가 실행 되지만, 프로토타입 스코프의 빈은 스프링 컨테이너에서 빈을 조회할 때 생성되고, 초기화 Method 실행
  2. 프로토타입 빈을 2번 조회했기 때문에 완전히 다른 스프링 빈이 만들어지고, 초기화도 2번 실행
  3. 싱글톤 빈은 스프링 컨테이너가 관리하기 때문에 스프링 컨테이너가 종료될 때, 빈의 종료 Method가 실행되나, 프로토타입 빈은 스프링 컨테이너가 생성과 의존 관계 주입 그리고, 초기화 까지만 신경쓰고, 신경을 꺼 버리기 때문에 즉, 더 관리하지 않기 때문에 프로토타입 빈은 스프링 컨테이너가 종료될 때 [ @PreDestory ] 같은 종료 Method가 전혀 실행되지 않는다.

 

반응형

 

    📍 Prototype Scope - Singleton Bean과 함께 사용시 문제점

스프링 컨테이너에 프로토타입 스코프의 빈을 요청하면 항상 새로운 객체 인스턴스를 생성해서 반환하는 것이에요.

하지만, 싱글톤 빈과 함께 사용할 땐 의도한대로 동작하지 않는 것이에요. 

 

         👉 Spring Container Prototype Bean Direct Request 1

 

최초 클라이언트 A는 스프링 컨테이너에 프로토타입 빈을 요청하고, 스프링 컨테이너는 프로토타입 빈을 새로 생성한 뒤 반환(x01)하는 것이에요. 해당 빈의 count Field 값은 0인 상태인 것이에요.

클라이언트는 조회한 프로토타입 빈에 addCount()를 부르면서 count Field를 +1하는 것이에요. 

그렇게 되면 프로토타입 빈(x01)의 count는 1이 되겠지요?

 

 

 

 

         👉 Singleton Bean at use Prototype Bean 1

 

clientBean은 싱글톤이기 때문에 보통 스프링 컨테이너 생성 시점에 함께 생성되고, 의존 관계 주입도 이 때 발생하는 것이에요.

clientBean은 의존 관계 자동 주입을 사용하고, 주입 시점에 스프링 컨테이너에 프로토타입 빈을 요청하는 것이에요.

스프링 컨테이너는 프로토타입 빈을 생성한 뒤 clientBean에 반환을 하고, 프로토타입 빈의 count Field 값은 0인 것이에요.

그런 뒤 clientBean은 프로토타입 빈을 내부 Field에 참조값을 보관한답니다.

 

 

         👉 Singleton Bean at use Prototype Bean 2

 

클라이언트 A는 clientBean을 스프링 컨테이너에 요청해서 받는 것이에요. 싱글톤이기 때문에 당연히 항상 같은 clientBean이 반환될 것이에요.

클라이언트 A가 clientBean.logic()을 부르게 되면 clientBean은 prototypeBean의 addCount()를 불러 프로토타입 빈의 count를 증가시키게 되고, 이 때문에 count 값이 1이 되는 것이에요.

 

 

         👉 Singleton Bean at use Prototype Bean 3

 

클라이언트 B가 clientBean을 스프링 컨테이너에 요청해서 받을 때, clientBean은 싱글톤이기 때문에 항상 같은 clientBean이 반환되는 것이에요.

여기서 중요한 것은 clientBean이 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈인 것이에요.
주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성된 것이지 사용할 때마다 새로 생성되는 것이 아닌 것이에요!

클라이언트 B는 clientBean.logic()을 부르게 되고, clientBean은 prototypeBean의 addCount()를 불러 프로토 타입 빈의 count를 증가시키게 되는 것이에요. 원래 count 값이 1이였기 때문에 2가 되는 것이에요.

 

 

 

스프링은 일반적으로 싱글톤 빈을 사용하기 때문에 싱글톤 빈이 프로토타입 빈을 사용하게 되는 것이에요.
그런데, 싱글톤 빈은 생성 시점에만 의존 관계 주입을 받기 때문에 프로토타입 빈이 새로 생성되기는 하나, 싱글톤 빈과 함께 계속 유지되는 것이 문제인 것이에요.

아마 프로토타입을 사용한다는 것은 이것을 희망하고 사용하는 것은 아닐것이에요.
프로토 타입 빈을 주입 시점에만 새로 생성하는게 아니라, 사용할 때 마다 새로 생성해서 사용하는 것을 원하기 때문에 이것을 사용하려고 하는 것일테닌까요!

 

💡 참고 :

여러 빈에서 같은 프로토타입 빈을 주입 받으면 주입 받는 시점에 각각 새로운 프로토타입 빈 생성.
예 들어 clientA, clientB가 각각 의존관계 주입을 받게 되면 각각 다른 인스턴스의 프로토타입 빈 주입을 받는다.

clientA 👉 prototypeBean@x01
clientB 👉 prototypeBean@x02

단, 사용할 때 마다 새로 생성되는 것은 아님

 

 

    📍 Prototype Scope - Singleton Bean과 함께 사용시 Provider로 문제 해결

싱글톤 빈과 프로토타입 빈을 함께 사용할 때, 어떻게 하면 위의 문제를 해결하여 새로운 프로토타입 빈을 만들 수 있을까요?

 

         👉 Spring Container Request

가장 간단한 방법은 싱글톤 빈이 프로토타입을 사용할 때 마다 스프링 컨테이너에 새로 요청하는 것이에요.

 

 

ac.getBean()을 통해 새로운 프로토타입 빈이 생성되는 것을 볼 수 있는 것이에요.

의존 관계를 외부에서 주입(DI) 받는 것이 아니고, 이렇게 직접 필요한 의존 관계를 찾는 것을 Dependency Lookup(DL)이라고 하며, 이는 의존 관계 조회(탐색)이라 하는 것이에요.

스프링 애필리케이션 컨텍스트 전체를 주입 받게 되면 스프링 컨테이너에 종속적인 코드가 되고, 단위 테스트 역시 어려운 것이에요.

이 시점에 필요한 기능은 지정한 프로토타입 빈을 컨테이너에서 대신 찾아주는 DL 정도의 기능만 있으면 되는 것이에요.

이것을 스프링은 적절하게 지원해 준답니다!

 

 

    📍 ObjectFactory, ObjectProvider

지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공하는 것이 바로 ObjectProvider인 것이에요.
참고로 과거에는 ObjectFactory가 있었는데, 여기에 편의 기능을 추가해서 ObjectProvider가 만들어진 것이에요.

 

 

위의 코드를 실행해보면 prototypeBeanProvider.getObject()를 통해 항상 새로운 프로토타입 빈이 생성되는 것이에요.

ObjectProvicer의 getObjet()를 부르게 되면 내부에서 스프링 컨테이너를 통해 해당 빈을 찾아 반환하는 것이에요. (DL)

스프링이 제공하는 기능을 사용하지만, 기능이 단순하기 때문에 단위 테스트를 만들거나,  mock 코드를 만들기는 훨씬 쉽답니다!

ObjectProvider는 지금 딱 필요한 DL 정도의 기능만 제공하는 것이에요. 

 

 

특징

  1. ObjectFactory : 기능이 단순, 별도 Library없음, Spring에 의존
  2. ObjectProvider : ObjectFactory 상속, Option, Stream 처리 등 편의 기능이 많고, 별도 Library 필요 없으며, Spring에 의존

 

 

         👉 JSR-330 Provider

마지막으로 사용할 수 있는 방법은 javax.inject.Provider라는 JSR-330 Java Stadard를 사용하는 것이에요.

이것을 사용하기 위해서는 java.inject:javax.inject:1 Library를 gradle에 추가 해줘야 한답니다!

 

package javax.inject;

public interface Provider<T> {
 T get();
}

/* -------------------------------------------- */

//implementation 'javax.inject:javax.inject:1' gradle 추가 필수
	@Autowired	private Provider<PrototypeBean> provider;
		public int logic() {
 		PrototypeBean prototypeBean = provider.get();
 		prototypeBean.addCount();
 		int count = prototypeBean.getCount();
 		return count;
}

 

위의 코드를 실행해보면 provider.get()을 통해 항상 새로운 프로토타입 빈을 생성하는 것이에요.

provider의 get()을 부르게 되면 내부에서 스프링 컨테이너를 통해 해당 빈을 찾아 반환한답니다! (DL)

JAVA Standard이며, 기능이 단순하므로, 단위 테스트를 만들거나, mock 코드를 만들기는 훨씬 쉬워지는 것이에요.

Provider는 딱 필요한 DL 정보의 기능만 제공하는 것이에요.

 

 

특징

  1. get() 하나로 기능이 매우 단순
  2. 별도 Library 필요
  3. JAVA Standard로 Spring이 아닌 다른 컨테이너에서도 사용 가능

 

 

정리하자면

 

프로토타입 빈을 그러면 언제 사용할까요? 매번 사용할 때 마다 의존 관계 주입이 완료된 새로운 객체가 필요하면 사용하면 되는 것이에요. 그런데, 실무에서는 웹 애플리케이션을 개발해보면, 싱글톤 빈으로 대부분의 문제를 해결할 수 있기 때문에 이것을 쓰는 일은 드물다고 합니다!

ObjectProvider, JSR330 Provider 등은 프로토타입 뿐 아니라 DL이 필요한 경우 언제든 사용 가능한 것이에요.

 

💡 참고 :

스프링이 제공하는 Method에 @Lookup을 사용하는 방법도 있으나, 이전 방법들로 충분하다.

자바 표준인 JSR-330 Provider를 사용할 것인지 아니면 스프링이 제공하는 ObjectProvider를 사용할 것인지 고민이 되는 순간이 온다면 ObjcetProvider는 DL을 위한 편의 기능을 많이 제공하며, 스프링 외에 별도 의존 관계추가가 필요 없기 때문에 편하다. 거의 그럴일은 없겠다만 코드를 스프링이 아닌 다른 컨테이너에서도 사용할 수 있어야 한다면 JSR-330 Provider를 사용해야 한다.

스프링을 사용하다 보면 이 기능 뿐 아니라, 다른 기능들도 JAVA Standard와 스프링이 제공하는 기능이 겹칠때가 많은데, 대부분 스프링이 더 다양하고, 편리한 기능을 제공하기 때문에 특별히 다른 컨테이너를 함께 사용하지 않는 이상 스프링이 제공하는 기능을 사용한다.

 

 

 

    📍 Web Scope

지금까지 싱글톤과 프로토타입에 대해 알아본 것이에요. 싱글톤은 스프링 컨테이너의 시작과 끝까지 함께하는 범위가 매우 큰 스코프이고, 프로토타입은 생성과 의존관계 주입, 그리고 초기화까지만 진행하는 특별한 친구인 것이에요.

 

웹 스코프의 특징

  1. 웹 스코프는 웹 환경에서만 동작
  2. 웹 스코프는 프로토타입과 다르게 스프링이 해당 스코프의 종료 시점까지 관리.
    따라서 종료 Method가 호출

 

 

웹 스코프 종류

  1. request : HTTP 요청 하나가 들어오고, 응답으로 나갈 때 까지 유지되는 스코프로 각각의 HTTP 요청마다 별도의 Bean 인스턴스가 생성, 관리
  2. session : HTTP Session과 동일한 생명주기를 갖음
  3. application : Servlet Context와 동일한 생명주기를 갖음
  4. websocket : 웹 소켓과 동일한 생명주기를 갖음

 

 

         👉 HTTP Requset 당 각각 할당되는 Request Scope

 

 

         👉 Make Request Scope Example

 

WEB Environment Insert

웹 스코프는 웹 환경에서만 동작하기 때문에 WEB 환경이 동작하도록 Library 추가 필요

 

build.gradle

 

main Method 실행 시 아래와 같이 결과가 나오면 성공한 것이에요.

 

 

Spring-boot-starter-web Library를 추가하면 스프링 부트는 내장 톰켓 서버를 활용하여 웹 서버와 스프링을 함께 실행하는 것이에요.

스프링 부트는 웹 라이브러리가 없으면 지금까지 공부했던 AnnotationConfigApplicationContext를 기반으로 Application을 구동하지만, 웹 라이브러리가 추가되면 웹에 관련된 추가 설정과 환경이 필요하기 때문에 AnnotationConfigServletWebServerApplicationContext를 기반으로 Application을 구동하는 것이에요.

 

만약 기본 Port인 8080이 다른 곳에서 사용하여 기동이 되지 않는다면 9090 등과 같이 변경하면 되는 것이에요.

 

main/resources/application.properties

 

 

    📍 Request Scope Example Develop

동시에 여러 HTTP Request가 오면 정확히 어떤 Request가 남긴 Log인지 구분하기 어려운 것이에요.

이럴 때 사용하기 좋은것이 바로 Request Scope인 것이에요.

 

우리가 만들어볼 Code의 결과는 다음과 같아요!

 

[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

 

  • Common Format : [UUID] {requestURL} {message}
  • UUID를 사용하여 HTTP 요청 구분
  • requestURL 정보도 추가하여 어떤 URL Request에서 남은 Log인지 확인

 

JunyHarangLogger.java

 

위의 Class는 Log를 출력하기 위한 JunyHarangLogger인 것이에요.

@Scope(value = "request")를 통해 request 스코프를 지정한 것이에요. 이제 Bean은 HTTP Request 당 하나씩 생성되고, HTTP Request가 끝나는 시점에 소멸되는 것이에요.

해당 Bean이 생성되는 시점에 자동으로 @PostConstruct 초기화 Method를 사용하여 uuid를 생성하고 저장하는 것이에요. 해당 Bean은 HTTP Request 당 하나씩 생성되기 때문에 uuid를 저장해두면 다른 HTTP Request와 구분이 가능하답니다!

해당 Bean이 소멸되는 시점에 @PreDestroy를 사용해서 종료 메시지를 남길 것이에요.

requestURL은 해당 Bean이 생성되는 시점에는 알 수 없기 때문에 외부에서 setter로 입력 받게 한 것이에요.

 

LogDemoController.java

 

위의 Controller는 Logger가 잘 동작하는지 확인하기 위한 Test용 Controller인 것이에요.

여기서 HttpServletRequest를 통해 요청 URL을 받았구요. (requestURL Value = http://localhost:8080/log-demo)

이렇게 받은 requestURL 값을 JunyHarangLogger에 저장해두는 것이에요. JunyHarangLogger는 HTTP 요청 당 각각 구분되기 때문에 다른 HTTP 요청 때문에 값이 섞이지 않는 것이에요.

컨트롤러에서 이것은 컨트롤러 입네다!라는 로그를 남길 것이에요.

 

💡 참고 :

requestURL을 JunyHarangLogger에 저장하는 부분은 컨트롤러 보다 공통 처리가 가능한 스프링 인터셉터나 서블릿 필터 같은 곳을 활용하는 것이 좋다.

 

LogDemoService.java

 

비즈니스 로직이 있는 서비스 계층에서도 로그를 출력하게 할 것이에요.

여기서 중요한 것은 request scope를 사용하지 않고, 매개 변수(파라미터)로 이 모든 정보를 서비스 계층에 넘긴다면 매개 변수가 많아 드러워 지는 것이에요. 더 문제는 requestURL 같은 웹과 관련된 정보가 웹가 관련없는 서비스 계층까지 넘어가게 되는 것이에요. 웹과 관련된 부분은 컨트롤러까지만 사용하는 것이 좋고, 서비스 계층은 웹 기술에 종속되지 않고, 가급적 순수하게 유지하는 것이 유지보수 관점에서 좋은 것이에요.

request scope의 JunyHarangLogger 덕분에 이런 부분은 매개 변수로 넘기기 않고, JunyHarangLogger의 멤버변수에 저장하여 코드와 계층을 깔끔하게 유지할 수 있는 것이에요.

 

기대하는 출력

 

 

기대와는 다른 Exception

Error creating bean with name 'junyHarangLogger': 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;

 

Main Method가 있는 스프링 애플리케이션을 실행시키면 위와 같이 Exception이 터지는데, 메시지 마지막에 싱글톤이라는 단어가 나오는 것이에요. 스프링 애플리케이션을 실행하는 시점에 싱글톤 빈을 생성해서 주입이 가능하나, request 스코프 빈은 아직 생성되지 않는 것이에요. 해당 빈은 실제 클라이언트의 요청이 와야 생성할 수 있기 때문인 것이에요.

 

 

    📍 Scope And Provider

위의 문제를 해결하기 위해서 첫번째로 사용해 볼 수 있는 해결 방안은 바로 Provider인 것이에요.

간단히 ObjectProvider를 사용해 볼 것이에요.

 

package hello.core.web;
import hello.core.common.MyLogger;
import hello.core.logdemo.LogDemoService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;

	@Controller @RequiredArgsConstructor
	public class LogDemoController {
 		private final LogDemoService logDemoService;
 		private final ObjectProvider<JunyHarangLogger> jhLoggerProvider;
 
 	@RequestMapping("log-demo") @ResponseBody
 	public String logDemo(HttpServletRequest request) {
 		String requestURL = request.getRequestURL().toString();
 		JunyHarangLogger jhLogger = jhLoggerProvider.getObject();
 		jhLoggerProvider.setRequestURL(requestURL); 
        jhLoggerProvider.log("이것은 컨트롤러 입네다!");
 		logDemoService.logic("testId");
 		return "OK";
 }
}

 

 

package hello.core.logdemo;
import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;

	@Service @RequiredArgsConstructor
	public class LogDemoService {
 
 		private final ObjectProvider<JunyHarangLogger> jhLoggerProvider;
 
 		public void logic(String id) {
 		JunyHarangLogger jhLogger = jhLoggerProvider.getObject();
 		jhLogger.log("서비스에서 넘어온 동무 id는 다음과 같습네다! \n" + id);
 }
}

 

Main()을 실행해서 WEB Browser에 [ http://localhost:8080/log-demo ] 라고 입력하면 아래와 같이 결과가 나올 것이에요.

 

 

ObjectProvider 덕에 ObjectProvider.getObject()를 불러내는 시점까지 request Scope Bean의 생성을 지연할 수 있는 것이에요.

 

ObjcetProvider.getObject()를 불러내는 시점에는 HTTP 요청이 진행중이기 때문에 request Scope Bean의 생성이 정상 처리 된답니다!

 

ObjcetProvider.getObject()를 LogDemoController, Log DemoService에서 각각 한번씩 따로 호출해도 같은 HTTP 요청이면 같은 Spring Bean이 반환되는 것이에요. 이걸 직접 구분하려면 정말 힘들 것이에요.

 

 

 

    📍 Scope And Proxy

이번에는 Proxy 방식을 사용해 볼것이에요.

💡 참고 : Proxy란?

사전적 의미 : 1) 대리(권)
                  2) 대리인
                  3) (측정, 계산하려는 다른 것을 대표하도록 이용하는) 대용물

Proxy Server란?
Client가 자신을 통해서 다른 Network Svice에 간접적으로 접속할 수 있게 해주는 컴퓨터 시스템이나, 응용 프로그램. Server와 Client 사이에 중계기로써 대리로 통신을 수행하는 것을 가리켜 'Proxy', 그 중계 기능을 하는 것을 Proxy Server라고 칭한다.

Spring Proxy란?
원 래 객체를 Proxy로 감싸고, Client 측의 요청을 감싸진 Proxy Class를 통해 대리로 처리하도록 유도

 

 

 

 

proxyMode = ScopedProxyMode.TARGET_CLASS 이 부분이 아주아주 중요한 것이에요.

이것은 적용 대상이 인터페이스가 아닌 클래스면 TARGET_CLASS를 사용하고, 인터페이스라면 INTERFACES를 쓰면 되는 것이에요.

이렇게 하면 JunyHarangLogger의 가짜 프록시 클래스를 만들고, HTTP request와 상관 없이 가짜 프록시 클래스를 다른 Bean에 미리 주입할 수 있는 것이에요.

 

LogDemoController.java

 

 

LogDemoService.java

 

Code를 잘 보게 되면 LogDemoController와 LogDemoService는 Provider 사용 전과 완전 동일한 것이에요.

왜 그럴까요??

 

 

 

    📍 WEB Scope And Proxy Principle of motion

먼저 주입된 JunyHarangLogger를 확인 해 볼게요!

실행 결과

 

위에 결과를 보는 것과 같이 Spring이 만든 Proxy 객체가 만들어 진 것이에요!

@Scop의 proxyMode = ScopedProxyMode.TARGET_CLASS를 설정하게 되면 스프링 컨테이너는 CGLIB라는 바이트 코드를 조작하는 라이브러리를 사용해서, JunyHarangLogger를 상속 받는 가짜 프록시 객체를 생성하는 것이에요.

결과를 확인 해 보면 등록한 순수한 JunyHarangLogger 클래스가 아니라 [ JunyHarangLogger$$EnhancerBySpringCGLIB ]라는 클래스로 만들어진 객체가 대신 등록된 것을 볼 수 있는 것이에요. 그리고, 스프링 컨테이너에 [ jhLogger ] 라는 이름으로 진짜 대신 가짜 프록시 객체를 등록하는 것이에요.

ac.getBean("jhLogger", JunyHarangLogger.class)로 조회해도 프록시 객체가 조회 된답니다! 그렇기 때문에 의존 관계 주입도 이 가짜 프록시 객체가 주입되는 것이에요.

 

 

주니하랑은 MyLoggerProxy가 아닌 JunyHarangProxy

 

가짜 프록시 객체는 요청이 오면 그 때 내부에서 진빠 빈을 요청하는 위임 로직이 들어 있는 것이에요.

가짜 프록시 객체는 내부에 진짜 jhLogger를 찾는 방법을 알고 있는 것이에요.

클라이언트가 jhLogger.logic()을 부르게 되면 사실 가짜 프록시 객체의 메서드를 호출한 것이에요.

가짜 프록시 객체는 request 스코프의 진짜 jhLogger.logic()을 부르게 되는 것이지요.

가짜 프록시 객체는 원본 클래스를 상속 받아 만들어졌기 때문에 이 객체를 사용하는 클라이언트 입장에서는 사실 원본인지 아닌지도 모르게 동일하게 사용할 수 있답니다! 때문에 다형성을 만족하는 것이지요.

 

 

동작 정리

CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입하는 것이에요.

이 가짜 프록시 객체는 실제 요청이 오면 그 때 내부에서 실제 Bean을 요청하는 위임 로직이 들어 있답니다!

가짜 프록시 객체는 실제 request Scope와는 관계가 없는 것이에요. 그냥 가짜이고, 내부에 단순한 위임 로직만 있으며, 이것은 싱글톤처럼 동작하는 것이에요.

 

 

특징 정리

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

사실 Provider를 사용하든 Proxy를 사용하든 핵심은 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 것이랍니다. 단지 어노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체할 수 있는 것이에요. 이것이 바로 다형성과 DI 컨테이너가 가진 큰 장점이랍니다.

꼭 웹 스코프가 아니어도 프록시는 사용할 수 있는 것이에요.

 

 

주의점

 

마치 싱글톤을 사용하는 것 같지만, 다르게 동작하기 때문에 주의해서 사용해야 하는 것이에요. 이런 특별한 스코프는 꼭 필요한 곳에만 최소화해서 사용해야 한답니다. 무분별하게 사용하면 나중에 유지보수할 때 토 나오는 것이에요.


 

 

주니하랑의 글이 마음에 드셨나요? 구독과 공감! 그리고, 댓글 그리고 방명록은 주니하랑에게 많은 힘이 됩니다.

728x90
반응형