Loading...
Spring Framework Reference Documentation 7.0.2의 Dependency Injection의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
Dependency injection(DI)는 객체가 자신의 의존성(즉, 함께 동작하는 다른 객체들)을 생성자 인수, 팩터리 메서드의 인수, 또는 객체 인스턴스가 팩터리 메서드에서 생성되거나 반환된 이후에 설정되는 프로퍼티를 통해서만 정의하는 과정입니다. 컨테이너는 빈을 생성할 때 이러한 의존성을 주입합니다. 이 과정은 근본적으로 빈 자체가 클래스를 직접 생성하거나 Service Locator 패턴을 사용하여 스스로 의존성의 인스턴스화나 위치를 제어하는 것과 반대(따라서 Inversion of Control이라는 이름)입니다.
코드는 DI 원칙을 사용하면 더 깔끔해지고, 객체에 의존성을 제공할 때 디커플링이 더 효과적입니다. 객체는 자신의 의존성을 조회하지 않으며 의존성의 위치나 클래스를 알지 못합니다. 그 결과, 특히 의존성이 인터페이스나 추상 기본 클래스에 있을 때, 여러분의 클래스는 테스트하기 더 쉬워지며,
이를 통해 단위 테스트에서 스텁이나 목 구현을 사용할 수 있습니다.
DI에는 두 가지 주요 변형이 있습니다: Constructor-based dependency injection 과 Setter-based dependency injection입니다.
Constructor-based DI는 컨테이너가 여러 개의 인수를 가진 생성자를 호출함으로써
수행되며, 각 인수는 하나의 의존성을 나타냅니다. 빈을 생성하기 위해
특정 인수와 함께 static 팩터리 메서드를 호출하는 것은 거의 동등하며,
이 설명에서는 생성자와 static 팩터리 메서드의 인수를 동일하게 다룹니다.
다음 예제는 생성자 주입으로만 의존성 주입이 가능한 클래스를 보여줍니다:
1public class SimpleMovieLister { 2 3 // the SimpleMovieLister has a dependency on a MovieFinder 4 private final MovieFinder movieFinder; 5 6 // a constructor so that the Spring container can inject a MovieFinder 7 public SimpleMovieLister(MovieFinder movieFinder) { 8 this.movieFinder = movieFinder; 9 } 10 11 // business logic that actually uses the injected MovieFinder is omitted... 12}
1// a constructor so that the Spring container can inject a MovieFinder 2class SimpleMovieLister(private val movieFinder: MovieFinder) { 3 // business logic that actually uses the injected MovieFinder is omitted... 4}
이 클래스에는 특별한 점이 없다는 것에 주목하십시오. 이 클래스는 컨테이너 특화 인터페이스, 기본 클래스, 어노테이션에 대한 의존성이 없는 POJO입니다.
생성자 인수 해결 매칭은 인수의 타입을 사용하여 발생합니다. 빈 정의의 생성자 인수에 잠재적인 모호성이 없다면, 빈이 인스턴스화될 때 빈 정의에서 정의된 생성자 인수의 순서가 해당 인수가 적절한 생성자에 제공되는 순서입니다.
다음 클래스를 고려해 보십시오:
1package x.y; 2 3public class ThingOne { 4 5 public ThingOne(ThingTwo thingTwo, ThingThree thingThree) { 6 // ... 7 } 8}
1package x.y 2 3class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree)
ThingTwo와 ThingThree 클래스가 상속에 의해 관련되어 있지 않다고 가정하면,
잠재적인 모호성이 존재하지 않습니다. 따라서 다음 설정은 잘 동작하며,
<constructor-arg/> 엘리먼트에서 생성자 인수의 인덱스나 타입을
명시적으로 지정할 필요가 없습니다.
1<beans> 2 <bean id="beanOne" class="x.y.ThingOne"> 3 <constructor-arg ref="beanTwo"/> 4 <constructor-arg ref="beanThree"/> 5 </bean> 6 7 <bean id="beanTwo" class="x.y.ThingTwo"/> 8 9 <bean id="beanThree" class="x.y.ThingThree"/> 10</beans>
다른 빈이 참조될 때는 타입이 알려져 있고, (앞의 예제에서처럼) 매칭이
발생할 수 있습니다. <value>true</value>와 같은 단순 타입이 사용될 때는
Spring이 값의 타입을 결정할 수 없으므로, 도움 없이는 타입으로 매칭할 수 없습니다.
다음 클래스를 고려해 보십시오:
1package examples; 2 3public class ExampleBean { 4 5 // Number of years to calculate the Ultimate Answer 6 private final int years; 7 8 // The Answer to Life, the Universe, and Everything 9 private final String ultimateAnswer; 10 11 public ExampleBean(int years, String ultimateAnswer) { 12 this.years = years; 13 this.ultimateAnswer = ultimateAnswer; 14 } 15}
1package examples 2 3class ExampleBean( 4 private val years: Int, // Number of years to calculate the Ultimate Answer 5 private val ultimateAnswer: String // The Answer to Life, the Universe, and Everything 6)
앞의 시나리오에서, type 속성을 통해 생성자 인수의 타입을
명시적으로 지정하면 컨테이너는 단순 타입에 대해 타입 매칭을 사용할 수 있습니다.
다음 예제와 같습니다:
1<bean id="exampleBean" class="examples.ExampleBean"> 2 <constructor-arg type="int" value="7500000"/> 3 <constructor-arg type="java.lang.String" value="42"/> 4</bean>
다음 예제와 같이 index 속성을 사용하여 생성자 인수의 인덱스를
명시적으로 지정할 수 있습니다:
1<bean id="exampleBean" class="examples.ExampleBean"> 2 <constructor-arg index="0" value="7500000"/> 3 <constructor-arg index="1" value="42"/> 4</bean>
여러 개의 단순 값이 있을 때의 모호성을 해결하는 것 외에도, 인덱스를 지정하면 동일한 타입의 두 인수를 가진 생성자에서의 모호성도 해결됩니다.
Note: 인덱스는 0-based입니다.
다음 예제와 같이 값의 모호성을 제거하기 위해 생성자 매개변수 이름을 사용할 수도 있습니다:
1<bean id="exampleBean" class="examples.ExampleBean"> 2 <constructor-arg name="years" value="7500000"/> 3 <constructor-arg name="ultimateAnswer" value="42"/> 4</bean>
이것이 기본적으로 동작하게 하려면, Spring이 생성자에서 매개변수 이름을 찾을 수 있도록
코드를 -parameters 플래그를 활성화하여 컴파일해야 한다는 점을 명심하십시오.
-parameters 플래그로 코드를 컴파일할 수 없거나 원하지 않는 경우,
JDK 어노테이션인 @ConstructorProperties를 사용하여
생성자 인수의 이름을 명시적으로 지정할 수 있습니다.
샘플 클래스는 다음과 같이 작성해야 합니다:
1package examples; 2 3public class ExampleBean { 4 5 // Fields omitted 6 7 @ConstructorProperties({"years", "ultimateAnswer"}) 8 public ExampleBean(int years, String ultimateAnswer) { 9 this.years = years; 10 this.ultimateAnswer = ultimateAnswer; 11 } 12}
1package examples 2 3class ExampleBean 4@ConstructorProperties("years", "ultimateAnswer") 5constructor(val years: Int, val ultimateAnswer: String)
Setter-based DI는 컨테이너가 무인자 생성자나 무인자 static 팩터리 메서드를
호출하여 빈을 인스턴스화한 후, 빈의 setter 메서드를 호출함으로써 수행됩니다.
다음 예제는 순수 setter 주입을 사용해서만 의존성 주입이 가능한 클래스를 보여줍니다. 이 클래스는 일반적인 Java입니다. 이 클래스는 컨테이너 특화 인터페이스, 기본 클래스, 어노테이션에 대한 의존성이 없는 POJO입니다.
1public class SimpleMovieLister { 2 3 // the SimpleMovieLister has a dependency on the MovieFinder 4 private MovieFinder movieFinder; 5 6 // a setter method so that the Spring container can inject a MovieFinder 7 public void setMovieFinder(MovieFinder movieFinder) { 8 this.movieFinder = movieFinder; 9 } 10 11 // business logic that actually uses the injected MovieFinder is omitted... 12}
1class SimpleMovieLister { 2 3 // a late-initialized property so that the Spring container can inject a MovieFinder 4 lateinit var movieFinder: MovieFinder 5 6 // business logic that actually uses the injected MovieFinder is omitted... 7}
ApplicationContext는 자신이 관리하는 빈에 대해 constructor-based와 setter-based DI를
모두 지원합니다. 또한, 일부 의존성이 이미 생성자 방식을 통해 주입된 이후의
setter-based DI도 지원합니다. 여러분은 의존성을 BeanDefinition의 형태로
설정하며, 이를 PropertyEditor 인스턴스와 함께 사용하여 프로퍼티를
한 포맷에서 다른 포맷으로 변환합니다.
그러나 대부분의 Spring 사용자는
이러한 클래스를 직접(즉, 프로그래밍 방식으로) 다루지 않고, XML bean 정의,
어노테이션이 붙은 컴포넌트(예: @Component, @Controller 등), 또는
Java 기반 @Configuration 클래스의 @Bean 메서드를 사용합니다.
이러한 소스는 내부적으로 BeanDefinition 인스턴스로 변환되고,
전체 Spring IoC 컨테이너 인스턴스를 로드하는 데 사용됩니다.
Constructor-based와 setter-based DI를 혼합해서 사용할 수 있으므로, 필수 의존성에는 생성자를 사용하고 선택적 의존성에는 setter 메서드나 설정 메서드를 사용하는 것이 좋은 경험적 규칙입니다. setter 메서드에 @Autowired 어노테이션을 사용하여 프로퍼티를 필수 의존성으로 만들 수 있다는 점에 유의하십시오. 그러나 인수의 프로그래밍 방식 검증을 사용하는 생성자 주입이 더 바람직합니다.
Spring 팀은 일반적으로 생성자 주입을 옹호하는데, 이는
애플리케이션 컴포넌트를 불변 객체로 구현할 수 있게 하고
필수 의존성이 null이 아님을 보장하기 때문입니다. 또한
생성자로 주입된 컴포넌트는 항상 완전히 초기화된 상태로 클라이언트(호출) 코드에
반환됩니다.
부수적으로, 많은 수의 생성자 인수는 나쁜 코드 스멜로, 해당 클래스가 너무 많은 책임을 가지고 있음을 의미하며, 적절한 관심사의 분리를 더 잘 처리하도록 리팩터링해야 합니다.
Setter 주입은 주로 클래스 내에서 합리적인 기본값을 할당할 수 있는 선택적 의존성에만 사용해야 합니다. 그렇지 않으면, 코드가 의존성을 사용하는 모든 곳에서 not-null 체크를 수행해야 합니다. Setter 주입의 한 가지 이점은 setter 메서드가 해당 클래스의 객체를 나중에 재구성(reconfiguration)하거나 재주입(re-injection)하기에 적합하게 만든다는 점입니다.
따라서 JMX MBean을 통한 관리는 setter 주입에 대한 설득력 있는 사용 사례입니다.
특정 클래스에 가장 적합한 DI 스타일을 사용하십시오. 때로는 소스가 없는 서드파티 클래스를 다룰 때처럼 선택이 이미 정해져 있는 경우도 있습니다. 예를 들어, 서드파티 클래스가 어떤 setter 메서드도 노출하지 않는다면, 생성자 주입이 유일한 DI 형태일 수 있습니다.
컨테이너는 다음과 같이 빈 의존성 해결을 수행합니다:
ApplicationContext가 생성되고 모든 빈을 설명하는 설정 메타데이터로
초기화됩니다. 설정 메타데이터는 XML, Java 코드, 어노테이션으로 지정할 수 있습니다.int, long, String, boolean 등과 같은
모든 내장 타입으로 변환할 수 있습니다.Spring 컨테이너는 컨테이너가 생성될 때 각 빈의 설정을 검증합니다. 그러나 빈 프로퍼티 자체는 빈이 실제로 생성될 때까지 설정되지 않습니다. 싱글톤 스코프이고 사전 인스턴스화(기본값)로 설정된 빈은 컨테이너가 생성될 때 생성됩니다. 스코프는 Bean Scopes에 정의되어 있습니다.
그렇지 않으면, 빈은 요청될 때만 생성됩니다. 빈의 생성은 빈의 의존성과 그 의존성의 의존성(등등)이 생성되고 할당되면서 빈 그래프가 생성되는 결과를 초래할 수 있습니다. 이러한 의존성 간의 해결 불일치는 영향을 받는 빈이 처음 생성될 때와 같이 나중에 나타날 수 있다는 점에 유의하십시오.
생성자 주입을 주로 사용하는 경우, 해결 불가능한 순환 의존성 시나리오를 만들 수 있습니다.
예를 들어: Class A는 생성자 주입을 통해 class B의 인스턴스를 필요로 하고,
class B는 생성자 주입을 통해 class A의 인스턴스를 필요로 합니다.
Class A와 B에 대한 빈을 서로 주입되도록 설정하면,
Spring IoC 컨테이너는 런타임에 이러한 순환 참조를 감지하고
BeanCurrentlyInCreationException을 던집니다.
한 가지 가능한 해결책은 일부 클래스의 소스 코드를 수정하여 생성자가 아닌 setter로 설정되도록 하는 것입니다. 또는 생성자 주입을 피하고 setter 주입만 사용할 수도 있습니다. 즉, 권장되지는 않지만, setter 주입으로 순환 의존성을 설정할 수 있습니다.
일반적인 경우(순환 의존성이 없는 경우)와 달리, 빈 A와 빈 B 사이의 순환 의존성은 두 빈 중 하나가 자체가 완전히 초기화되기 전에 다른 빈에 주입되도록 강제합니다 (고전적인 닭과 달걀 시나리오).
일반적으로 Spring이 올바르게 동작한다고 믿을 수 있습니다. Spring은 존재하지 않는 빈에 대한 참조나 순환 의존성과 같은 설정 문제를 컨테이너 로드 시점에 감지합니다. Spring은 빈이 실제로 생성될 때 가능한 한 늦게 프로퍼티를 설정하고 의존성을 해결합니다.
이는 올바르게 로드된 Spring 컨테이너가 나중에 객체를 요청할 때, 해당 객체나 그 의존성 중 하나를 생성하는 데 문제가 있으면 예외를 발생시킬 수 있음을 의미합니다. 예를 들어, 빈이 누락되었거나 잘못된 프로퍼티의 결과로 예외를 던지는 경우입니다.
일부 설정 문제의
잠재적으로 지연된 가시성 때문에, ApplicationContext 구현은 기본적으로
싱글톤 빈을 사전 인스턴스화합니다. 실제로 필요하기 전에 이러한 빈을
생성하는 데 약간의 초기 시간과 메모리 비용이 들지만,
ApplicationContext가 생성될 때 설정 문제를 발견할 수 있고,
나중이 아닙니다. 싱글톤 빈이 즉시 사전 인스턴스화되는 대신
지연(lazy) 초기화되도록 이 기본 동작을 여전히 오버라이드할 수 있습니다.
순환 의존성이 존재하지 않는다면, 하나 이상의 협력 빈이 의존 빈에 주입될 때 각 협력 빈은 의존 빈에 주입되기 전에 완전히 설정됩니다. 이는 빈 A가 빈 B에 의존성을 가지고 있다면, Spring IoC 컨테이너가 빈 A의 setter 메서드를 호출하기 전에 빈 B를 완전히 설정한다는 것을 의미합니다.
다시 말해, (사전 인스턴스화된 싱글톤이 아니라면) 빈이 인스턴스화되고, 의존성이 설정되며, configured init method나 InitializingBean callback method와 같은 관련 라이프사이클 메서드가 호출됩니다.
다음 예제는 setter-based DI를 위한 XML 기반 설정 메타데이터를 사용합니다. Spring XML 설정 파일의 일부분이 다음과 같이 몇 가지 빈 정의를 지정합니다:
1<bean id="exampleBean" class="examples.ExampleBean"> 2 <!-- setter injection using the nested ref element --> 3 <property name="beanOne"> 4 <ref bean="anotherExampleBean"/> 5 </property> 6 7 <!-- setter injection using the neater ref attribute --> 8 <property name="beanTwo" ref="yetAnotherBean"/> 9 <property name="integerProperty" value="1"/> 10</bean> 11 12<bean id="anotherExampleBean" class="examples.AnotherBean"/> 13<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
다음 예제는 해당하는 ExampleBean 클래스를 보여줍니다:
1public class ExampleBean { 2 3 private AnotherBean beanOne; 4 5 private YetAnotherBean beanTwo; 6 7 private int i; 8 9 public void setBeanOne(AnotherBean beanOne) { 10 this.beanOne = beanOne; 11 } 12 13 public void setBeanTwo(YetAnotherBean beanTwo) { 14 this.beanTwo = beanTwo; 15 } 16 17 public void setIntegerProperty(int i) { 18 this.i = i; 19 } 20}
1class ExampleBean { 2 lateinit var beanOne: AnotherBean 3 lateinit var beanTwo: YetAnotherBean 4 var i: Int = 0 5}
앞의 예제에서, setter는 XML 파일에 지정된 프로퍼티와 일치하도록 선언됩니다.
다음 예제는 constructor-based DI를 사용합니다:
1<bean id="exampleBean" class="examples.ExampleBean"> 2 <!-- constructor injection using the nested ref element --> 3 <constructor-arg> 4 <ref bean="anotherExampleBean"/> 5 </constructor-arg> 6 7 <!-- constructor injection using the neater ref attribute --> 8 <constructor-arg ref="yetAnotherBean"/> 9 10 <constructor-arg type="int" value="1"/> 11</bean> 12 13<bean id="anotherExampleBean" class="examples.AnotherBean"/> 14<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
다음 예제는 해당하는 ExampleBean 클래스를 보여줍니다:
1public class ExampleBean { 2 3 private AnotherBean beanOne; 4 5 private YetAnotherBean beanTwo; 6 7 private int i; 8 9 public ExampleBean( 10 AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { 11 this.beanOne = anotherBean; 12 this.beanTwo = yetAnotherBean; 13 this.i = i; 14 } 15}
1class ExampleBean( 2 private val beanOne: AnotherBean, 3 private val beanTwo: YetAnotherBean, 4 private val i: Int 5)
빈 정의에 지정된 생성자 인수는
ExampleBean의 생성자에 대한 인수로 사용됩니다.
이제 생성자를 사용하는 대신, Spring이 객체의 인스턴스를 반환하기 위해
static 팩터리 메서드를 호출하도록 지시하는 예제를 변형해서 고려해 보십시오:
1<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance"> 2 <constructor-arg ref="anotherExampleBean"/> 3 <constructor-arg ref="yetAnotherBean"/> 4 <constructor-arg value="1"/> 5</bean> 6 7<bean id="anotherExampleBean" class="examples.AnotherBean"/> 8<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
다음 예제는 해당하는 ExampleBean 클래스를 보여줍니다:
1public class ExampleBean { 2 3 // a private constructor 4 private ExampleBean(...) { 5 ... 6 } 7 8 // a static factory method; the arguments to this method can be 9 // considered the dependencies of the bean that is returned, 10 // regardless of how those arguments are actually used. 11 public static ExampleBean createInstance ( 12 AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { 13 14 ExampleBean eb = new ExampleBean (...); 15 // some other operations... 16 return eb; 17 } 18}
1class ExampleBean private constructor() { 2 companion object { 3 // a static factory method; the arguments to this method can be 4 // considered the dependencies of the bean that is returned, 5 // regardless of how those arguments are actually used. 6 @JvmStatic 7 fun createInstance(anotherBean: AnotherBean, yetAnotherBean: YetAnotherBean, i: Int): ExampleBean { 8 val eb = ExampleBean(...) 9 // some other operations... 10 return eb 11 } 12 } 13}
static 팩터리 메서드에 대한 인수는 생성자가 실제로 사용된 것과
정확히 동일하게 <constructor-arg/> 엘리먼트에 의해 제공됩니다.
팩터리 메서드가 반환하는 클래스의 타입은 static 팩터리 메서드를 포함하는
클래스와 동일한 타입일 필요는 없습니다(이 예제에서는 동일하지만).
인스턴스(비 static) 팩터리 메서드는 (class 속성 대신 factory-bean 속성을
사용하는 것을 제외하면) 본질적으로 동일한 방식으로 사용할 수 있으므로,
여기에서는 그 세부 사항을 논의하지 않습니다.
Dependencies
Dependencies and Configuration in Detail