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은 구성 클래스 TestConfiguration1 및 TestConfiguration2 내에 포함되어 있습니다 .
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 에서 찾을 수 있습니다 .