2022. 8. 17. 13:50ㆍJAVA/Spring
이전글에서는 외부 라이브러리 클래스의 빈을 등록하기 위해서 설정 파일 클래스를 정의하고 빈을 정의하였습니다. 이번글에서는 외부 라이브러리와 동일한 이름의 빈을 프로젝트에 정의하고 어떤 결과가 출력되는지 봅니다. 그리고 동일한 이름의 빈을 프로젝트에 명시적으로 정의하지 않고 설정 파일을 이용해서 외부 라이브러리의 빈의 값을 설정하는 방법을 알아봅니다.
1. 자동설정의 문제점 : 명시적인 빈의 무시
외부 라이브러리 자동설정
외부 라이브러리인 "yonghwan-spring-boot-starter"의 Holoman 클래스 빈을 다음과 같이 정의되어 있다고 가정합니다.
@Configuration
public class HolomanConfiguration {
@Bean
public Holoman holoman(){
Holoman holoman = new Holoman("김용환", 5);
return holoman;
}
}
dependencies {
implementation 'com.yh:yonghwan-spring-boot-starter:1.0'
}
"yonghwan-spring-boot-starter" 의조성을 추가하고 테스트코드를 수행하면 다음과 같습니다.
@RunWith(SpringRunner.class)
@SpringBootTest
public class HolomanTest {
@Autowired
Holoman holoman;
@Test
public void beanTest() throws Exception{
//given
//when
//then
System.out.println(holoman);
assertThat(holoman.getHowLong()).isEqualTo(5);
}
}
Holoman(name=김용환, howLong=5)
외부 라이브러리의 빈을 명시적으로 정의하는 경우
@Configuration
public class HolomanConfiguration {
@Bean
public Holoman holoman(){
Holoman holoman = new Holoman("김용환", 15);
return holoman;
}
}
위 HolomanConfiguration 설정 클래스는 외부 라이브러리에 정의된 것이 아닌 현재 프로젝트에 추가한 클래스 정의입니다. 위와 같이 정의된 상태에서 테스트코드를 출력하면 다음과 같습니다.
@RunWith(SpringRunner.class)
@SpringBootTest
public class HolomanTest {
@Autowired
Holoman holoman;
@Test
public void beanTest() throws Exception{
//given
//when
//then
System.out.println(holoman);
}
}
Holoman(name=김용환, howLong=5)
위 결과와 같이 howLong의 값이 "15"를 기대했으나 결과는 외부 라이브러리의 자동설정에 설정된 5가 출력됨을 볼수 있습니다.
명시적 빈은 왜 무시되었는가?
스프링 부트 애플리케이션을 실행시 IoC 컨테이너에 빈을 등록하는 과정을 수행합니다. 빈을 등록하는 과정은 2단계로 이루어져 있습니다.
- 1단계 : @ComponentScan 어노테이션을 통한 빈 등록
- 2단계 : @EnableAutoConfiguration 어노테이션을 통한 외부 라이브러리의 빈 등록
1단계인 @ComponentScan 어노테이션이 실행되면 해당 클래스의 패키지 포함 하위 패키지까지 @Component 어노테이션이 적용된 클래스의 빈을 IoC 컨테이너에 등록하게 됩니다.
2단계인 @EnableAutoConfiguration 어노테이션이 실행되면 외부 라이브러리에 @Configuration 어노테이션이 적용된 클래스에 @Bean 어노테이션이 적용된 메서드를 빈으로 등록합니다.
이때 명시적 빈을 정의했다면 1단계인 @ComponentScan 과정중에서 빈으로 등록되고 2단계인 외부 라이브러리의 빈 등록 과정에서 이름이 동일하기 때문에 명시적인 빈은 덮어 씌워지게 되어 명시적 빈은 무시되게 됩니다.
다음 그림은 위 설명을 그림으로 표현한 것입니다.
- 1단계 : @ComponentScan을 수행함으로써 @Configuration 어노테이션이 적용된 클래스의 @Bean을 IoC 컨테이너에 등록합니다.
- 2단계 : @EnableAutoConfiguration을 수행함으로써 외부 라이브러리의 자동 설정을 실행하여 빈을 등록합니다.
- 3단계 : 빈 등록시 IoC 컨테이너에 동일한 이름의 빈이 존재할때 기존 빈을 덮어씁니다.
- 4단계 : 자동 주입을 수행할때 IoC 컨테이너에 등록된 빈을 주입합니다.
2. 명시적인 빈의 해결안 : @ConditionalOnMissingBean
명시적인 빈의 무시를 해결하기 위해서 @ConditionalOnMissingBean을 사용합니다. @ConditionalOnMissingBean 어노테이션은 빈 등록시 IoC 컨테이너에 동일한 이름의 빈이 이미 존재하면 등록하지 않는 기능을 수행합니다. 외부 라이브러리의 설정 클래스의 빈을 다음과 같이 개선할 수 있습니다.
@Configuration
public class HolomanConfiguration {
@Bean
@ConditionalOnMissingBean
public Holoman holoman(){
Holoman holoman = new Holoman("김용환", 5);
return holoman;
}
}
다시 테스트 코드를 수행하면 Holoman(name="김용환", 5) 빈은 등록되지 않고 명시적 빈이 등록됨을 알 수 있습니다.
Holoman(name=김용환, howLong=15)
명시적 빈의 문제점
명시적인 빈을 정의한 이유는 외부라이브러리의 등록된 빈의 기본값을 바꾸기 위해서였습니다. 그러나 값을 바꾸기 위해서 다음과 같이 설정 클래스를 생성하고 빈을 등록하는 것은 비효율적입니다.
@Configuration
public class HolomanConfiguration {
@Bean
public Holoman holoman(){
Holoman holoman = new Holoman("김용환", 15);
return holoman;
}
}
즉, 명시적 빈의 문제점은 외부 라이브러리의 빈의 값을 변경하기 위해서 불필요한 클래스를 생성한다는 점입니다. 이 문제를 해결하기 위해서 @ConfigurationProperties 어노테이션을 사용하여 외부 라이브러리의 빈에게 설정값을 전달하여 빈을 등록합니다.
3. @ConfigurationProperties 어노테이션을 통한 자동 설정 생성
@ConfigurationProperties 어노테이션은 무엇인가?
*.properties, *yml 파일에 있는 proerty를 자바 클래스에 값을 가져와서 대응(바인딩)해주는 어노테이션입니다.
@ConfigurationProperties 어노테이션을 이용하여 외부 라이브러리의 HolomanConfiguration 설정 클래스에 holoman 빈에 설정을 주입하도록 하겠습니다.
0. Holoman 클래스 정의
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ToString
public class Holoman {
private String name;
private int howLong;
}
1. 속성 클래스인 HolomanProperties 클래스 생성
@ConfigurationProperties("holoman")
@Getter
@Setter
public class HolomanProperties {
private String name;
private int howLong;
}
@ConfigurationProperties 어노테이션 사용시 "spring-boot-configuration-processor" 의존성이 필요하기 때문에 외부 프로젝트의 build.gradle 파일에 의존성을 추가합니다.
dependencies {
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
}
2. 설정 클래스인 HolomanConfiguration 클래스를 다음과 같이 정의
@Configuration
@EnableConfigurationProperties(HolomanProperties.class)
public class HolomanConfiguration {
@Bean
@ConditionalOnMissingBean
public Holoman holoman(HolomanProperties properties){
Holoman holoman = new Holoman(properties.getName(), properties.getHowLong());
return holoman;
}
}
- @EnableConfigurationProperties 어노테이션 : 매개변수에 해당하는 클래스의 속성 설정을 활성화
3. yonghwanspringbootstarter 프로젝트를 로컬 메이븐 저장소에 배포
$ ./gradlew clean publishToMavenLocal
만약 로컬 메이븐 저장소에 배포하는 방법을 알고 싶다면 다음글을 참고해주시면 감사하겠습니다.
https://yonghwankim-dev.tistory.com/508
4. 외부 라이브러리를 사용하고자 하는 프로젝트에서 의존성을 업데이트합니다.
5. 현재 프로젝트에서 자동설정시 holoman 빈에게 설정값을 전달하기 위해서 resources/application.yml 파일에 설정값을 정의합니다.
holoman:
name: 김용환
how-long: 60
*.properties 파일에서는 다음과 같이 정의할 수 있습니다.
holoman.name = 김용환
holoman.how-long = 60
6. 테스트 코드를 수행합니다.
@RunWith(SpringRunner.class)
@SpringBootTest
public class HolomanTest {
@Autowired
Holoman holoman;
@Test
public void beanTest() throws Exception{
//given
//when
//then
assertThat(holoman.getHowLong()).isEqualTo(60);
}
}
테스트 결과 application.xml에 정의된 속성값으로 빈이 설정된 것을 볼 수 있습니다.
정리하며
- 명시적 빈을 사용할때 무시되는 이유는 @EnableAutoConfiguration 수행시 기존 빈을 덮어씌우기 때문이다.
- 이를 해결하기 위해서는 @ConditionalOnMissingBean 어노테이션을 사용함
- 명시적 빈을 사용하지 않기 위해서 @ConfigurationProperties 어노테이션을 사용하여 빈에 속성을 주입할 수 있음
- 외부 라이브러리 빈에게 주입하기 위해서 *.properties 파일이나 *.yml 파일을 통해서 속성값을 정의하여 주입할 수 있음
References
source code : https://github.com/yonghwankim-dev/springboot_study
[Spring] @Configuration 개념과 장점
[Spring Boot]@EnableConfigurationProperties
'JAVA > Spring' 카테고리의 다른 글
빈(Bean)의 스코프 (0) | 2022.08.31 |
---|---|
[Spring][IoC] @Autowired 어노테이션 (0) | 2022.08.29 |
컴포넌트 스캔(ComponentScan)과 빈(Bean) 설정 (0) | 2022.08.15 |
[springboot][Gradle] 자동 설정 만들기 #1 Starter와 Auto-Configure (0) | 2022.08.15 |
[springboot] 자동 설정(Auto-Configuration) 이해 (0) | 2022.08.15 |