해결된 질문
작성
·
684
·
수정됨
1
안녕하세요 토비님. 우선, 감동으로 청강을 이어갈 기회를 얻게되어 참 감사드립니다.
아래는 섹션8 - DataSource 자동 구성 클래스
파트의 @Bean 메서드 등록과정에 생긴 문제입니다.
의문점은 메서드 선언 순서에 영향을 받는다는것을 알게되서 다음처럼 2번의 테스트를 진행했습니다.
simple , hikari
hikari, simple
/* DataSourceConfig.java */
@Bean
@ConditionalOnMissingBean
public DataSource simpleDriverDataSource() {
...
}
@Bean
@ConditionalOnMissingBean
@ConditionalMyOnClass("com.zaxxer.hikari.HikariDataSource")
public DataSource hikariDataSource() {
...
}
hikari 에 `@ConditionalMyOnClass` 가 있기때문에 등록될 것으로 예상했지만,
simple 이 등록되었습니다.
@Bean
@...
public DataSource hikariDataSource() {
...
}
@Bean
@...
public DataSource simpleDriverDataSource() {
...
}
기묘하게도 @Bean 메서드 선언순서를 바꾸니 Hikari 빈이 등록되었습니다. 이상하다 싶어, @Primary 와 @Order(n) 을 주고 다시 테스트 해봤지만, 영향을 주지 않았습니다.
이 결과로 @ConditionalOnClass
의 인식되는 FQCN 의 조건을 바꿔보기도 하고, 위치도 바꿔봤는데, 이렇다할 동작방식을 파악하기 힘들었습니다.
그리고 @Primay
나 @Order
를 붙여가며 추가적인 등록순서에 대해 테스트를 진행했으나, 이것들 역시 영향을 주지 않았습니다.
여러 곳에서 알아본 바, @Bean 메서드는 등록순서에 영향을 주지 않는것으로 알고있었는데, 이런 경우에는 어떤 매커니즘이 동작하는지가 궁금합니다. 이 결과를 보고 생각에 혼란이 와서 질문을 드리게 되었어요. 혹시 제가 놓친것이 있다면 조언을 부탁드려도 되겠습니까?
읽어주셔서 감사합니다☺️
답변 2
2
"@Bean 메서드는 등록순서에 영향을 주지 않는것으로 알고있었는데,"
등록 순서가 아니라 등록할 대상인지 판단하는 순서에 영향을 줍니다. 빈 오브젝트를 생성하는 순서를 결정하는 것은 또 다른 복잡한 결정 요인이 있습니다.
2
@ConditionalOnMissingBean이 붙은 경우 앞에서 같은 타입의 빈 정보가 이미 등록이 됐으면 빈 등록 조건을 통과하지 못하고 무시됩니다. 동일하게 DataSource 타입의 빈이 두 개가 나열되어있고 해당 조건이 붙어있으니 둘 중의 하나만 등록이 됩니다.
@ConditionalOnMissingBean은 @Conditional을 테스트하는 순서가 핵심입니다. 앞에서 먼저 동일 타입 빈이 등록됐으면 더 이상 빈 등록을 하지 말라는 것이니까요.
그렇다면 @Conditional이 붙은 클래스 또는 @Bean 메소드를 스프링이 가져와서 판단하는 순서는 어떻게 되는가가 중요한데요. 제가 테스트 해본 바로는 @Bean으로 같은 클래스에서 정의된 경우 소스 코드에 나열된 순서대로 하나씩 가져와서 조건을 테스트 합니다. 따라서 앞에 위치한 @Bean의 조건이 통과하는 경우 뒤의 것은 무시됩니다. 만약 앞에서 @ConditionalOnClass 같은 조건을 통과하지 못했다면 다음 @Bean도 가능성이 생기겠죠.
테스트 해보신 결과가 딱 이에 맞습니다.
@Primary, @Order 등은 "이미 빈으로 등록이 되는 것으로 결정된" 여러 빈들 사이에 의존 주입 우선순위나 오브젝트 생성 순위 등을 결정하는 것이므로 지금 질문하신 내용과는 아무 관련이 없습니다. 같은 타입의 @Bean 메소드 중에서 하나만 빈으로 등록되고 나머지는 빈 등록 대상에서 아예 빠지니까요.
그런데 실제로 스프링 부트에 DataSource를 등록하는 자동 구성 코드에서는 @Bean 메소드의 나열 순서로 하지 않고, 각 DataSource 빈을 가진 자동 구성 클래스를 따로 만들고 이를 @Import하도록 만들면서 @Import에 나열된 순서대로 조건을 판단하도록 만들었습니다.
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {
이 경우에는 @Import안에 나열한 클래스 순서대로 조건을 테스트합니다. Hikari가 가장 앞에 있으니 일단 Hikari 클래스가 있는지 보고 그게 있으면 DataSource빈으로 등록될테고 나머지는 다 무시되겠죠. Hikari가 없는 경우는 그 다음 클래스로 조건 테스트가 넘어갈테고요.
이 정도로 이해하시면 문제 없을 듯합니다.
제가 강의 예제에서 스프링 부트처럼 DataSource 빈을 클래스로 다 분리해서 정의하고 @Import를 쓰는 대신 @Bean 메소드로만 구분해서 나열한 것은 예제가 너무 복잡해지지 않게 만들려고 했기 때문입니다.
저의 정리되지않은 우문에 디테일한 설명으로 혼란스런 생각정리에 도움이 많이 되었구요,
추가적인 Spring-boot 내부동작을 알려주셔서 이해에 도움이 많이 되었습니다.
감사드립니다😌