복붙노트

[SPRING] Spring : 런타임시 인터페이스 구현을 변경하는 방법

SPRING

Spring : 런타임시 인터페이스 구현을 변경하는 방법

Java 개발자로서 필자는 종종 인터페이스의 여러 구현 중에서 선택해야합니다. 때로는이 선택을 한 번 수행 할 수 있지만 다른 경우에는 프로그램에서받는 다양한 입력에 따라 다른 구현이 필요합니다. 즉, 런타임에 구현을 변경할 수 있어야합니다. 이는 (사용자 입력을 기반으로 한) 일부 키를 적합한 인터페이스 구현에 대한 참조로 변환하는 도우미 객체를 통해 쉽게 달성 할 수 있습니다.

Spring을 사용하면 bean과 같은 객체를 디자인 할 수 있으며, 필요하다면 삽입 할 수 있습니다.

public class MyClass {

    @Autowired
    private MyHelper helper;

    public void someMethod(String someKey) {
        AnInterface i = helper.giveMeTheRightImplementation(someKey);
        i.doYourjob();
    }

}

자, 도우미를 어떻게 구현해야합니까? 이것을 시작해 보겠습니다.

@Service
public class MyHelper {

    public AnInterface giveMeTheRightImplementation(String key) {
        if (key.equals("foo")) return new Foo();
        else if (key.equals("bar")) return new Bar();
        else ...
    }

}

이러한 솔루션에는 몇 가지 결함이 있습니다. 최악의 경우 중 하나는 도우미로부터 반환 된 인스턴스가 컨테이너에 알려지지 않아서 종속성 주입의 이점을 얻을 수 없다는 사실입니다. 즉, 내가 Foo 클래스를 다음과 같이 정의하더라도 :

@Service
public class Foo {

    @Autowired
    private VeryCoolService coolService;

    ...

}

... MyHelper에서 반환 한 Foo 인스턴스에는 coolService 필드가 제대로 초기화되지 않습니다.

이를 피하기 위해 자주 제안되는 해결 방법은 각 가능한 구현을 도우미에 삽입하는 것입니다.

@Service
public class MyHelper {

    @Autowired
    private Foo foo;

    @Autowired
    private Bar bar;

    ...

    public AnInterface giveMeTheRightImplementation(String key) {
        if (key.equals("foo")) return foo;
        else if (key.equals("bar")) return bar;
        else ...
    }

}

그러나 나는 그런 해결책에 대한 큰 팬이 아니다. 나는 이렇게 우아하고 유지 보수가 잘되는 것을 찾는다.

@Service
public class MyHelper {

    @Autowired
    private ApplicationContext app;

    public AnInterface giveMeTheRightImplementation(String key) {
        return (AnInterface) app.getBean(key);
    }

}

이것은 Spring의 ApplicationContext를 기반으로합니다.

비슷한 해결책은 ServiceLocatorFactoryBean 클래스를 사용하는 것입니다.

public interface MyHelper {

    AnInterface giveMeTheRightImplementation(String key);

}

// Somewhere else, in Java config

@Bean
ServiceLocatorFactoryBean myHelper() {
    ServiceLocatorFactoryBean bean = new ServiceLocatorFactoryBean();
    bean.setServiceLocatorInterface(MyHelper.class);
    return bean;
}

그러나 저는 스프링 전문가가 아니기 때문에 더 나은 접근법이 있는지 궁금합니다.

