spring @Bean

스프링에 자주 사용되는 어노테이션으로 @Bean에 대해 살짝 맛만 볼라고 한다.
저번에 한번 얘기를 했는데 ConfigurationClassParser클래스 doProcessConfigurationClass 메소드에 여러 메타 어노테이션을 파싱하는 부분이 있다.

...

// Process individual @Bean methods
Set<MethodMetadata> beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata methodMetadata : beanMethods) {
    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

...

doProcessConfigurationClass 메소드의 Bean을 파싱하는 부분이다.
하지만 메타 정보만 갖고 있고 이때 인스턴스는 하지 않는다.(이 부분은 저번에도 얘기 한듯 하다)
그럼 우리가 흔히 쓰는 @Bean 은 언제 인스턴스 하는지 알아보자

@Bean
public ModelClass modelClass(){
    return new ModelClass();
}

예로 위와 같은 빈이 있다 가정하자
저번에 얘기 했듯이 Spring은 내부적으로 getBean을 호출하면서 그때 인스턴스도 같이 한다.(물론 안하는 Context도 있다. 안한다는 것보다 할 필요가 없는 듯 해서 그런거 같다.)
그 중에 @Bean 어노테이션들은 BeanMethodInterceptor aop 프록시를 통해 해당 빈을 호출한다.
그리고 나서 DefaultSingletonBeanRegistry클래스에 싱글톤 빈이라고 저장해둔다.(아마도 여기에 모든 싱글톤이 있는 듯하다)

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

저기에는 실제 인스턴스화된 빈들이 저장 되어 있는 곳이다.
왜 이얘길 하냐면 우리들이 흔히 쓰는 getBean을 호출 할때 singletonObjects 멤버 변수에서 꺼내서 주는 거다.

Object sharedInstance = getSingleton(beanName);

getBean을 호출 할때 저 함수를 호출한다. getSingleton 함수를 보자

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

첫 줄만 봐도 알 것이다. Map으로 저장된 빈들을 꺼내서 사용한다.

그럼 Bean의 스코프가 prototype 일 경우는 어떠할까?
실질적으로 하는 행동은 같다.
AbstractBeanFactory 클래스의 일부분이다.

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
            try {
                return createBean(beanName, mbd, args);
            }
            catch (BeansException ex) {
                // Explicitly remove instance from singleton cache: It might have been put there
                // eagerly by the creation process, to allow for circular reference resolution.
                // Also remove any beans that received a temporary reference to the bean.
                destroySingleton(beanName);
                throw ex;
            }
        }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

else if (mbd.isPrototype()) {
    // It's a prototype -> create a new instance.
    Object prototypeInstance = null;
    try {
        beforePrototypeCreation(beanName);
        prototypeInstance = createBean(beanName, mbd, args);
    }
    finally {
        afterPrototypeCreation(beanName);
    }
    bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}

코드만 봐도 비슷한 일을 하고 있다 대신 싱글톤일 경우엔 getSingleton 을 호출 하면서 싱글톤으로 등록 할 뿐이다.
저기 위에서 말했듯이 getBean을 호출 할 경우 getSingleton에서 싱글톤이 있는지 없는지 확인을 하는데 prototype일 경우엔 없으니 계속 Bean을 새로 호출 할 것이다.

스코프가 session이거나 request일 경우에도 비슷하게 동작 하지 않나 싶다. (확인을 안해서 믿지 마시길)

다 아는 내용 이겠지만 테스트를 해봤다.

  • singleton 일 경우
ModelClass hello = applicationContext.getBean("modelClass", ModelClass.class);
ModelClass hello2 = applicationContext.getBean("modelClass", ModelClass.class);
System.out.println(hello == hello2);
//true
  • prototype 일 경우
ModelClass hello = applicationContext.getBean("modelClass", ModelClass.class);
ModelClass hello2 = applicationContext.getBean("modelClass", ModelClass.class);
System.out.println(hello == hello2);
//false

이렇게 @Bean이 어떻게 동작하는지 조금 알아봤다.