[Spring] 기본편 09. 빈 스코프

74314 단어 SpringSpring

이 글은 스프링 [핵심원리 - 기본편]을 듣고 정리한 내용입니다

📌 빈 스코프란?

스프링이 지원하는 다양한 스코프

  • 싱글톤: 기본 스코프, 스플이 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프.
  • 프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
  • 웹 관련 스코프
    • request: 웹 요청이 들어오고 나갈 때 까지 유지되는 스코프
    • session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프
    • application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

  • 컴포넌트 스캔 자동 등록
@Scope("prototype")
  @Component
  public class HelloBean {}
  • 수동 등록
@Scope("prototype")
  @Bean
  PrototypeBean HelloBean() {
      return new HelloBean();
  }

📌 프로토타입 스코프

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

싱글톤 빈 요청


1. 싱글톤 스코프의 빈을 스프링 컨테이너에 요청한다.
2. 스프링 컨테이너는 본인이 관리하는 스프링 빈을 반환한다.
3. 이후에 스프링 컨테이너에 같은 요청이 오면, 같은 객체 인스턴스의 스프링 빈을 반환한다.

프로토타입 빈 요청


1. 프로토타입 스코프의 빈을 스프링 컨테이너에 요청한다.
2. 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입한다.

3. 스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환한다.
4. 이후에 스프링 컨테이너에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환한다.

정리

  • 스프링컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리한다.
  • 이후 프로토타입 빈을 관리할 책임은 빈을 받은 클라이언트에게 있다.

  • 싱글톤 스코프 빈 테스트
package hello.core.scope;

public class SingletonTest {

    @Test
    void singletonBeanfind(){
        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);

        //결과: 같음
        Assertions.assertThat(singletonBean1).isSameAs(singletonBean2);

        ac.close();

    }

    @Scope("singleton")
    static class SingletonBean{
        @PostConstruct
        public void init(){
            System.out.println("SingletonBean.init");
        }

        @PreDestroy
        public void destroy(){
            System.out.println("SingletonBean.destroy");
        }
    }
}
  • 싱글톤 스코프 결과 출력
  • 빈 초기화 메서드 실행
  • 같은 인스턴스의 빈을 조회
  • 종료 메서드 정상 호출 확인.

  • 프로토타입 스코프 빈 테스트
package hello.core.scope;

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 prototypeBean1");
        PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);

        System.out.println("prototypeBean1 = " + prototypeBean1);
        System.out.println("prototypeBean2 = " + prototypeBean2);

        //bean1과 bean2는 다름
         assertThat(prototypeBean1).isNotSameAs(prototypeBean2);

         ac.close();
    }

    @Scope("prototype")
    static class PrototypeBean{
        @PostConstruct
        public void init(){
            System.out.println("PrototypeBean.init");
        }

        @PreDestroy
        public void destroy(){
            System.out.println("PrototypeBean.destroy");
        }
    }
}
  • 프로토타입 스코프 결과 출력
  • 싱글톤 빈은 스프링 컨테이너 생성 시점에 초기화 메서드가 실행되지만, 프로토타입 스코프의 빈은 스프링 컨테이너에서 빈을 조회할 때 생성되고, 초기화 메서드도 실행된다.
  • 2번 조회했으므로 완전히 다른 스프링 빈이 생성되고, 초기화도 2번 실행되었다. -> 서로다른 참조값이 나온다.

프로토타입 빈의 특징 정리

  • 스프링 컨테이너에 요청할때마다 새로 생성됨.
  • 스프링 컨테이너는 프로토타입의 빈의 생성과 의존관계 주입, 초기화 까지만 관여함
  • 종료 메서드가 호출되지 않음.
  • 프로토타입 빈은 프로토타입 빈을 조회한 클라이언트가 관리해야 한다. 종료 메서드에 대한 호출도 클라이언트가 직접 해야함.

