1. 소개

Spring Boot 2.1 업그레이드는 예상치 못한 BeanDefinitionOverrideException 발생으로 사람들을 놀라게 했습니다 . 이는 개발자를 혼란스럽게 할 수 있으며 Spring에서 bean 재정의 동작에 무슨 일이 일어났는지 궁금하게 만들 수 있습니다.

이 사용방법(예제)에서는 이 문제를 해결하고 이를 해결하는 최선의 방법을 배웁니다.

2. 메이븐 의존성

예제 Maven 프로젝트의 경우 Spring Boot Starter 의존성을 추가해야 합니다  .

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

3. 빈 재정의

스프링 빈은 ApplicationContext 내에서 이름으로 식별됩니다 .

따라서 bean overriding은 다른 bean과 같은 이름을 가진 ApplicationContext 내에서 bean을 정의할 때 발생하는 기본 동작입니다 . 이름이 충돌하는 경우 이전 빈을 간단히 대체하여 작동합니다.

Spring 5.1부터 개발자가 예기치 않은 bean 재정의를 방지하기 위해 예외를 자동으로 throw할 수 있도록 BeanDefinitionOverrideException 이 도입되었습니다 . 기본적으로 Bean 재정의를 허용하는 원래 동작을 계속 사용할 수 있습니다.

4. Spring Boot 2.1의 구성 변경

Spring Boot 2.1은 방어적인 접근 방식으로 기본적으로 Bean 재정의를 비활성화했습니다. 주된 목적은 실수로 Bean을 오버라이딩하는 것을 방지하기 위해 중복된 Bean 이름을 미리 알아두는 것입니다 .

따라서 Spring Boot 애플리케이션이 Bean 재정의에 의존하는 경우 Spring Boot 버전을 2.1 이상으로 업그레이드한 후 BeanDefinitionOverrideException 이 발생할 가능성이 매우 높습니다 .

다음 섹션에서는 BeanDefinitionOverrideException이 발생하는 예를 살펴본 다음 몇 가지 솔루션에 대해 설명합니다.

5. 충돌하는 Bean 식별

BeanDefinitionOverrideException을 생성하기 위해 각각 testBean() 메서드 가 있는 두 개의 서로 다른 Spring 구성을 생성해 보겠습니다 .

@Configuration
public class TestConfiguration1 {

    class TestBean1 {
        private String name;

        // standard getters and setters

    }

    @Bean
    public TestBean1 testBean(){
        return new TestBean1();
    }
}
@Configuration
public class TestConfiguration2 {

    class TestBean2 {
        private String name;

        // standard getters and setters

    }

    @Bean
    public TestBean2 testBean(){
        return new TestBean2();
    }
}

다음으로 Spring Boot 테스트 클래스를 만듭니다.

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {TestConfiguration1.class, TestConfiguration2.class})
public class SpringBootBeanDefinitionOverrideExceptionIntegrationTest {

    @Test
    public void whenBeanOverridingAllowed_thenTestBean2OverridesTestBean1() {
        Object testBean = applicationContext.getBean("testBean");

        assertThat(testBean.getClass()).isEqualTo(TestConfiguration2.TestBean2.class);
    }
}

테스트를 실행하면 BeanDefinitionOverrideException 이 생성됩니다 . 그러나 예외는 몇 가지 유용한 정보를 제공합니다.

Invalid bean definition with name 'testBean' defined in ... 
... com.baeldung.beandefinitionoverrideexception.TestConfiguration2 ...
Cannot register bean definition [ ... defined in ... 
... com.baeldung.beandefinitionoverrideexception.TestConfiguration2] for bean 'testBean' ...
There is already [ ... defined in ...
... com.baeldung.beandefinitionoverrideexception.TestConfiguration1] bound.

예외는 두 가지 중요한 정보를 나타냅니다.

첫 번째는 충돌하는 빈 이름인 testBean 입니다 .

Invalid bean definition with name 'testBean' ...

두 번째는 영향을 받는 구성의 전체 경로를 보여줍니다.

