Spring 의존 자동 주입 @Primary, @Qualifier
Spring 의존 자동 주입
의존 자동 주입은 스프링 설정 파일에서 객체를 주입할 때, 명시를 하지 않아도 스프링 컨테이너가 자동으로 필요한 의존 대상 객체를 찾아서 필요한 객체에게 주입해주는 기능이다.
여기에서는 타입이 같은 빈 객체가 설정하고 자동으로 주입을 받게 되었을 시에 원하지 않는 객체를 받아오는 경우가 있다. 어떤한 경우에 그렇게 되는지 예제 코드를 통해서 알아보도록 하자.
프로젝트 생성
먼저, 아래와 같이 curl
명령어를 사용하여 Spring Boot 초기 프로젝트를 생성한다.
curl https://start.spring.io/starter.tgz \
-d bootVersion=2.6.6 \
-d baseDir=spring-core-bean-injection \
-d groupId=com.devkuma \
-d artifactId=spring-core-bean-injection \
-d packageName=com.devkuma.bean.injection \
-d applicationName=BeanInjectionApplication \
-d packaging=jar \
-d language=kotlin \
-d javaVersion=11 \
-d type=gradle-project | tar -xzvf -
위 명령어에서는 특별히 의존성을 넣지 않았다.
타입이 같은 빈 객체가 1개인 경우
타입 같은 빈 객체가 중복으로 존재하지 않고 1개만 있는 경우이다.
아래는 빈으로 만들 클래스이다.
package com.devkuma.bean.injection.ex1
class FooBean1(
val id: Int,
val name: String
)
설정 객체에서는 FooBean1
클래스로 빈을 1개만 설정을 하였다.
package com.devkuma.bean.injection.ex1
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class FooBeanConfig1 {
@Bean
fun fooBean1() =
FooBean1(
id = 1,
name = "fooBean1"
)
}
서비스 객체에서는 설정으로 만든 FooBean1
타입의 빈을 주입 받는다.
package com.devkuma.bean.injection.ex1
import org.springframework.stereotype.Service
@Service
class FooService1(
private var fooBean: FooBean1
) {
fun printBean() {
println("fooBean: id=${fooBean.id}, name=${fooBean.name}")
}
}
이제 printBean
메서드를 호출해서 객체를 표시해 본다.
package com.devkuma.bean.injection
import com.devkuma.bean.injection.ex1.FooService1
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class BeanInjectionApplication(
private val fooService1: FooService1,
) : CommandLineRunner {
override fun run(vararg args: String?) {
println("Ex 1) fooService1 ---------")
fooService1.printBean()
}
}
fun main(args: Array<String>) {
runApplication<BeanInjectionApplication>(*args)
}
실행 결과:
Ex 1) fooService1 ---------
fooBean: id=1, name=fooBean1
예상 했듯이 설정에서 넣은 id, name이 표시되었다.
타입이 같은 빈 객체가 2개인 경우
@Primary, @Qualifier 사용함
두번째로는 타입이 같은 빈 객체를 2개를 만들어서 @Primary
, @Qualifier
사용하는 설정하고, 주입 받는 곳에서 @Qualifier
사용한 경우와 사용하지 않는 경우를 보도록 하겠다.
빈으로 만들 클래스를 아래와 같이 작성한다.
package com.devkuma.bean.injection.ex2
class FooBean2(
val id: Int,
val name: String
)
설정 객체에서는 FooBean2
클래스로 빈을 2개를 설정하였다. 1개의 객체는 @Primary
어노테이션을 설정하였고, 또 다른 1개에는 @Qualifier
어노테이션에 빈 이름을 설정하였다.
package com.devkuma.bean.injection.ex2
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
@Configuration
class FooBeanConfig2 {
@Primary
@Bean
fun fooBean20() =
FooBean2(
id = 20,
name = "fooBean20"
)
@Qualifier("fooBean21")
@Bean
fun fooBean21() =
FooBean2(
id = 21,
name = "fooBean21"
)
}
서비스 객체에서는 설정으로 만든 FooBean2
타입의 빈 2개를 각각 주입 받았다.
package com.devkuma.bean.injection.ex2
import org.springframework.stereotype.Service
@Service
class BadFooService2(
private val fooBean20: FooBean2,
private val fooBean21: FooBean2
) {
fun printBean() {
println("fooBean20: id=${fooBean20.id}, name=${fooBean20.name}")
println("fooBean21: id=${fooBean21.id}, name=${fooBean21.name}")
}
}
또 다른 서비스 객체에서는 설정으로 만든 FooBean2
타입의 빈 2개를 각각 주입 받았는데, @Qualifier
를 사용하여 주입할 빈의 이름을 명시하였다.
package com.devkuma.bean.injection.ex2
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Service
@Service
class GoodFooService2(
@Qualifier("fooBean20") private val fooBean20: FooBean2,
@Qualifier("fooBean21") private val fooBean21: FooBean2
) {
fun printBean() {
println("fooBean20: id=${fooBean20.id}, name=${fooBean20.name}")
println("fooBean21: id=${fooBean21.id}, name=${fooBean21.name}")
}
}
이제 printBean
메서드를 호출해서 객체를 표시해 본다.
package com.devkuma.bean.injection
import com.devkuma.bean.injection.ex2.BadFooService2
import com.devkuma.bean.injection.ex2.GoodFooService2
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class BeanInjectionApplication(
private val badFooService2: BadFooService2
) : CommandLineRunner {
override fun run(vararg args: String?) {
println("Ex 2) badFooService2 ------")
badFooService2.printBean()
}
}
fun main(args: Array<String>) {
runApplication<BeanInjectionApplication>(*args)
}
실행 결과:
Ex 2) badFooService2 ------
fooBean20: id=20, name=fooBean20
fooBean21: id=20, name=fooBean20
결과를 보면 첫번째 badFooService2인 경우에는 fooBean20, fooBean21 빈의 내용이 동일하게 표시가 되었다. fooBean21에 fooBean20의 빈 내용이 표시된 것이다.
두번째 goodFooService2인 경우에는 @Qualifier
로 명시한 대로 빈의 내용이 표시되고 있는 것을 뵬수 있다.
@Primary, @Qualifier 사용 않함
마지막으로 타입이 같은 빈 객체를 2개를 만들어서 @Primary
, @Qualifier
를 설정하지 않는 경우를 보도록 하겠다.
빈으로 만들 클래스를 아래와 같이 작성하였다.
package com.devkuma.bean.injection.ex3
class FooBean3(
val id: Int,
val name: String
)
설정 객체에서는 FooBean3
클래스로 빈을 2개를 설정하였다. 위에서 언급한 대로 @Primary
, @Qualifier
은 따로 설정하지 않았다.
package com.devkuma.bean.injection.ex3
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class FooBeanConfig3 {
@Bean
fun fooBean31() =
FooBean3(
id = 30,
name = "fooBean31"
)
@Bean
fun fooBean32() =
FooBean3(
id = 31,
name = "fooBean32"
)
}
서비스 객체에서는 설정으로 만든 FooBean3
타입의 빈 2개를 각각 주입 받았는데, 주입시에 @Qualifier
를 사용하지 않았다.
package com.devkuma.bean.injection.ex3
import org.springframework.stereotype.Service
@Service
class FooService3(
private var fooBean31: FooBean3,
private var fooBean32: FooBean3
) {
fun printBean() {
println("fooBean31: id=${fooBean31.id}, name=${fooBean31.name}")
println("fooBean30: id=${fooBean32.id}, name=${fooBean32.name}")
}
}
이번에도 printBean
메서드를 호출해서 객체를 표시해 본다.
package com.devkuma.bean.injection
import com.devkuma.bean.injection.ex3.FooService3
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class BeanInjectionApplication(
private val goodFooService2: GoodFooService2
) : CommandLineRunner {
override fun run(vararg args: String?) {
println("Ex 3) fooService3 ---------")
fooService3.printBean()
}
}
fun main(args: Array<String>) {
runApplication<BeanInjectionApplication>(*args)
}
실행 결과:
Ex 3) fooService3 ---------
fooBean31: id=30, name=fooBean31
fooBean30: id=31, name=fooBean32
결과를 보면 fooBean31, fooBean32 둘다 설정에서 넣은대로 id, name이 표시되었다.
정리
- 타입이 같은 빈 객체가 한개면 그 빈 객체를 사용한다.
- 타입이 같은 빈 객체가 두개 이상이면,
@Primary
나@Qualifier
와 설정이 있다면 같은 값을 갖는 빈 객체를 찾고 존재하면 그 객체를 사용한다. - 타입이 같은 빈 객체가 두개 이상이고,
@Qualifier
나@Primary
설정이 없다면@Bean
메서드 명이 같은 빈 객체를 찾는다. 존재하면 그 객체를 사용한다.
결론
@Primary
나 @Qualifier
는 꼭 필요한 경우가 아니라면 사용하지 않고, @Bean
메서드 명과 주입받은 변수명을 맞추기를 추천한다.
위에 예제 코드는 GitHub에서 확인해 볼 수 있다.