[Spring] Bean의 Scope

2020년 01월 13일 by Xion

    [Spring] Bean의 Scope 목차

Bean의 Scope이란?

-Bean의 Scope란 Bean의 생성방식을 결정하는 것입니다.

ex) Scope에 따라서 Bean이 Application당 1개만 생성(singleton방식)되거나, 필요할 때마다 새로 생성을 한다던지 하는 그런한 방식을 의미합니다.

 

※ single ton ?

- 해당 애플리케이션 전반에 걸쳐서 해당 bean의 instance가 오직 1개뿐인 경우.

 

2. Bean의 Scope 종류

Singleton

 어플리케이션이 동작하는동안 단 한개만 만들어진다는 의미입니다. Spring에서의 Bean들은 별도의 설정이 없다면 기본적으로 Singleton으로 생성이 됩니다. 즉, 지금까지 생성했던 Bean들은 모두 Singleton으로 주입이 되었던 것입니다.

예제를 통해 조금 더 자세히 알아보겠습니다.

@Component

public class Single { ... }

-> 이것이 Bean의 Scope의 Singleton입니다.

@Component
public class Test {
    @Autowired
    Single single;
​
    public Single getSingle(){
        return single;
    }

}

테스트를 위해서 하나의 Class를 더 생성했습니다. Test Class에서는 Single 객체를 @Autowired어노테이션을 통해 주입을 받습니다. getSingle()메소드를 가지고 있습니다.

@Component
public class AppRunner implements ApplicationRunner {
    @Autowired
    Single single;
    @Autowired
    Test test;
​
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(single);

        System.out.println(test.getSingle());
    }

}

실행을 위한 runner Class입니다. 앞서 생성한 Single, Test Class를 @Autowired어노테이션을 통해 주입받고 있습니다. 실행시에는 주입받은 Single객체를 출력하고, 주입받은 test객체를 통해 single을 얻어와 또 출력하도록 했습니다. 결과는?

모두 같은 객체를 출력하는 것을 볼 수 있습니다.

 

2.2 Prototype

다음은 Prototype Scope입니다. Prototype의 경우에는 Bean을 IoC컨테이너로 부터 받아올 때마다 매번 새로운 객체를 생성하게 됩니다.

 

@Component

@Scope("prototype")

public class Proto { ... }

Prototype Scope을 가지는 Proto Class 입니다. 클래스 상단에 @Component 어노테이션 이외에 @Scope("prototype") 어노테이션을 부여해 Scope를 Prototype으로 지정했습니다.

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

Single Class입니다. Proto 객체를 주입받고, 그 객체를 리턴해주는 getProto() 메소드를 가지고 있습니다.

@Component
public class AppRunner implements ApplicationRunner {
​
    @Autowired
    Single single;
    @Autowired
    Proto proto;
​
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(proto);
        System.out.println(single.getProto());
    }

}

테스트를 위한 Runner Class 입니다. Single에서 주입 받도록한 Proto외 또 하나의 Proto 객체를 주입받는 것을 볼 수 있습니다. AppRunner Class에서 주입받은 proto와 Single Class 에서 주입받은 proto를 출력 해보도록하겠습니다.

 

모두 다른 객체가 출력되는 것을 볼 수 있습니다. 바로 IoC 컨테이너로 부터 Bean을 가져올때마다 새로운 객체가 생성되기 때문이죠

 


2.2.1 Singleton Scope안의 Prototype Scope

 

@Component
@Scope("prototype")

public class Proto {}

우선 Proto Class는 매번 새로운 객체를 주입 받기 위해 prototype의 Scope를 가지도록 했습니다.

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

}

Single Class의 경우에는 아무런 Scope을 지정하지 않았으므로 Singleton Scope을 기본으로 가지게 됩니다. 또한, Proto 객체를 주입받고, 그것을 리턴하는 메소드를 만들었군요. 문제는 여기서 발생합니다. 

@Component
public class AppRunner implements ApplicationRunner {
​
    @Autowired
    ApplicationContext ctx;
​
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(ctx.getBean(Single.class).getProto());
        System.out.println(ctx.getBean(Single.class).getProto());
        System.out.println(ctx.getBean(Single.class).getProto());
    }

}

​

== 결과 ==
com.keesun.spring.Proto@58c1da09
com.keesun.spring.Proto@58c1da09

com.keesun.spring.Proto@58c1da09

proto의 경우에는 IoC 컨테이너에 의해 Bean을 주입을 받을때마다 새로운 객체가 생성된다고 하지 않았냐구요?

잘 생각해보아야 합니다. single에 IoC 컨테이너가 Bean을 주입해줄 때 single의 필드에 존재하는 Proto에 또 주입이 되고 있는 형태입니다. 이때 proto는 Prototype이지만 single은 Singleton입니다. 즉 Single안에 존재하는 Proto는 새로 Bean이 생성되는 일이 없다는 것이죠.

해결방법

1. Proxymode 사용

 

@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)

public class Proto {}

proto의 @Scope 어노테이션 안에 proxyMode를 추가하면 됩니다

@Component

public class Single {

    @Autowired

    Proto proto;

이렇게 된다면 위의 proto안에 Proto를 감싸고 있는 Proxy객체가 주입되게 됩니다. Proto를 감싸는 Proxy는 자동으로 Proto를 상속받아 생성되므로 Proto타입의 참조변수 안에 주입이 가능해지게 됩니다.

Single안의 Proto의 경우는 Proxy에 의해 새로운 객체들이 생성되어 출력되는 것을 볼 수 있습니다. 

why?

 

 

Single안의 Proto의 참조를 하는 경우 Proxy를 통해 참조하도록 하여 새로운 객체를 돌려주도록 하는 것입니다. 왜 Proxy로 감싸야할까요? 반대로 한번 생각해보면 간단합니다.

Single안에 있는 Proto를 직접 참조하게 된다면 중간에 Spring이 개입하여 새로운 객체를 반환하도록 할 수 있는 개입의 여지가 아예 없기 때문입니다.

 

 

 


결과적으로, 위에 사진과 같이 proxyMode를 설정해줌에 따라 프록시를 참조하게 되면, Prototype 즉, 매번 새로운 객체를 가져올 수 있습니다. 

대부분의 상황에서는 singleton을 사용하지만

longrun하는(Scope가 넓은) Scope에서 짧은 생명주기(Scope)를 주입받을때는 반드시 고려하자.

ex)멀티쓰레드 환경에서 int value라는값이 동일한 곳을 보고있는데  A라는 쓰레드에서 설정한 값이 B에서는 동일한 값이 나오면 안되므로 thread safe한 방법으로 코딩을 해야한다.