📌 프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점

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

  • 하지만 싱글톤 빈과 함께 사용할 때는 의도한 대로 잘 동작하지 않으므로 주의해야한다.
    -> 여기서 잘 동작하지 않는다는건, 프로토타입 빈을 사용할때마다 새로 생성해서 사용하는것을 기대하지만 싱글톤 빈과 함께 사용하면 프로토타입 빈을 주입 시에만 새로 생성한다는 뜻이다.

  • 스프링 컨테이너에 프로토타입 빈 직접 요청

public class SingletonWithPrototypeTest1 {
      @Test
      void prototypeFind() {
          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 destroy() {
              System.out.println("PrototypeBean.destroy");
          }
} }
  • 각 프로토타입 빈의 count는 둘다 1로 검증이 성공한다.

  • 싱글톤에서 프로토타입 빈 사용
package hello.core.scope;

public class SingletonWithPrototypeTest1 {

    @Test
    void prototypeFind(){
        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);
    }

    @Test
    void singletonClientUsePrototype(){
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);

        ClientBean clientBean1 = ac.getBean(ClientBean.class);
        int count1 = clientBean1.logic();
        assertThat(count1).isEqualTo(1);

        ClientBean clientBean2 = ac.getBean(ClientBean.class);
        int count2 = clientBean2.logic();
        assertThat(count2).isEqualTo(2);

    }

    @Scope("singleton")
    static class ClientBean{
        private final PrototypeBean prototypeBean; //생성시점에 주입

        @Autowired
        public ClientBean(PrototypeBean prototypeBean){
            this.prototypeBean=prototypeBean;
        }

        public int logic(){
            prototypeBean.addCount();
            int count = prototypeBean.getCount();
            return count;
        }
    }

    @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 destroy(){
            System.out.println("PrototypeBean.destroy");
        }
    }
}
  • 두번째 프로토타입 빈의 count=2임을 볼 수 있다.
  • 스프링은 일반적으로 싱글톤 빈을 사용하므로, 싱글톤 빈이 프로토타입 빈을 사용하게 된다.
  • 그런데 싱글톤 빈은 생성 시점에만 의존관계를 주입받기 때문에, 프로토타입 빈이 새로 생성되기는 하지만, 싱글톤 빈과 함께 계속 유지되는 것이 문제이다.
  • 원하는 것은, 프로토타입 빈을 주입 시점에만 새로 생성하는게 아니라, 사용할 때마다 새로 생성해서 사용하는 것이다.

*참고

  • 여러 빈에서 같은 프로토타입 빈을 주입 받으면, 주입 받는 시점에 각각 새로운 프로토타입 빈이 생성된다.
  • 예를 들어, clientA, clinetB가 각각 의존관계 주입을 받으면 각각 다른 인스턴스의 프로토타입을 주입 받는다.
    • clientA -> prototypeBean@x01
    • clientB -> prototypeBean@x02
  • 물론 사용할때마다 새로 생성되는 것은 아니다.

📌 프로토타입 스코프 - 싱글톤 빈과 함께 사용 시 Provider로 문제 해결

  • 싱글톤 빈과 프로토타입 빈을 함께 사용할 때, 사용할 때마다 항상 새로운 프로토타입 빈을 생성하려면 ?

스프링 컨테이너에 요청

  • 가장 간단한 방법은 싱글톤 빈이 프로토타입을 사용할 때마다 스프링 컨테이너에 새로 요청하는 것이다.
@Autowired
  private ApplicationContext ac;
  public int logic() {
  //그러나 굉장히 무식한 방법임.
      PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
      prototypeBean.addCount();
      int count = prototypeBean.getCount();
      return count;
 }
  • ac.getBean()을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 볼 수 있다.
  • 의존관계를 외부에서 주입(DI) 받는게 아니라 직접 필요한 의존관계를 찾는 것을 Dependency Lookup(DL) 의존관계 조회(탐색)이라 한다.

🌱 ObjectFactory, ObjectProvider

  • ObjectProvider: 지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공한다.
  • 과거에는 ObjectFactory가 있었는데, 여기에 편의 기능을 추가해서 ObjectProvider가 만들어졌다.
