자동 구성 Bean을 커스텀 정의하면 기존 Bean은 어떻게 될까?

2023. 2. 8. 13:39Spring

스프링 부트를 사용하면서 궁금했던 점이 있다.

스프링 부트는 내가 사용하는 의존성과 관련된 구성 정보를 따로 설정하지 않아도 디폴트 자동 구성Bean을 제공해준다.

따라서 내가 복잡한 DB, DB 접근 기술(MyBatis, JPA) 같은 의존성 Bean을 관리하지 않아도 되었다.

하지만 기본 값이 아닌 추가 설정이 필요하다면 Bean으로 등록해 커스텀 할 수 있는데 예를 들어, MyBatis에 대한 설정을 커스텀할 때, 다음과 같이 우리는 SqlSessionFactory Bean을 정의한다.

자동 구성이 제공하는 Bean 처리 방법에 대한 어떠한 내용도 정의하지 않는다.

@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource);
    factoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml"));
    factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/**/*.xml"));
    factoryBean.setTypeAliasesPackage("com.example.mybatis");
    return factoryBean.getObject();
}

그렇다면 스프링 부트가 제공하는 자동 구성 Bean은 어떻게 되는 걸까? 그냥 재정의될까? 하는 궁금증이 생겼다.


스프링 부트가 제공해주는 자동구성 SqlSessionFactory Bean을 찾아보자.

외부 라이브러리 내의 MyBatis 관련 라이브러리에서 Bean을 찾을 수 있었다.

여기서 우리의 구성과는 다른 점을 찾을 수 있다. 바로 @ConditionalOnMissingBean이다.

이 애노테이션에 대해 알아보자.

JavaDoc을 읽어보면 메서드 리턴 유형의 Bean이 BeanFactory에 존재하지 않을 때, 등록이 된다고 나와있다.

따라서 SqlSessionFactory Bean을 먼저 찾아보고 존재하지 않으면 Bean으로 등록한다.

→ 우리가 SqlSessionFactory를 Custom하기 위해 Bean으로 등록을 했기 때문에 @ConditionalOnMissingBean의 조건을 만족하지 않아(Bean Factory에 이미 존재해서) 자동 구성 Bean이 등록이 되지 않는 것이다.


궁금증을 해결할 수 있었다. 다만 실제 그렇게 동작하는지 예제 코드로 테스트해보자.

public class ConditionalOnMissingBeanTest {
    @Test
    public void test() {
                // Spring Bean 등록
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(ConditionalOnMissingBeanTest.ExampleConfiguration.class);
        context.register(ConditionalOnMissingBeanTest.ExampleConfiguration2.class);
        context.refresh();

        ExampleConfiguration bean = context.getBean(ExampleConfiguration.class);
        ExampleConfiguration2 bean2 = context.getBean(ExampleConfiguration2.class);

                assertThat(bean.ec).isNull();
        assertThat(bean2.ec).isNotNull();
    }

    @Configuration
    static class ExampleConfiguration {
        ExampleClass ec;
        @Bean
        @ConditionalOnMissingBean
        public ExampleClass exampleClass() {
            FirstClass firstClass = new FirstClass();
            ec = firstClass;
            return firstClass;
        }
    }

    @Configuration
    static class ExampleConfiguration2 {
        ExampleClass ec;
        @Bean
        public ExampleClass exampleClass() {
            SecondClass secondClass = new SecondClass();
            ec = secondClass;
            return secondClass;
        }
    }

    interface ExampleClass {
    }

    static class FirstClass implements ExampleClass {
    }

    static class SecondClass implements ExampleClass {
    }
}

두 개의 Bean을 정의했고 ExampleClass라는 같은 타입을 반환한다.

따라서 ExampleConfiguration 내의 exampleClass 메서드는 ExampleClass 가 존재하는지 확인하고 실행된다. 하지만 이미 ExampleConfiguration2 에서 Bean이 생성되었기 때문에 따로 실행되지 않는다.

그래서 ExampleConfiguration의 ExampleClass는 Null이어야하고 2의 경우는 Null이 아니어야한다.

아래는 해당 테스트 코드의 결과이다.

예상했던 대로 수행되는 것을 확인할 수 있었다.