ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • spring @bean
    카테고리 없음 2023. 4. 19. 09:55

    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()) {
        // Its 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이 어떻게 동작하는지 조금 알아봤다.

    댓글

Designed by Tistory.