@Autowired
  private ObjectProvider<PrototypeBean> prototypeBeanProvider;
  public int logic() {
      PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
      prototypeBean.addCount();
      int count = prototypeBean.getCount();
      return count;
  • prototypeBeanProvider.getObject()을 통해서 항상 새로운 프로토타입 빈이 생성된다.
  • ObjectProvidergetObject()를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다. (DL)

특징

  • ObjectFactory: 기능이 단순, 별도의 라이브러리 필요 없음, 스프링에 의존
  • ObjectProvider: ObjectFactory 상속, 옵션, 스트림 처리 등의 편의기능이 많고, 별도의 라이브러리 필요 없음, 스프링에 의존

🌱 JSR-330 Provider

  • javax.inject.Provider 라는 JSR-330 자바 표준을 사용하는 방법
  • javax.inject:javax.inject:1 라이브러리를 gradle에 추가 해야한다.
 @Autowired
        private Provider<PrototypeBean> prototypeBeanProvider;

        public int logic(){
            PrototypeBean prototypeBean = prototypeBeanProvider.get();
            prototypeBean.addCount();
            int count = prototypeBean.getCount();
            return count;
        }
  • provider.get()을 통해서 항상 새로운 프로토타입 빈이 생성되는것을 확인할수 있다.
  • provider의 get()을 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다(DL)
  • 별도의 라이브러리가 필요하고(gradle에 추가), 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.

정리

  • 프로토타입 빈을 언제 사용할까?
  • 매번 사용할 때 마다 의존관계 주입이 완료된 새로운 객체가 필요하면 사용하면 되는데, 실무에서 사용하는 일은 매우 드물다!
  • ObjectProvider, JSR330 Provider 등은 프로토타입 뿐만 아니라 DL이 필요한 경우는 언제든지 사용할 수 있다.

스프링이 제공하는 기능을 사용하자

  • 스프링을 사용하다 보면 이 기능 뿐만 아니라 다른 기능들도 자바 표준과 스프링이 제공하는 기능이 겹칠때가 많다.
  • 대부분 스프링이 더 다양하고 편리한 기능을 제공해주기 때문에, 특별히 다른 컨테이너를 사용할 일이 없다면, 스프링이 제공하는 기능을 사용하면 된다.

📌 웹 스코프

웹 스코프 특징

  • 웹 환경에서만 동작한다
  • 웹 스코프는 프로토타입과 다르게 스프링이 해당 스코프의 종료시점 까지 관리한다. -> 종료 메서드가 호출된다.

웹 스코프 종류

  • request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프로, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리된다.
  • session: HTTP Session과 동일한 생명주기를 가지는 스코프
  • application: 서블릿 컨텍스트(ServletContext)와 동일한 생명주기를 가지는 스코프
  • websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프

📌 request 스코프 예제 만들기

🌱 환경 세팅

  • 웹 스코프는 웹 환경에서만 동작하므로 web 환경이 동작하도록 라이브러리를 추가한다.
  • build.gradle에 추가
//web 라이브러리 추가
    implementation 'org.springframework.boot:spring-boot-starter-web'
  • CoreApplication을 실행하면 웹 어플리케이션이 잘 실행된다.
omcat started on port(s): 8080 (http) with context path ''
    Started CoreApplication in 0.914 seconds (JVM running for 1.528)

*참고

  • 스프링 부트는 웹 라이브러리가 없으면 AnnotationConfigApplicationContext을 기반으로 애플리케이션을 구동한다.
  • 웹 라이브러리가 추가되면 웹가 관련된 추가 설정과 환경들이 필요하므로 AnnotationConfigServletWebServerApplicationContext를 기반으로 애플리케이션을 구동한다.

🌱 request 스코프 예제 개발

  • 동시에 여러 HTTP 요청이 오면 정확히 어떤 요청이 남긴 로그인지 구분하기 어렵다
  • 이럴 때 request스코프를 사용한다.
  • 다음과 같이 로그가 남도록 request 스코프를 활용하여 추가기능 개발
[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}
  • MyLogger.class
package hello.core.common;

@Component
@Scope(value="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("");
        System.out.println("["+uuid+"]"+" request scope bean close: "+this);
    }


}
  • 로그를 출력하기 위한 클래스.
  • @Scope(value="reuqest)를 사용하여 request 스코프로 지정.
  • 이제 이 빈은 HTTP 요청 당 하나씩 생성되고, HTTP 요청이 끝나는 시점에 소멸된다.
  • @PostConstruct, @PreDestory 메서드 추가해서 초기화 시 uuid부여하고, 초기화 및 종료 메세지를 남긴다.
  • LogDemoController.class
package hello.core.web;

import javax.servlet.http.HttpServletRequest;

// 로거가 잘 작동하는지 확인하는 테스트용 컨트롤러
@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";
    }
}
  • HttpServletRequest를 통해서 요청 URL을 받는다.
    • requesetURL 값: http://localhost:8080/log-demo
  • myLogger는 HTTP 요청 당 각각 구분되므로 다른 HTTP 요청 때문에 값이 섞이는 걱정은 안해도 된다.
  • LogDemoService.class
