최근 팀원분 중 한명이 class를 새로 만들었었는데 예시는 아래와 같았습니다.
@Configuration
class ConfigurationA() { ... }
그리고 문제 없이 작동하고 있었습니다. 하지만 의문이 있었습니다. 왜냐하면 이미 다른 모듈에 똑같은 클래스가 있었기 때문입니다.
그동안의 제 상식으로는 이해가 가지 않았습니다. spring은 처음떠서 bean들을 등록할 때 이름을 직접 넣어주는게 아니라면 클래스명의 제일 앞글자를 소문자로 바꿔서 bean 이름을 등록하는 것으로 알고 있었기 때문입니다.
팀원분이 실수로 같은 클래스 명의 bean을 만들었다면 애플리케이션을 실행하는 과정에서 에러가 발생해야한다고 생각했습니다.
애플리케이션이 문제없이 떴으니 bean이름을 한번 쭉 뽑아봤습니다.
fun main(args: Array<String>) {
val applicationContext: ConfigurableApplicationContext = runApplication<Test>(*args)
applicationContext.beanDefinitionNames.sorted().forEach {
log.info { "beanDefinitionName: $it" }
}
}
실행시켜봤더니 bean이 2개가 등록이 됐는데 서로 이름이 달랐습니다.
하나는 configurationA
다른 하나는 app.a.b.configurationA
이렇게 등록되어있었고 클래스의 경로를 따서 빈이름이 생성됐었습니다.
원인은 spring.factories 파일을 통한 Configuration 등록때문이었습니다.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
app.a.b.config.ConfigurationA
spring.factories 파일을 이용하여 설정파일이 빈으로 등록될때는 일반 bean이랑은 다른 로직으로 빈으로 등록이 되는것을 확인했습니다.
내부코드를 살펴보니 spring.factories파일을 통해 Configuration Class를 등록하면
AutoConfigurationImportSelector
이라는 클래스의
getCandidateConfiguration()
함수에서 설정 클래스들을 가져오고 필터링을 한뒤에 bean으로 등록하는데 이때 bean이름을 spring.factories파일에 등록한 그대로 이름을 지정합니다.
그래서 같은 클래스가 있었어도 패키지 경로가 추가된 bean이름으로 등록이 됐었습니다.
spring.factories에 등록한 Configuration class의 빈 등록과정을 살펴보았으니 일반적인 @Service @Controller, @Repository 어노테이션이 붙은 클래스들의 bean이름은 어떻게 생성되는지도 살펴보았습니다.
우선 ClassPathBeanDefinitionScanner라는 클래스에 doScan이라는 함수가 있습니다.
빨간색 박스의 generateBeanName이라는 함수를 통해 bean이름을 생성해줍니다.
내부적으로는 아래와 같은 흐름대로 이름이 생성됩니다.
결론적으로 bean의 이름을 직접 정하는게 아니면 Class명의 가장 앞에만 소문자로 바꿔서 bean 이름으로 등록합니다.
추가로 위의 doScan이라는 함수에서 checkCandidate(beanName, candidate)
라는 함수에서 유효성을 체크하는데 만약 중복되는 이름이 있다면
ConflictingBeanDefinitionException
에러를 뱉습니다.
spring.factories 파일을 활용하여 Configuration Class를 등록하게 되면 명시적으로 볼수있다는 장점이 있지만 이렇게 기대와 다르게 bean 이름이 생성될 수 있고 또 누군가 같은 이름의 class를 만들어서 설정 파일로 등록하게 되면 라이브러리에 따라서 설정값이 오버라이딩 된다거나 하는 문제가 있을 수 있을거같습니다.
결국 팀원들끼리 자주 공유하고 약속을 정하는것이 중요할 것 같습니다.