해결법

  1. ==============================

    1.원하는 것을 수행하는 표준 방법은 다음과 같아야합니다.

    원하는 것을 수행하는 표준 방법은 다음과 같아야합니다.

    interface YourInterface {
        void doSomething();
    }
    
    public class YourClass {
    
        @Inject @Any Instance<YourInterface> anImplementation;
    
        public void yourMethod(String someInput) {
            Annotation qualifier = turnInputIntoQualifier(someInput);
            anImplementation.select(qualifier).get().doSomething();
        }
    
        private Annotation turnInputIntoQualifier(String input) {
            ...
        }
    
    }
    

    현재, 그러나 Spring은 그것을 지원하지 않는다. v5.x 용으로 계획되었습니다.) 그것은 작동해야합니다 응용 프로그램 서버.

    Spring을 고집하고 싶다면 ServiceLocatorFactoryBean 기반 솔루션 아마 최고의 것입니다.

  2. ==============================

    2.내 프로젝트에서이 접근법을 따른다. 이것은 절대 안전한 것은 아니지만 구성 코드가 매우 적어서 새로운 구현을 추가하는 측면에서 상당히 도움이됩니다.

    내 프로젝트에서이 접근법을 따른다. 이것은 절대 안전한 것은 아니지만 구성 코드가 매우 적어서 새로운 구현을 추가하는 측면에서 상당히 도움이됩니다.

    나는 이런 식으로 enum을 만든다.

    enum Mapper{
        KEY1("key1", "foo"),
        KEY2("key2", "bar")
        ;
    
        private String key;
        private String beanName;
    
        public static getBeanNameForKey(String key){
           // traverse through enums using values() and return beanName for key
        }
    }
    

    Foo와 Bar가 공통 인터페이스에서 구현한다고 가정 해 봅시다. 인터페이스라고 부르 자.

    class ImplFactory{
    
        @Autowired
        Map<String, AnInterface> implMap; // This will autowire all the implementations of AnInterface with the bean name as the key
    
        public AnInterface getImpl(string beanName){
                implMap.get(beanName);
        }
      }
    

    그리고 도우미 수업이 이렇게 보일거야.

    @Service
    public class MyHelper {
    
    @Autowired
    ImplFactory factory;
    
        public AnInterface giveMeTheRightImplementation(String key) {
    
            String beanName = Mapper.getBeanNameForKey(key);  
            factory.getImpl(beanName);
        }  
    }
    

    이 접근법의 장점 중 일부는,  1. 올바른 구현을 선택하는 데 시간이 오래 걸릴 수 있습니다.  2. 새 구현을 추가하려는 경우. 당신이해야만하는 일은 당신의 enum에 Mapper를 추가하는 것입니다 (당신의 새로운 Impl 클래스를 추가하는 것 외에는).  3. 원하는 impl 클래스에 대한 빈 이름을 구성 할 수도 있습니다 (스프링으로 기본 빈 이름을 지정하지 않으려는 경우). 이 이름은 팩토리 클래스의 맵 키입니다. 열거 형에서 사용해야하는 것.

    편집하다:    빈에 사용자 정의 이름을 지정하려면 스테레오 타입 어노테이션 중 하나의 value 속성을 사용할 수 있습니다. 예를 들어. Impl을 @Component 또는 @Service로 주석 처리 한 경우 @Component ( "myBeanName1") 또는 @Service ( "myBeanName2")를 수행하십시오.

  3. ==============================

    3.선언 할 때 bean의 이름을 지정할 수 있으며, 도우미는 주어진 유형의 bean을 리턴하도록 응용 프로그램 컨텍스트에 요청할 수 있습니다. Bean에 선언 된 범위를 기반으로 컨텍스트를 기반으로 단일 또는 다른 범위를 사용할 필요가 있거나 다시 사용할 수있는 경우 응용 프로그램 컨텍스트에서 새 인스턴스를 만들 수 있습니다. 이렇게하면 스프링 기능을 완전히 활용할 수 있습니다.

    선언 할 때 bean의 이름을 지정할 수 있으며, 도우미는 주어진 유형의 bean을 리턴하도록 응용 프로그램 컨텍스트에 요청할 수 있습니다. Bean에 선언 된 범위를 기반으로 컨텍스트를 기반으로 단일 또는 다른 범위를 사용할 필요가 있거나 다시 사용할 수있는 경우 응용 프로그램 컨텍스트에서 새 인스턴스를 만들 수 있습니다. 이렇게하면 스프링 기능을 완전히 활용할 수 있습니다.

    예를 들어

    @Service
    public class MyHelper {
       @Autowired
       ApplicationContext applicationContext;
    
       public AnInterface giveMeTheRightImplementation(String key) {
    
       return context.getBean(key);
    }
    
    }
    
    @Service("foo")
    public class Foo implements AnInterface {
    }
    
  4. from https://stackoverflow.com/questions/44000672/spring-how-to-change-interface-implementations-at-runtime by cc-by-sa and MIT license