package hello.core.web;

import hello.core.common.MyLogger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class LogDemoService {

    private final MyLogger myLogger;

    public void logic(String id) {
        myLogger.log("service id = "+ id);
    }
}
  • *중요한 점
    • request scope를 사용하지않고, 파라미터로 이 모든 정보를 서비스 계층에 넘기게 되면 파라미터가 많아서 지저분해진다.
    • 더 문제는 requestURL 같은 웹과 관련된 정보가 웹가 관련없는 서비스 계층까지 넘어가게 된다.
    • 웹과 관련된 부분은 컨트롤러 까지만 사용해야 한다. 서비스 계층은 웹 기술에 종속되지 않고, 가급적 순수하게 유지하는 것이 유지보수 관점에서 좋다.

실행 시 오류

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;
  • 스프링 애플리케이션을 실행하면 오류가 발생한다.
  • 스프링 애플리케이션을 실행하는 시점에, 싱글톤 빈은 생성해서 주입이 가능하지만, request 스코프 빈은 아직 생성되지 않는다.
  • request 스코프 빈은 실제 고격의 요청이 와야 생성할 수 있다!!

📌 스코프와 Provider

  • 첫번째 해결방안: Provider 사용하기
package hello.core.web;

@Controller
@RequiredArgsConstructor
public class LogDemoController {

    private final LogDemoService logDemoService;
    private final ObjectProvider<MyLogger> myLoggerProvider; //ObjectProvider 사용

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) throws InterruptedException {

        String requestURL = request.getRequestURL().toString();
        MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.setRequestURL(requestURL);

        myLogger.log("controller test");
        Thread.sleep(1000);
        logDemoService.logic("testId");
        return "OK";
    }
}
package hello.core.web;

@Service
@RequiredArgsConstructor
public class LogDemoService {

    private final ObjectProvider<MyLogger> myLoggerProvider;//ObjectProvider 사용

    public void logic(String id) {

        MyLogger myLogger = myLoggerProvider.getObject();
        myLogger.log("service id = "+ id);
    }
}
  • 실행 확인 (localhost:8080/log-demo 접속)
  • 새로고침 여러번 했을 때
  • ObjectProvider 덕분에 ObjectProvider.getObject()를 호출하는 시점까지 request scope빈의 생성을 지연할 수 있다.
  • ObjectProvider.getObject()를 호출하는 시점에서는 HTTP 요청이 진행중이므로 request scope빈의 생성이 정상 처리된다.
  • ObjectProvider.getObject()LogDemoController, LogDemoService에서 각각 한번씩 따로 호출해도 같은 HTTP 요청이면 같은 스프링 빈이 반환된다!

📌 스코프와 프록시

