육감적 코딩

빈의 스코프 본문

정리/Spring

빈의 스코프

감감감감감감 2020. 8. 10. 17:19
모든 빈들은 스코프가 있습니다.

 

지금까지 정리한 내용을 살펴면, 여태까지는 모든 빈을 싱글톤 스코프의 빈만을 생성하였습니다.

(아무런 설정을 하지않고 빈을 생성하면 기본적으로 싱글톤 스코프의 빈이 생성됩니다) 

 

싱글톤 스코프란?

간단히 설명하면 애플리케이션 전반에 걸쳐서 해당 빈의 인스턴스가 오직 한 개만 존재함을 의미합니다.

 

 

그렇다면 프로토타입 스코프는 무엇을 의미할까요?

이것도 간단히 설명하면 빈을 참조할 때 마다 새로운 빈의 인스턴스를 반환하여 사용하게 됩니다.

 

싱글톤스코프와 프로토타입스코프를 확인

@Component

public class AppRunner implements ApplicationRunner {
   @Autowired
   ApplicationContext ctx;

   @Override
   public void run(ApplicationArguments args) throws Exception {
       System.out.println("proto");
       System.out.println(ctx.getBean(Proto.class));
       System.out.println(ctx.getBean(Proto.class));
       System.out.println(ctx.getBean(Proto.class));

       System.out.println("single");
       System.out.println(ctx.getBean(Single.class));
       System.out.println(ctx.getBean(Single.class));
       System.out.println(ctx.getBean(Single.class));
   }
}
@Component @Scope("prototype")
public class Proto {
}
@Component
public class Single {
   @Autowired
   private Proto proto;

   public Proto getProto() {
       return proto;
   }
}

프로토타입 스코프의 빈은 매번 새로운 인스턴스를 만들고, 싱글톤 스코프의 빈은 계속 같은 인스턴스임을 확인할 수 있습니다.

 

간단히 싱글톤과 프로토타입의 차이에 대해 알아보았는데, 여기서 생각해 봐야할 부분이있습니다.

따로 사용되었을 때는 문제가 없을거같지만 한 빈이 서로 스코프가 다른 빈들을 가지고 있다면 어떻게 될까요?

 

프로토타입의 빈이 싱글톤 빈을 갖고 있는 것은 원하는데로 잘 작동하여 문제가 되지않을것 같지만(프토타입이 가지고있는 싱글톤 스코프의 빈은 모든 프로토타입의 인스턴스에서 동일한 인스턴스를 가지고있는), 반대의 경우는 꽤 복잡할 수 있습니다.

 

싱글톤 스코프의 빈이 프로토타입의 스코프빈을 사용할 때,

싱글톤 스코프의 빈은 한 번만 만들어지고 이때, 이미 프로토타입 스코프의 프로퍼티도 이미 세팅이 되기때문에 싱글톤 타입의 프로퍼티를 사용할때 프로토타입의 프로퍼티가 변경이 되지않습니다.

 

문제의 상황을 확인해 보기위해 코드를 약간 수정해줍니다.

@Component
public class AppRunner implements ApplicationRunner {
   @Autowired
   ApplicationContext ctx;

   @Override
   public void run(ApplicationArguments args) throws Exception {
       System.out.println("proto");
       System.out.println(ctx.getBean(Proto.class));
       System.out.println(ctx.getBean(Proto.class));
       System.out.println(ctx.getBean(Proto.class));

       System.out.println("single");
       System.out.println(ctx.getBean(Single.class));
       System.out.println(ctx.getBean(Single.class));
       System.out.println(ctx.getBean(Single.class));

       System.out.println("proto by single");
       System.out.println(ctx.getBean(Single.class).getProto());
       System.out.println(ctx.getBean(Single.class).getProto());
       System.out.println(ctx.getBean(Single.class).getProto());
   }
}

의도한 스코프와는 다르게 싱글톤 스코프의빈의 프로토타입 빈은 변경되지 않는 것을 확인할 수 있습니다.



이를 해결하기 위한 방법이 여러개 존재합니다.

그 중 가장 쉬운방법은 스코프의 프록시 모드를 바꾸어주는 것 입니다.

@Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.DEFAULT)
public class Proto {
}

기본 값으로 셋팅되어있는 값은 DEFAULT입니다. DEFAULT는 프록시를 사용하지 않는것을 의미합니다. 

 

해당경우는 클래스이므로 TARGET_CLASS 로 설정해주어야 합니다.

TARGET_CLASS를 사용한 경우에는 CGLIB 를 사용한 다이나믹 프록시가 적용이 됩니다.

@Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Proto {
}

기대했던 대로 싱글톤 스코프의 프로토타입 빈이 계속 변경되는것을 확인 할 수 있습니다.

 

약간의 설명을 추가하면, 해당 클래스를 클래스 기반의 프록시로 감싸는 것입니다.

즉, ‘다른 빈들이  사용할 때 감싼 이 프록시 빈을 사용하라’ 라고 설정 한것입니다.

 

직접쓰는 것이 아닌 프록시를 거쳐서 사용하게 됩니다.

왜 프록시로 빈을 감싸야 하는 가?

다른 인스턴스들이 프로타입 스코프의 빈을 직접참조하게하면 안되기 때문입니다.

그렇다면, 왜 프록시를 거쳐야 하는가 ?

직접 참조하게되면 위의 결과처럼 이미 Proto 프로퍼티의 설정이 끝났기 때문에 새로운 인스턴스의 Proto 빈을 바꿔 줄 여지가 없기때문입니다.

즉, 매번 새로운 인스턴스로 바꿔줄 수 있는 프록시로(해당 클래스를 상속한 클래스를 만들어주는 CGLIB 기반의 라이브러리) 감싸주는 것입니다.

 

정리하면 Proto를 감싼 Proxy 인스턴스가 빈으로 등록되게 됩니다.

 

그리고 그 Proxy 빈이 Single에 주입되게 됩니다. 

(Proto를 상속받았기 때문에, 주입받은 Proxy빈도 주입가능합니다.)

 




보통 싱글톤 스코프의 빈을 사용하기 때문에 자주 사용할 만한 내용은 아닙니다.

하지만 싱글톤 스코프에서 프로토 스코프의 인스턴스를 참조할 때,

스코프 주기가 긴 인스턴스에서 짧은 스코프 주기를 가진 빈들을 주입받을 때는 반드시 생각해 봐야합니다.



싱글톤 객체 사용시 주의할 점

  • 프로퍼티가 공유가 된다는점.

    • 어떤 변수의 값을 수정하게 된다면, 그 변수가 Thread safe할 거라고 보장받을 수 없다.

    • ex) a Thread 에서 변경한 값과 b Thread에서 변경한 값이 같은 것을 바라보고 있기 때문입니다

    • v라는 변수를 a Thread에서 1로 변경 →  b Thread에서 2로 변경 → a Thread에서 v를 출력 → 2가 출력.

    • Thread safe한 방법으로 코딩.

  • 모든 싱글톤 스코프의 빈은 ApplicationContext를 만들 때 만들어집니다.

    • 애플리케이션 구동 시 시간이 더 걸릴 수 있다.

Comments