2024. 11. 14. 12:28ㆍSpring Framework/Spring IoC
Method Injection
대부분의 애플리케이션 시나리오에서, 컨테이너 내의 대부분의 빈들은 싱글톤입니다. 싱글톤 빈이 다른 싱글톤 빈과 협력해야 하거나 비싱글톤 빈이 다른 비싱글톤 빈과 협력해야 할 때, 일반적으로 다른 하나의 빈을 속성으로 정의함으로써 의존성을 처리합니다. 문제는 빈 생명주기가 다를 때 발생합니다. 싱글톤 빈 A가 비싱글톤(프로토타입) 빈 B를 사용해야 한다고 가정해 보겠습니다. 컨테이너는 싱글톤 빈 A를 단 한 번만 생성하므로 속성을 설정할 기회도 한 번뿐입니다. 컨테이너는 필요할 때마다 빈 A에 새로운 빈 B 인스턴스를 제공할 수 없습니다.
해결책은 일부 제어의 역전을 포기하는 것입니다. ApplicationContextAware 인터페이스를 구현하여 빈 A가 컨테이너를 인식하게 하고, 빈 A가 필요할 때마다 컨테이너에 getBean("B") 호출을 하여 (일반적으로 새로운) 빈 B 인스턴스를 요청할 수 있습니다. 다음 예시는 이 접근 방법을 보여줍니다:
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* A class that uses a stateful Command-style class to perform
* some processing.
*/
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
package fiona.apple;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
// Command interface
interface Command {
void setState(Map<String, Object> state);
Object execute();
}
// Concrete Command implementation
class ConcreteCommand implements Command {
private Map<String, Object> state;
@Override
public void setState(Map<String, Object> state) {
this.state = state;
}
@Override
public Object execute() {
// Perform some operation based on the state
return "Command executed with state: " + state;
}
}
// CommandManager class
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map<String, Object> commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
return this.applicationContext.getBean("command", Command.class);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
// Main method to run the example
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
CommandManager commandManager = context.getBean(CommandManager.class);
// Creating a sample state map
Map<String, Object> state = Map.of("key1", "value1", "key2", "value2");
// Processing the command
Object result = commandManager.process(state);
System.out.println(result);
}
}
// Spring Configuration class
@Configuration
@ComponentScan("fiona.apple")
class AppConfig {
@Bean
public Command command() {
return new ConcreteCommand();
}
@Bean
public CommandManager commandManager() {
return new CommandManager();
}
}
// giveUpIoC 참조
앞서 언급된 방법은 바람직하지 않습니다. 왜냐하면 비즈니스 코드가 Spring 프레임워크를 인지하고 이에 종속되기 때문입니다. Spring IoC 컨테이너의 다소 고급 기능인 메소드 주입은 이러한 사용 사례를 깔끔하게 처리할 수 있게 합니다.
Lookup Method Injection
Lookup method injection은 컨테이너가 컨테이너 관리 빈의 메소드를 오버라이드하고 컨테이너 내의 다른 이름이 지정된 빈에 대한 조회 결과를 반환하는 능력입니다. 조회는 일반적으로 앞서 설명된 시나리오에서처럼 프로토타입 빈을 포함합니다. Spring 프레임워크는 CGLIB 라이브러리의 바이트코드 생성을 사용하여 메소드를 오버라이드하는 동적 서브클래스를 생성함으로써 이 메소드 인젝션을 구현합니다.
⦁ 이 동적 서브클래싱이 작동하려면, Spring 빈 컨테이너가 서브클래싱하는 클래스는 final이 아니어야 하며, 오버라이드될 메소드도 final이어서는 안됩니다.
⦁ 추상 메소드를 가진 클래스를 단위 테스트하는 것은 해당 클래스를 직접 서브클래싱하고 추상 메소드에 대한 스텁 구현을 제공해야 합니다.
⦁ 구체적인 메소드는 컴포넌트 스캐닝에도 필요한데, 이는 구체적인 클래스를 선택하는 데 필요합니다.
⦁ 더욱 중요한 제한 사항은, 조회 메소드가 팩토리 메소드와 특히 설정 클래스 내의 @Bean 메소드와 작동하지 않는다는 것인데, 이 경우 컨테이너가 인스턴스 생성을 담당하지 않기 때문에, 즉석에서 런타임 생성된 서브클래스를 생성할 수 없기 때문입니다.
이전 코드 스니펫에서의 CommandManager 클래스의 경우, Spring 컨테이너는 createCommand() 메소드의 구현을 동적으로 오버라이드합니다. CommandManager 클래스는 재작업된 예제에서 보여주듯이 Spring 의존성이 없습니다.
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
메소드가 주입될 클라이언트 클래스(이 경우 CommandManager)에서, 주입될 메소드는 다음과 같은 형식의 시그니처를 요구합니다:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
메서드가 추상적이면 동적으로 생성된 하위 클래스가 메서드를 구현합니다. 그렇지 않으면 동적으로 생성된 하위 클래스가 원래 클래스에 정의된 구체적 메서드를 오버라이드합니다. 다음 예를 고려하세요.
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandManager uses myCommand prototype bean -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
commandManager로 식별된 빈은 myCommand 빈의 새 인스턴스가 필요할 때마다 자체 createCommand() 메서드를 호출합니다. 실제로 필요한 경우 myCommand 빈을 프로토타입으로 배포하는 데 주의해야 합니다. 싱글톤인 경우 매번 동일한 myCommand 빈 인스턴스가 반환됩니다.
또는 다음 예제와 같이 어노테이션 기반 구성 메타데이터 모델 내에서 @Lookup 어노테이션을 통해 조회 메서드를 선언할 수 있습니다.
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
또는 보다 관용적으로, 조회 메서드의 선언된 리턴 타입에 대해 타겟 빈이 해결되는 것을 기대할 수 있습니다.
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
일반적으로 이러한 어노테이션이 달린 조회 메서드는 구체적인 스텁 구현으로 선언해야 하며, 이를 통해 추상 클래스가 기본적으로 무시되는 Spring의 구성 엘리먼트 스캐닝 규칙과 호환될 수 있습니다. 이 제한은 명시적으로 등록되거나 명시적으로 가져온 빈 클래스에는 적용되지 않습니다.
Scope가 다르게 지정된 타겟 빈에 액세스하는 또 다른 방법은 ObjectFactory/Provider 주입 지점입니다. 종속성으로서의 범위가 지정된 빈을 참조하세요.
ServiceLocatorFactoryBean(org.springframework.beans.factory.config 패키지에 있음)도 유용할 수 있습니다.
Arbitrary Method Replacement
조회 메서드 주입보다 덜 유용한 메서드 주입 형태는 관리되는 빈의 임의의 메서드를 다른 메서드 구현으로 대체하는 기능입니다. 이 기능이 실제로 필요할 때까지 이 섹션의 나머지 부분은 건너뛸 수 있습니다.
XML 기반 구성 메타데이터를 사용하면 배포된 빈에 대해 replace-method 엘리먼트를 사용하여 기존 메서드 구현을 다른 메서드 구현으로 대체할 수 있습니다. 다음 클래스를 고려해 보세요. 여기에는 오버라이드하려는 ComputeValue라는 메서드가 있습니다.
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
org.springframework.beans.factory.support.MethodReplacer 인터페이스를 구현하는 클래스는 다음 예제와 같이 새로운 메서드 정의를 제공합니다.
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
'Spring Framework > Spring IoC' 카테고리의 다른 글
Annotation-based Container Configuration (0) | 2024.11.14 |
---|---|
Bean Scopes (0) | 2024.11.14 |
Using depends-on, Lazy-initialized Beans, Autowiring Collaborators (0) | 2024.11.14 |
Dependencies (0) | 2024.11.14 |
Dependency Injection (0) | 2024.06.11 |