ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [05] ApplicationContext - Bean의 scope
    Spring/Spring 핵심 기술 2020. 4. 14. 09:48
    반응형

    [Bean의 Scope]

    Bean에는 여러 가지 scope가 있고 default는 singleton scope입니다.

     

    scope에 대한 여러 가지 테스트를 위해 먼저 두 개의 bean과 application runner를 등록합니다.

    먼저 Proto bean입니다.

    @Component
    public class Proto {
        
    }

     

    다음으로 Proto를 주입받아 getter로 제공하는 Single Bean입니다.

    @Component
    public class Single {
        @Autowired
        Proto proto;
    
        public Proto getProto() {
            return proto;
        }
    }

     

    마지막으로 ApplicationRunner를 구현한 AppRunner입니다. 여기서는 Single bean과 Proto bean을 주입받고 있으며 주입된 Proto bean이 Single에 주입된 Proto bean과 동일한 bean인지 찍어보는 코드가 들어가 있습니다.

    @Component
    public class AppRunner implements ApplicationRunner {
        @Autowired
        Single single;
    
        @Autowired
        Proto proto;
    
        public void run(ApplicationArguments args) throws Exception {
            System.out.println(proto);              //AppRunner에 주입받은 Proto bean
            System.out.println(single.getProto());  //Single에 주입 받은 Proto bean
        }
    }

     

    위의 코드를 실행해보면 다음과 같이 Single에서 주입받은 Proto bean과 AppRunner에서 주입받은 Proto bean이 동일한 bean임을 확인할 수 있습니다.

    실행결과

    이와 같이 application 전체를 통틀어 해당 bean의 객체를 하나만 사용하는 경우를 singleton scope라고 합니다.

     

    Singleton scope 이외의 scope bean을 필요로 할 때마다 새롭게 생성해서 사용합니다. 대표로 prototype의 예를 코드로 살펴보도록 합시다. prototype scope를 지정하기 위해 bean에 해당하는 class에 @Scope를 붙여주고 매개 값으로 "prototype"을 줍니다. Proto.java의 내용을 다음과 같이 prototype scope로 bean이 생성되도록 변경합니다.

    @Component @Scope("prototype")
    public class Proto {
    
    }

     

    AppRunner.java에서 singleton과 prototype bean을 각각 3번씩 가져오도록 합니다. prototype은 매번 다른 bean 객체를 생성할 것이고 반대로 singleton은 항상 동일한 bean 객체를 사용할 것입니다.

    @Component
    public class AppRunner implements ApplicationRunner {
        @Autowired
        ApplicationContext ctx;
    
        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));
        }
    }

     

    위의 코드를 실행해보면 예상했던 것과 동일한 결과가 나오는 것을 확인할 수 있습니다.

     

    이와 같이 bean의 scope를 변경할 수 있는 점은 spring의 강점 중 하나입니다.

     

     

     

     

     

    [Singleton scope와 다른 scope를 혼합하여 사용하는 경우]

     

    case 1 : singleton이 아닌  scope의 bean에서 singleton scope bean을 주입받는 경우

    @Component @Scope("prototype")
    public class Proto {
        @Autowired
        Single single;
    }

    singleton이 아닌 bean은 사용될 때마다 새롭게 생성되고 그 안에 주입받은 singleton bean은 단 하나만 생성되므로 큰 문제없이 사용할 수 있습니다.

     

     

    case 2 : singleton scope의 bean에서 singleton scope이 아닌 bean을 주입받는 경우

    singleton scope의 bean은 application 실행 중 한 번만 만들어지고 이때 주입받은 singleton scope이 아닌 bean의 property도 이미 세팅이 된 상태입니다. 이경우 application 여러 곳에서 singleton scope bean을 사용해도 주입받은 singleton scope가 아닌 bean은 새롭게 생성되지 않습니다. 매번 생성하여 사용하기 위해 singleton scope이 아닌 bean을 사용했는데 새롭게 생성되지 않는 것은 문제입니다. 이를 코드로 확인하기 위해 singleton scope의 bean에서 주입받은 prototype의 bean의 내용을 찍어보도록 하는 구문을 AppRunner.java에 추가합니다.

    @Component
    public class AppRunner implements ApplicationRunner {
        @Autowired
        ApplicationContext ctx;
    
        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());
        }
    }

     

    예상한 대로 주입받은 prototype scope의 bean이 3회 사용되었지만 한 번만 생성된 것을 확인할 수 있습니다.

     

     

    위와 같이 singleton scope bean 안에서 singleton scope이 아닌 bean을 의도한 대로 사용하기 위한 방법은 다음과 같습니다.

     

    1.@Scope 어노테이션 옵션을 변경하는 방법

    @Scope 어노테이션 옵션으로 proxyMode를 ScopedProxyMode의 설정 값 중 현재 코드에 맞는 것으로 설정합니다.

        ScopedProxyMode

            -Default : proxy를 사용하지 않음. (기본설정 값)

            -TARGET_CLASS : class에 대해 proxy 사용을 설정

            -INTERFACES : interface에 대해 proxy  사용을 설정 

     

    테스트 코드에서는 class에 대해 proxy 사용을 설정해야 하므로 Proto class에 @Scope 어노테이션을 주고 proxyMode옵션 값으로 ScopedProxyMode.TARGET_CLASS를 설정합니다.

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

     

    다시 application을 실행해보면 의도했던대로 singleton bean에 주입받은 singleton scope가 아닌 bean이 singleton scope의 bean이 사용될 때마다 새롭게 생성됨을 확인할 수 있습니다.

     

     

    singleton scope bean에 singleton scope가 아닌 bean을 주입할 때 매번 bean을 새롭게 생성해서 주입해줄 수 있도록 해주는 것이 proxy bean입니다. 즉, proxy bean 내부에서 non singleton scope bean들을 관리하고 singleton scope의 bean에서는 이 proxy bean을 주입받는 것입니다. proxy bean은 singleton scope bean에서 본래 주입받고자 한 bean을 상속받아 생성하므로 singleton scope bean에서는 동일 type으로 주입받을 수 있습니다. 주입할 bean이 class로 정의된 경우는 CG Library를 사용하여 proxy bean으로 만들어줍니다. JDK에서 제공하는 dynamic proxy는 interface에 대해서만 proxy로 만들어 줄 수 있습니다. 그렇기 때문에 ScopedProxyMode로 TARGET_CLASS를 지정한 것입니다.

     

     

     

    2.ObjectProvider<BeanType> type을 사용한 방법 (Spring code를 사용자 code에 넣게 되므로 비추천)

    Sigle.java에서 prototype bean을 주입받을 때 ObjectProvider<> generic type으로 받아오고 getIfAvailable() method로 주입할 bean을 얻을 수 있습니다.

    @Component
    public class Single {
        @Autowired
        private ObjectProvider<Proto> proto;
    
        public Proto getProto() {
            return proto.getIfAvailable();
        }
    }

    긴 life cycle을 가진 bean에서 그 보다 짧은 life cycle의 bean을 주입받을 경우 proxy bean을 사용하거나 ObjectProvider<> 타입으로 받아 처리할 수 있습니다.

     

    sigleton scope bean은 application에 단 하나만 생성된다는 특징이 있으므로 bean의 field에 대해 접근하여 값을 변경할 때는 thread safe한 방법을 통해 접근해야 합니다. 

     

    singleton scope의 bean들은 application context를 생성할 때 만들도록 되어있어 singleton scope의 bean이 많을 수록 application 구동 시 시간이 오래 걸릴 수 있습니다.

    댓글

Designed by Tistory.