프록시란?

  • 프록시(Proxy)란 '대신'이라는 의미를 가지고 있다.
  • 프로토콜에 있어서 대리 응답 등에서 사용하는 개념이다.
  • 보안상의 문제로 직접 통신을 주고 받을 수 없는 사이에서 프록시를 이용해서 중계를 하는 개념이다.
  • 이렇게 중계 기능을 하는것을 프록시 서버 라고 부른다.

프록시 서버의 특징

  • 프록시 서버는 클라이언트와 서버의 입장에서 볼 때 서로 반대의 역할을 하는것 처럼 보여지게 된다.
    • 클라이언트가 프록시를 바라보면 '서버'처럼 동작하게 되는 거고, 서버가 프록시를 바라보면 '클라이언트'처럼 작동을 하는것과 같다.
  • 프록시는 프록시 서버에 요청이 된 내용들을 '캐시'를 이용해 저장해 둔다. 이렇게 캐시로 저장을 해 두면 다시 데이터를 가져올 상황이 발생하지 않으므로 전송시간을 절약할 수 있다.
  • 프록시 방식 사용
@Component
@Scope(value="request", proxyMode= ScopedProxyMode.TARGET_CLASS)
//proxy 설정, MyLogger가 class이므로 TARGET_CLASS로 설정. 
//적용 대상이 인터페이스면 INTERFACE 선택
public class MyLogger {
}
  • 이렇게하면 MyLogger의 가짜 프록시 클래스를 만들어두고 HTTP request와 상관 없이 가짜 프록시 클래스를 다른 빈에 미리 주입해 둘 수 있다.
  • Controller, Service 이전 코드로 변경
package hello.core.web;

@Controller
@RequiredArgsConstructor
public class LogDemoController {

    private final LogDemoService logDemoService;
    private final MyLogger myLogger;

    @RequestMapping("log-demo")
    @ResponseBody
    public String logDemo(HttpServletRequest request) throws InterruptedException {

        String requestURL = request.getRequestURL().toString();

        System.out.println("myLogger.getClass() = " + myLogger.getClass());
        myLogger.setRequestURL(requestURL);

        myLogger.log("controller test");
        Thread.sleep(1000);
        logDemoService.logic("testId");
        return "OK";
    }
}
package hello.core.web;

@Service
@RequiredArgsConstructor
public class LogDemoService {

    private final MyLogger myLogger;

    public void logic(String id) {

        myLogger.log("service id = "+ id);
    }
}
  • myLogger.getClass 출력

  • 일반 MyLogger클래스가 아니라 CGLIB가 적힌 결과가 출력된다.

동작 정리

  • CGLIB라는 라이브러리로 내 클래스를 상속 받은 가짜 프록시 객체를 만들어서 주입한다.
  • 이 가짜 프록시 객체는 실제 요청이 오면 그때 내부에서 실제 빈을 요청하는 위임 로직이 들어있다.
  • 가짜 프록시 객체는 실제 requeset scope와는 관계가 없다. 그냥 가짜이고, 내부에 단순한 위임 로직만 있고 싱글톤 처럼 동작한다.

특징 정리

  • 프록시 객체 덕분에 클라이언트는 마치 싱글톤 빈을 사용하듯이 편리하게 request scope를 사용할 수 있다.
  • Provider를 사용하던, 프록시를 사용하던 중요한 아이디어는 진짜 객체 조회를 꼭 필요한 시점까지 지연처리 한다는 것이다.
  • 단지 어노테이션 설정 변경만으로 원본 객체를 프록시 객체로 대체 할 수 있다. 이것이 바로 다형성과 DI 컨테이너가 가진 큰 강점이다.
  • 꼭 웹스코프가 아니어도 프록시는 사용 가능하다.

주의점

  • 싱글톤을 사용하는 것 같지만, 동작은 다르므로 주의해서 사용하자
  • 이런 특별한 scope는 꼭 필요할때만 사용해야 유지보수에 무리가 안간다.

참고 사이트
https://milkye.tistory.com/202

좋은 웹페이지 즐겨찾기