본문 바로가기

SpringFramework Core - I. IoC 컨테이너/4. 의존성

4.6.1. 메서드 주입 검색

원문: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-lookup-method-injection

 

 

메서드 주입 검색은 메서드를 오버라이드하게 해주고, 다른 bean에 대한 검색 결과를 반환하게 해주는 컨테이너의 능력을 말한다. 검색은 보통 앞선 부분에서 서술했던 프로토타입 bean과 관련된다. 스프링 프레임워크는 CGLIB 라이브러리의 바이트 코드 생성을 사용하여 이러한 메서드 주입을 구현한다. 이를 통해 메서드를 오버라이드하는 서브클래스를 동적으로 생성한다.

 

  • 이 동적인 서브클래스가 작동하도록 하기 위해서는, 서브클래스가 final이어서도 안되고 오버라이드될 메소드가 final이어서도 안된다.
  • 추상 메서드를 가지고 있는 클래스를 단위 테스트할 때는 테스트 클래스를 서브클래스화하고 추상 메서드의 스텁 구현체를 제공해야 한다.
  • 구체적인 클래스를 찾아내는 컴포넌트 스캐닝에서도 구체적인 메서드가 필요하다.
  • 더 중요한 한계는, 검색 메서드가 팩토리 메서드와 함께 할 수 없다는 점이다. 특히 설정 클래스에 @Bean 어노테이션이 없는 메소드들은 컨테이너가 인스턴스를 생성할 책임이 없기 때문에 런타임 시에 서브클래스를 그때그때 만들어내지 못한다.

 

이전 코드에서 CommandManager 클래스의 경우에는, 스프링 컨테이너가 createCommand() 메서드의 구현체를 동적으로 오버라이드 했다. 수정된 다음의 예시가 보여주듯이 CommandManager 클래스는 스프링에 어떤 의존성도 가지고 있지 않다. 

package fiona.apple;

// 더 이상 스프링 import는 없다!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // 적절한 커맨드 인터페이스의 인스턴스를 생성
        Command command = createCommand();
        
        // 커맨트 인스턴스의 상태를 세팅
        command.setState(commandState);
        
        return command.execute();
    }
    
    // 음.. 이 메소드의 구현체는 어디 있는걸까?
    protected abstract Command createCommand();
}

주입될 메서드를 포함하고 있는 클라이언트 클래스(이 경우에는 CommandManager)에서, 주입될 메서드는 다음의 형식을 필요로 한다.

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

만약 추상 메소드라면,  동적으로 생성된 서브 클래스가 메소드를 구현한다. 하지만 추상 메소드가 아니라면, 동적으로 생성된 서브클래스는 구체적인 메소드를 오버라이드 한다. 다음 예시를 참고하자.

<!-- 정적인 bean이 (싱글턴이 아닌)프로토타입으로 설정된다 -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- 필요한 의존성들을 여기서 주입함 -->
</bean>

<!-- commandProcessor는 statefulCommandHelper를 사용한다 -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand" />
</bean>

commandManager라는 bean이 새로운 myCommand 인스턴스가 필요할 때마다 자신의 createCommand() 메소드를 호출한다. myCommand bean은 프로토타입으로 설정되어야 한다는 사실을 주의하라. 만약 싱글턴이면 매번 같은 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();
}

또는 좀 더 관용적으로는, 선언된 lookup 메서드의 반환 타입에 대상 bean이 매칭되도록 할 수도 있다.

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }
    
    @Lookup
    protected abstract MyCommand createCommand();
}

어노테이션이 붙은 lookup 메서드들은 구체적인 스텁 구현체와 함께 선언되어야 함을 주의하라. 추상 클래스가 기본적으로 무시되는 스프링의 컴포넌트 스캐닝과 양립가능해야하기 때문이다. 이러한 한계는 명확하게 등록되거나 import된 bean들에게는 적용되지 않는다.

 

 다른 범위의 bean에 접근하는 다른 방법은 'ObjectFactory/Provider' injection point이다. '의존성으로서의 Scoped Beans'를 참고하라.

또한 org.springframework.beans.factory.config 패키지 안의 ServiceLocatorFactoryBean도 유용할 것이다.