... com.baeldung.beandefinitionoverrideexception.TestConfiguration2 ...
... com.baeldung.beandefinitionoverrideexception.TestConfiguration1 ...

결과적으로 서로 다른 두 개의 bean이 testBean 으로 식별되어 충돌이 발생하는 것을 볼 수 있습니다. 또한 Bean은 구성 클래스 TestConfiguration1TestConfiguration2 내에 포함되어 있습니다 .

6. 가능한 해결책

구성에 따라 Spring Bean은 명시적으로 설정하지 않는 한 기본 이름을 갖습니다.

따라서 가능한 첫 번째 솔루션은 빈의 이름을 바꾸는 것입니다. Spring에서 빈 이름을 설정하는 몇 가지 일반적인 방법이 있습니다.

6.1. 메서드 이름 변경

기본적으로 Spring은 어노테이션이 달린 메소드의 이름을 bean 이름으로 사용합니다 .

따라서 우리의 예제와 같이 구성 클래스에 정의된 빈이 있는 경우 단순히 메서드 이름을 변경하면 BeanDefinitionOverrideException이 방지 됩니다 .

@Bean
public TestBean1 testBean1() {
    return new TestBean1();
}
@Bean
public TestBean2 testBean2() {
    return new TestBean2();
}

6.2. @Bean 어노테이션

Spring의 @Bean 어노테이션은 bean을 정의하는 매우 일반적인 방법입니다.

따라서 또 다른 옵션은 @Bean 어노테이션 의 이름 속성을 설정하는 것입니다 .

@Bean("testBean1")
public TestBean1 testBean() {
    return new TestBean1();
}
@Bean("testBean2")
public TestBean1 testBean() {
    return new TestBean2();
}

6.3. 스테레오타입 어노테이션

빈을 정의하는 또 다른 방법은 스테레오타입 어노테이션을 사용하는 것입니다 . Spring의 @ComponentScan 기능을 사용하면 @Component 어노테이션을 사용하여 클래스 수준에서 빈 이름을 정의할 수 있습니다 .

@Component("testBean1")
class TestBean1 {

    private String name;

    // standard getters and setters

}
@Component("testBean2")
class TestBean2 {

    private String name;

    // standard getters and setters

}

6.4. 타사 라이브러리에서 오는 Bean

경우에 따라 타사 스프링 지원 라이브러리에서 생성된 빈으로 인해 이름 충돌이 발생할 수 있습니다 .

이런 일이 발생하면 위의 솔루션 중 하나를 사용할 수 있는지 확인하기 위해 애플리케이션에 속하는 충돌 빈을 식별해야 합니다.

그러나 빈 정의를 변경할 수 없는 경우 빈 재정의를 허용하도록 Spring Boot를 구성하는 것이 해결 방법이 될 수 있습니다.

빈 재정의를 활성화하려면 application.properties 파일 에서 spring.main.allow-bean-definition-overriding 속성을 true 로 설정합니다 .

spring.main.allow-bean-definition-overriding=true

이렇게 함으로써 우리는 bean 정의를 변경하지 않고 bean 재정의를 허용하도록 Spring Boot에 지시하고 있습니다.

마지막으로 빈 생성 순서는 대부분 런타임에 영향을 받는 의존성 관계에 의해 결정되기 때문에 어떤 빈이 우선 순위를 가질지 추측하기 어렵다는 점을 알아야 합니다 . 따라서 bean 재정의를 허용하면 bean의 의존성 계층 구조를 충분히 알지 못하는 한 예기치 않은 동작이 발생할 수 있습니다.

7. 결론

이 글에서는 Spring에서 BeanDefinitionOverrideException이 무엇을 의미하는지, 왜 갑자기 나타나는지, 그리고 Spring Boot 2.1 업그레이드 이후에 어떻게 대처해야 하는지에 대해 설명하였다.

항상 그렇듯이 이 기사의 전체 소스 코드는 GitHub 에서 찾을 수 있습니다 .

res – REST with Spring (eBook) (everywhere)