hotamul의 개발 이야기

Configuration, proxyBeanMethods 본문

Dev./Spring-Boot

Configuration, proxyBeanMethods

hotamul 2023. 9. 7. 23:50

@Configuration annotation은 @Component라는 meta annotation으로 만들어진 composed annotation이다. (Meta Annotation, Composed Annotation)
하지만 @Component와는 다른 점이 바로 proxyBeanMethods 라는 것이다.

 

proxyBeanMethods = false

기본적으로 @Configuration annotation의 proxyBeanMethodstrue이다. 그럼 proxyBeanMethodsfalse일 때와 true일 때와 뭐가 다른 걸까?

 

@Configuration annotation에 대해 이해하기 위해 아래와 같은 테스트 코드를 만들어봤다.

public class ConfigurationTest {
    @Test
    void configuration() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
        ac.register(MyConfig.class);
        ac.refresh();

        Bean1 bean1 = ac.getBean(Bean1.class);
        Bean2 bean2 = ac.getBean(Bean2.class);

        Assertions.assertThat(bean1.common).isSameAs(bean2.common);

    }

    @Configuration
    static class MyConfig {
        @Bean
        Common common() {
            return new Common();
        }

        @Bean
        Bean1 bean1() {
            return new Bean1(common());
        }

        @Bean
        Bean2 bean2() {
            return new Bean2(common());
        }
    }

    static class Bean1 {
        private final Common common;

        Bean1(Common common) {
            this.common = common;
        }
    }

    static class Bean2 {
        private final Common common;

        Bean2(Common common) {
            this.common = common;
        }
    }

    static class Common {
    }
}

 

Java 코드로만 보자면 위 코드의 MyConfig 클래스는 bean1(), bean2() 메소드가 호출되면 새로운 Common 객체를 생성하는 것으로 보인다. (return new Bean1(common()))

 

하지만 아래 테스트 코드를 실행하면 테스트가 성공하는 것을 확인할 수 있다.

    @Test
    void configuration() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
        ac.register(MyConfig.class);
        ac.refresh();

        Bean1 bean1 = ac.getBean(Bean1.class);
        Bean2 bean2 = ac.getBean(Bean2.class);

        Assertions.assertThat(bean1.common).isSameAs(bean2.common);

    }

 

위 테스트 결과는 factory method를 이용해서 object를 생성하는 코드를 여러 번 호출하더라도 딱 한 개의 object만 사용하고 있다는 것을 말해준다. 어떻게 된 일일까?

 

java 코드상으로만 보면 하나의 Bean(Common)을 두 개 이상의 다른 Bean(Bean1, Bean2)에서 의존할 때 factory method(common())로 호출하게 되면 새로운 Bean이 만들어져야 할 것 같다.

 

Spring Container에서 관리되는 Bean은 기본적으로 singleton이다. Spring의 기본적인 동작 방식(Bean을 Singleton으로 관리)을 java 코드로도 가능하게 하려다 보니 Proxy를 만들어서 이러한 Singleton 방식을 유지하고 있는 것이다.

간단하게 @Configuration annotation이 선언된 클래스가 동작하는 과정을 아래 코드에서 확인해 볼 수 있다.

    static class MyConfigProxy extends MyConfig {
        private Common common;

        @Override
        Common common() {
            if (this.common == null) this.common = super.common();

            return this.common;
        }
    }

 

@Configuration annotation을 선언하면 기본적으로 MyConfigProxy 클래스처럼 생성한 객체를 캐싱해서 return 한다.

Spring 5.2에서부터 이러한 proxy 방식을 off 할 수 있게 되었는데 이것이 proxyBeanMethods = false이다. 즉 @Configuration(proxyBeanMethods=false)를 사용한다는 것은 @Component annotation을 사용하는 것과 동일하다.

    // @Configuration(proxyBeanMethods=false)
    @Component
    static class MyConfig {
        @Bean
        Common common() {
            return new Common();
        }

        @Bean
        Bean1 bean1() {
            return new Bean1(common());
        }

        @Bean
        Bean2 bean2() {
            return new Bean2(common());
        }
    }

(@Component가 선언된 MyConfig 클래스는 @Configuration(proxyBeanMethods=false)가 선언된 것과 동일하게 동작한다)

 

Spring에서 proxyBeanMethods=false 사용하는 예시

EnableScheduling annotation을 보면 SchedulingConfiguration을 import 하는 것을 볼 수 있는데 이때 SchedulingConfiguration 클래스에 @Confugration annotation이 proxyBeanMethods=false로 선언되어 있는 것을 확인할 수 있다.

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {

    @Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
        return new ScheduledAnnotationBeanPostProcessor();
    }

}

 

해당 포스트는 토비의 스프링 부트 - 이해와 원리 강의 중 "@Configuration과 proxyBeanMethods" 내용을 정리한 글입니다.

Comments