Method Injection

2024. 11. 14. 12:28Spring Framework/Spring IoC

🌱 Spring Method Injection 정리

Spring에서는 빈의 생명주기가 다를 때 발생하는 문제를 해결하기 위해 "메서드 주입(Method Injection)" 기능을 제공합니다.
일반적인 의존성 주입(Dependency Injection) 방식으로 해결할 수 없는 경우 Lookup Method Injection, Arbitrary Method Replacement 등의 기법을 활용할 수 있습니다.

1️⃣ Method Injection이란?

📌 기본 개념

🔹 싱글톤 빈이 프로토타입 빈을 참조할 때, 새로운 인스턴스를 주입받아야 하는 경우 발생하는 문제를 해결하는 방법
🔹 Spring 컨테이너가 런타임에 동적으로 메서드를 오버라이드하여, 새로운 프로토타입 빈을 반환하도록 구현
🔹 Spring이 제공하는 고급 기능 중 하나로, 특정한 경우에만 사용됨

2️⃣ Method Injection이 필요한 상황

Spring의 대부분의 빈은 싱글톤(Singleton)으로 관리됩니다.
하지만 때때로 싱글톤 빈이 프로토타입(Prototype) 빈을 사용해야 하는 상황이 발생할 수 있습니다.

🔥 문제 상황

  • 싱글톤 빈 A가 프로토타입 빈 B를 사용해야 한다고 가정
  • 싱글톤 빈 A는 한 번만 생성되므로, 프로토타입 빈 B도 한 번만 주입됨
  • 새로운 요청마다 새로운 B 인스턴스를 생성할 수 없음 → 문제 발생
@Component
public class SingletonBean {
    private final PrototypeBean prototypeBean;

    @Autowired
    public SingletonBean(PrototypeBean prototypeBean) {
        this.prototypeBean = prototypeBean;
    }

    public void usePrototype() {
        System.out.println("Using prototype: " + prototypeBean);
    }
}

싱글톤 빈은 한 번만 생성되므로 PrototypeBean도 한 번만 주입됨 → 새로운 인스턴스를 받을 수 없음

3️⃣ 해결 방법

방법 1: ApplicationContext를 활용한 Bean 조회 (비추천)

Spring 컨테이너를 직접 사용하여 필요할 때마다 새로운 빈을 가져오는 방식

@Component
public class CommandManager implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.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);
    }
}

📌 단점
Spring 프레임워크에 종속적 → 비즈니스 로직이 Spring API에 의존하게 됨
테스트 및 유지보수 어려움

🚀 Spring이 제공하는 더 나은 해결책: "Lookup Method Injection"

방법 2: Lookup Method Injection (@Lookup 사용)

  • Spring 컨테이너가 특정 메서드를 오버라이드하여, 새로운 프로토타입 빈을 반환하도록 만듦
  • 비즈니스 로직이 Spring API에 의존하지 않음 → 더 깔끔한 코드

📌 예제: @Lookup 활용

@Component
public abstract class CommandManager {

    public Object process(Map<String, Object> commandState) {
        Command command = createCommand(); // 새 Command 인스턴스 생성
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("command") // "command" 빈을 주입받음 (새로운 인스턴스 반환)
    protected abstract Command createCommand();
}

📌 @Configuration 설정

@Configuration
public class AppConfig {

    @Bean
    @Scope("prototype") // Command 빈을 프로토타입으로 설정
    public Command command() {
        return new ConcreteCommand();
    }

    @Bean
    public CommandManager commandManager() {
        return new CommandManager() {}; // 익명 서브클래스 생성
    }
}

📌 @Lookup을 활용한 방식의 특징
Spring API에 의존하지 않음ApplicationContextAware보다 훨씬 깔끔
필요할 때마다 새로운 프로토타입 빈을 반환
Spring이 자동으로 메서드를 오버라이드

방법 3: ObjectFactory 또는 Provider 사용

  • Spring이 제공하는 ObjectFactory 또는 Provider를 활용하여 프로토타입 빈을 생성
  • Spring 컨테이너를 직접 참조하지 않음 → ApplicationContextAware 방식보다 개선된 방법

📌 ObjectFactory 활용 예제

@Component
public class CommandManager {
    private final ObjectFactory<Command> commandFactory;

    @Autowired
    public CommandManager(ObjectFactory<Command> commandFactory) {
        this.commandFactory = commandFactory;
    }

    public Object process(Map<String, Object> commandState) {
        Command command = commandFactory.getObject(); // 새로운 인스턴스 반환
        command.setState(commandState);
        return command.execute();
    }
}

📌 Provider 활용 예제

@Component
public class CommandManager {
    private final Provider<Command> commandProvider;

    @Autowired
    public CommandManager(Provider<Command> commandProvider) {
        this.commandProvider = commandProvider;
    }

    public Object process(Map<String, Object> commandState) {
        Command command = commandProvider.get(); // 새로운 인스턴스 반환
        command.setState(commandState);
        return command.execute();
    }
}

📌 특징
Spring 컨테이너를 직접 참조하지 않음 → 코드가 더 깔끔하고 유연함
필요할 때마다 새로운 프로토타입 빈을 반환

4️⃣ Arbitrary Method Replacement (임의 메서드 대체)

  • 기존 빈의 특정 메서드를 다른 메서드로 대체하는 기능
  • 일반적인 DI보다 활용 빈도가 낮지만, 특수한 경우 사용 가능

예제: 기존 메서드 동적 대체

public class MyValueCalculator {
    public String computeValue(String input) {
        return "Original: " + input;
    }
}

📌 MethodReplacer 인터페이스를 사용하여 동적 메서드 대체

public class ReplacementComputeValue implements MethodReplacer {
    @Override
    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        return "Replaced: " + args[0];
    }
}

📌 XML 설정으로 기존 메서드 교체

<bean id="myValueCalculator" class="MyValueCalculator">
    <replaced-method name="computeValue" replacer="replacementComputeValue"/>
</bean>

<bean id="replacementComputeValue" class="ReplacementComputeValue"/>

📌 특징
런타임에 특정 메서드를 동적으로 변경 가능
프록시 패턴을 사용할 필요 없이 기존 메서드 로직을 교체 가능

🎯 Summary

Method Injection 활용 방법

방법 특징
ApplicationContextAware Spring 컨테이너에서 직접 getBean() 호출 (비추천)
@Lookup 컨테이너가 자동으로 메서드를 오버라이드하여 새로운 프로토타입 빈을 반환 (추천)
ObjectFactory / Provider 필요할 때마다 새로운 빈을 반환하도록 설정 (추천)
Arbitrary Method Replacement 기존 메서드를 런타임에 동적으로 교체

 

📌 Method Injection을 적절히 활용하면 유지보수성과 유연성이 높은 애플리케이션을 만들 수 있습니다! 🚀

 

출처 : https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-method-injection.html

 

Method Injection :: Spring Framework

In most application scenarios, most beans in the container are singletons. When a singleton bean needs to collaborate with another singleton bean or a non-singleton bean needs to collaborate with another non-singleton bean, you typically handle the depende

docs.spring.io

 

'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