Loading...
Spring Framework Reference Documentation 7.0.2의 Method Injection의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
대부분의 애플리케이션 시나리오에서, 컨테이너 안의 대부분 빈은 singletons입니다. 하나의 singleton 빈이 다른 singleton 빈과 협력해야 하거나 non-singleton 빈이 다른 non-singleton 빈과 협력해야 할 때, 일반적으로 한 빈을 다른 빈의 프로퍼티로 정의함으로써 의존성을 처리합니다.
빈 라이프사이클이 서로 다를 때 문제가 발생합니다. singleton 빈 A가 non-singleton (prototype) 빈 B를 사용해야 한다고 가정해 봅시다. 아마도 A에 대한 각 메서드 호출마다 그럴 수 있습니다. 컨테이너는 singleton 빈 A를 한 번만 생성하므로 프로퍼티를 설정할 기회도 한 번만 갖습니다. 컨테이너는 빈 A에 필요할 때마다 빈 B의 새 인스턴스를 제공할 수 없습니다.
해결책은 일부 inversion of control을 포기하는 것입니다.
ApplicationContextAware 인터페이스를 구현하여
빈 A를 컨테이너를 인지하도록 만들고,
A가 필요할 때마다 (일반적으로 새로운) 빈 B 인스턴스를 요청하기 위해
컨테이너에 getBean("B") 호출을 하도록
할 수 있습니다. 다음 예제는 이 접근 방식을 보여 줍니다:
1package fiona.apple; 2 3// Spring-API imports 4import org.springframework.beans.BeansException; 5import org.springframework.context.ApplicationContext; 6import org.springframework.context.ApplicationContextAware; 7 8/** 9 * A class that uses a stateful Command-style class to perform 10 * some processing. 11 */ 12public class CommandManager implements ApplicationContextAware { 13 14 private ApplicationContext applicationContext; 15 16 public Object process(Map commandState) { 17 // grab a new instance of the appropriate Command 18 Command command = createCommand(); 19 // set the state on the (hopefully brand new) Command instance 20 command.setState(commandState); 21 return command.execute(); 22 } 23 24 protected Command createCommand() { 25 // notice the Spring API dependency! 26 return this.applicationContext.getBean("command", Command.class); 27 } 28 29 public void setApplicationContext( 30 ApplicationContext applicationContext) throws BeansException { 31 this.applicationContext = applicationContext; 32 } 33}
1package fiona.apple 2 3// Spring-API imports 4import org.springframework.context.ApplicationContext 5import org.springframework.context.ApplicationContextAware 6 7// A class that uses a stateful Command-style class to perform 8// some processing. 9class CommandManager : ApplicationContextAware { 10 11 private lateinit var applicationContext: ApplicationContext 12 13 fun process(commandState: Map<*, *>): Any { 14 // grab a new instance of the appropriate Command 15 val command = createCommand() 16 // set the state on the (hopefully brand new) Command instance 17 command.state = commandState 18 return command.execute() 19 } 20 21 // notice the Spring API dependency! 22 protected fun createCommand() = 23 applicationContext.getBean("command", Command::class.java) 24 25 override fun setApplicationContext(applicationContext: ApplicationContext) { 26 this.applicationContext = applicationContext 27 } 28}
위의 방식은 비즈니스 코드가 Spring Framework를 인지하고 결합(coupled)되어 있기 때문에 바람직하지 않습니다. Method Injection은 Spring IoC 컨테이너의 다소 고급 기능으로, 이 유스 케이스를 깔끔하게 처리할 수 있게 해 줍니다.
Method Injection에 대한 동기를 이 블로그 글에서 더 읽어볼 수 있습니다.
Lookup method injection은 컨테이너에서 관리되는 빈의 메서드를 오버라이드하고 컨테이너 안에 있는 다른 이름의 빈에 대한 조회 결과를 반환하는 컨테이너의 능력입니다. 조회는 일반적으로 앞 절에서 설명한 시나리오와 같이 prototype 빈을 포함합니다. Spring Framework는 CGLIB 라이브러리로부터의 바이트코드 생성을 사용하여 메서드를 오버라이드하는 서브클래스를 동적으로 생성함으로써 이 method injection을 구현합니다.
- 이러한 동적 서브클래싱이 동작하려면, Spring 빈 컨테이너가 서브클래싱하는 클래스는
final일 수 없고, 오버라이드될 메서드 또한final일 수 없습니다.abstract메서드를 가진 클래스를 단위 테스트하려면 해당 클래스를 직접 서브클래싱하고abstract메서드에 대한 스텁 구현을 제공해야 합니다.- 또 다른 핵심 제약은 lookup 메서드가 팩터리 메서드와, 특히 설정 클래스 안의
@Bean메서드와 함께 동작하지 않는다는 점입니다. 이 경우에는 컨테이너가 인스턴스 생성을 담당하지 않으므로 런타임에 생성된 서브클래스를 즉석에서 만들 수 없습니다.
이전 코드 스니펫의 CommandManager 클래스의 경우, Spring 컨테이너는
createCommand() 메서드의 구현을 동적으로 오버라이드합니다. 다음의 수정된
예제에서 보듯이 CommandManager 클래스는 어떤 Spring 의존성도 갖지
않습니다:
1package fiona.apple; 2 3// no more Spring imports! 4 5public abstract class CommandManager { 6 7 public Object process(Object commandState) { 8 // grab a new instance of the appropriate Command interface 9 Command command = createCommand(); 10 // set the state on the (hopefully brand new) Command instance 11 command.setState(commandState); 12 return command.execute(); 13 } 14 15 // okay... but where is the implementation of this method? 16 protected abstract Command createCommand(); 17}
1package fiona.apple 2 3// no more Spring imports! 4 5abstract class CommandManager { 6 7 fun process(commandState: Any): Any { 8 // grab a new instance of the appropriate Command interface 9 val command = createCommand() 10 // set the state on the (hopefully brand new) Command instance 11 command.state = commandState 12 return command.execute() 13 } 14 15 // okay... but where is the implementation of this method? 16 protected abstract fun createCommand(): Command 17}
주입될 메서드를 포함하는 클라이언트 클래스(이 경우 CommandManager)에서,
주입될 메서드는 다음 형식의 시그니처가 필요합니다:
1<public|protected> [abstract] <return-type> theMethodName(no-arguments);
메서드가 abstract인 경우, 동적으로 생성된 서브클래스가 해당 메서드를
구현합니다. 그렇지 않으면, 동적으로 생성된 서브클래스가 원래 클래스에 정의된
구현 메서드를 오버라이드합니다. 다음 예제를 살펴보십시오:
1<!-- a stateful bean deployed as a prototype (non-singleton) --> 2<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype"> 3 <!-- inject dependencies here as required --> 4</bean> 5 6<!-- commandManager uses myCommand prototype bean --> 7<bean id="commandManager" class="fiona.apple.CommandManager"> 8 <lookup-method name="createCommand" bean="myCommand"/> 9</bean>
commandManager로 식별되는 빈은 myCommand 빈의 새 인스턴스가 필요할 때마다
자신의 createCommand() 메서드를 호출합니다. 실제로 필요한 것이 prototype이라면
myCommand 빈을 prototype으로 배포해야 합니다. 만약
singleton이라면,
매번 동일한 myCommand 빈 인스턴스가 반환됩니다.
또는 애노테이션 기반 컴포넌트 모델 내에서, 다음 예제에 보이듯이
@Lookup 애노테이션을 통해 lookup 메서드를 선언할 수 있습니다:
1public abstract class CommandManager { 2 3 public Object process(Object commandState) { 4 Command command = createCommand(); 5 command.setState(commandState); 6 return command.execute(); 7 } 8 9 @Lookup("myCommand") 10 protected abstract Command createCommand(); 11}
1abstract class CommandManager { 2 3 fun process(commandState: Any): Any { 4 val command = createCommand() 5 command.state = commandState 6 return command.execute() 7 } 8 9 @Lookup("myCommand") 10 protected abstract fun createCommand(): Command 11}
또는, 더 관용적인 방식으로, lookup 메서드의 선언된 반환 타입에 대해 대상 빈이 resolve되는 것에 의존할 수 있습니다:
1public abstract class CommandManager { 2 3 public Object process(Object commandState) { 4 Command command = createCommand(); 5 command.setState(commandState); 6 return command.execute(); 7 } 8 9 @Lookup 10 protected abstract Command createCommand(); 11}
1abstract class CommandManager { 2 3 fun process(commandState: Any): Any { 4 val command = createCommand() 5 command.state = commandState 6 return command.execute() 7 } 8 9 @Lookup 10 protected abstract fun createCommand(): Command 11}
서로 다른 스코프의 대상 빈에 접근하는 또 다른 방법은
ObjectFactory/Provider주입 지점을 사용하는 것입니다. Scoped Beans as Dependencies를 참조하십시오.org.springframework.beans.factory.config패키지에 있는ServiceLocatorFactoryBean도 유용할 수 있습니다.
lookup method injection보다 덜 유용한 method injection 형태는 관리되는 빈에서 임의의 메서드를 다른 메서드 구현으로 교체하는 기능입니다. 실제로 이 기능이 필요할 때까지 이 섹션의 나머지 부분은 건너뛰어도 됩니다.
XML 기반 설정 메타데이터에서는, 배포된 빈에 대해 기존 메서드
구현을 다른 것으로 교체하기 위해 replaced-method element를 사용할
수 있습니다. 다음 예제는 오버라이드하려는 computeValue라는 메서드를 가진
클래스를 보여 줍니다:
1public class MyValueCalculator { 2 3 public String computeValue(String input) { 4 // some real code... 5 } 6 7 // some other methods... 8}
1class MyValueCalculator { 2 3 fun computeValue(input: String): String { 4 // some real code... 5 } 6 7 // some other methods... 8}
org.springframework.beans.factory.support.MethodReplacer
인터페이스를 구현하는 클래스가 다음 예제에 보이듯이 새로운 메서드 정의를
제공합니다:
1/** 2 * meant to be used to override the existing computeValue(String) 3 * implementation in MyValueCalculator 4 */ 5public class ReplacementComputeValue implements MethodReplacer { 6 7 public Object reimplement(Object o, Method m, Object[] args) throws Throwable { 8 // get the input value, work with it, and return a computed result 9 String input = (String) args[0]; 10 ... 11 return ...; 12 } 13}
1/** 2 * meant to be used to override the existing computeValue(String) 3 * implementation in MyValueCalculator 4 */ 5class ReplacementComputeValue : MethodReplacer { 6 7 override fun reimplement(obj: Any, method: Method, args: Array<out Any>): Any { 8 // get the input value, work with it, and return a computed result 9 val input = args[0] as String; 10 ... 11 return ...; 12 } 13}
원래 클래스를 배포하고 메서드 오버라이드를 지정하는 빈 정의는 다음 예제와 비슷할 것입니다:
1<bean id="myValueCalculator" class="x.y.z.MyValueCalculator"> 2 <!-- arbitrary method replacement --> 3 <replaced-method name="computeValue" replacer="replacementComputeValue"> 4 <arg-type>String</arg-type> 5 </replaced-method> 6</bean> 7 8<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
<replaced-method/> element 안에서 하나 이상의 <arg-type/> element를 사용하여
오버라이드되는 메서드의 메서드 시그니처를 나타낼 수 있습니다. 메서드가
오버로드되어 클래스 안에 여러 variant가 존재하는 경우에만 argument에 대한
시그니처가 필요합니다.
편의를 위해 argument에 대한 타입 문자열은 완전
수식 타입 이름의 서브스트링일 수 있습니다. 예를 들어, 다음은 모두
java.lang.String과 일치합니다:
1java.lang.String 2String 3Str
argument 수만으로도 가능한 각 선택지를 구분하기에 충분한 경우가 많으므로, 이 shortcut은 argument 타입과 일치하는 가장 짧은 문자열만 입력하게 함으로써 많은 타이핑을 줄여 줄 수 있습니다.
Autowiring Collaborators
Bean Scopes