복붙노트

[SPRING] Spring은 런타임시 bean 구현을 선택한다.

SPRING

Spring은 런타임시 bean 구현을 선택한다.

주석이있는 Spring Beans를 사용하고 있으며 런타임에 다른 구현을 선택해야합니다.

@Service
public class MyService {
   public void test(){...}
}

예를 들어 Windows 플랫폼의 경우 MyServiceWin을 확장해야합니다. Linux 플랫폼의 경우 MyServiceLnx가 MyService를 확장해야합니다.

현재로서는 나는 단지 하나의 끔찍한 해결책을 안다.

@Service
public class MyService {

    private MyService impl;

   @PostInit
   public void init(){
        if(windows) impl=new MyServiceWin();
        else impl=new MyServiceLnx();
   }

   public void test(){
        impl.test();
   }
}

XML 설정이 아닌 주석 만 사용하고 있다고 생각해주십시오.

해결법

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

    1.빈 주입을 다음과 같이 구성으로 이동할 수 있습니다.

    빈 주입을 다음과 같이 구성으로 이동할 수 있습니다.

    @Configuration
    public class AppConfig {
    
        @Bean
        public MyService getMyService() {
            if(windows) return new MyServiceWin();
            else return new MyServiceLnx();
        }
    }
    

    또는 프로파일 윈도우와 리눅스를 사용하고 서비스 구현에 @Profile ( "linux") 또는 @Profile ( "windows")과 같은 @Profile 주석을 달고 애플리케이션에이 프로필 중 하나를 제공 할 수 있습니다.

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

    2.

    public class LinuxCondition implements Condition {
      @Override
      public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return context.getEnvironment().getProperty("os.name").contains("Linux");  }
    }
    

    Windows와 동일합니다.

    @Configuration
    public class MyConfiguration {
       @Bean
       @Conditional(LinuxCondition.class)
       public MyService getMyLinuxService() {
          return new LinuxService();
       }
    
       @Bean
       @Conditional(WindowsCondition.class)
       public MyService getMyWindowsService() {
          return new WindowsService();
       }
    }
    
    @Service
    public class SomeOtherServiceUsingMyService {
    
        @Autowired    
        private MyService impl;
    
        // ... 
    }
    
  3. ==============================

    3.아름다운 설정을 만들어 봅시다.

    아름다운 설정을 만들어 봅시다.

    우리가 Animal 인터페이스를 가지고 있고 개와 고양이 구현이 있다고 상상해보십시오. 우리는 다음과 같이 쓰고 싶다.

    @Autowired
    Animal animal;
    

    어떤 구현이 반환되어야합니까?

    그래서 해결책은 무엇입니까? 문제를 푸는 방법은 여러 가지가 있습니다. @Qualifier와 Custom Conditions를 함께 사용하는 방법을 쓰겠습니다.

    그래서 먼저 모든 사용자 정의 주석을 작성하겠습니다.

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
    public @interface AnimalType {
        String value() default "";
    }
    

    및 구성 :

    @Configuration
    @EnableAutoConfiguration
    @ComponentScan
    public class AnimalFactoryConfig {
    
        @Bean(name = "AnimalBean")
        @AnimalType("Dog")
        @Conditional(AnimalCondition.class)
        public Animal getDog() {
            return new Dog();
        }
    
        @Bean(name = "AnimalBean")
        @AnimalType("Cat")
        @Conditional(AnimalCondition.class)
        public Animal getCat() {
            return new Cat();
        }
    
    }
    

    Bean의 이름은 AnimalBean입니다. 왜이 콩이 필요한거야? 왜냐하면 우리가 Animal 인터페이스를 삽입 할 때 우리는 단지 @Qualifier ( "AnimalBean")

    또한 커스텀 조건에 값을 전달하기 위해 커스텀 주석을 작성했습니다.

    이제 우리의 조건은 다음과 같습니다 ( "Dog"이름은 설정 파일 또는 JVM 매개 변수 또는 ...)

       public class AnimalCondition implements Condition {
    
        @Override
        public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
            if (annotatedTypeMetadata.isAnnotated(AnimalType.class.getCanonicalName())){
               return annotatedTypeMetadata.getAnnotationAttributes(AnimalType.class.getCanonicalName())
                       .entrySet().stream().anyMatch(f -> f.getValue().equals("Dog"));
            }
            return false;
        }
    }
    

    그리고 마지막으로 주사 :

    @Qualifier("AnimalBean")
    @Autowired
    Animal animal;
    
  4. ==============================

    4.모든 구현을 @Qualifier 주석이있는 팩토리에 자동으로 가져온 다음 팩토리에서 필요한 서비스 클래스를 반환하십시오.

    모든 구현을 @Qualifier 주석이있는 팩토리에 자동으로 가져온 다음 팩토리에서 필요한 서비스 클래스를 반환하십시오.

    public class MyService {
        private void doStuff();
    }
    

    내 Windows 서비스 :

    @Service("myWindowsService")
    public class MyWindowsService implements MyService {
    
        @Override
        private void doStuff() {
            //Windows specific stuff happens here.
        }
    }
    

    My Mac 서비스 :

    @Service("myMacService")
    public class MyMacService implements MyService {
    
        @Override
        private void doStuff() {
            //Mac specific stuff happens here
        }
    }
    

    내 공장 :

    @Component
    public class MyFactory {
        @Autowired
        @Qualifier("myWindowsService")
        private MyService windowsService;
    
        @Autowired
        @Qualifier("myMacService")
        private MyService macService;
    
        public MyService getService(String serviceNeeded){
            //This logic is ugly
            if(serviceNeeded == "Windows"){
                return windowsService;
            } else {
                return macService;
            }
        }
    }
    

    정말 까다로워 지길 원하면 enum을 사용하여 구현 클래스 유형을 저장 한 다음 enum 값을 사용하여 반환 할 구현을 선택하십시오.

    public enum ServiceStore {
        MAC("myMacService", MyMacService.class),
        WINDOWS("myWindowsService", MyWindowsService.class);
    
        private String serviceName;
        private Class<?> clazz;
    
        private static final Map<Class<?>, ServiceStore> mapOfClassTypes = new HashMap<Class<?>, ServiceStore>();
    
        static {
            //This little bit of black magic, basically sets up your 
            //static map and allows you to get an enum value based on a classtype
            ServiceStore[] namesArray = ServiceStore.values();
            for(ServiceStore name : namesArray){
                mapOfClassTypes.put(name.getClassType, name);
            }
        }
    
        private ServiceStore(String serviceName, Class<?> clazz){
            this.serviceName = serviceName;
            this.clazz = clazz;
        }
    
        public String getServiceBeanName() {
            return serviceName;
        }
    
        public static <T> ServiceStore getOrdinalFromValue(Class<?> clazz) {
            return mapOfClassTypes.get(clazz);
        }
    }
    

    그런 다음 공장에서 응용 프로그램 컨텍스트로 이동하여 인스턴스를 자체 맵으로 가져올 수 있습니다. 새로운 서비스 클래스를 추가 할 때 열거 형에 다른 항목을 추가하기 만하면됩니다.

     public class ServiceFactory implements ApplicationContextAware {
    
         private final Map<String, MyService> myServices = new Hashmap<String, MyService>();
    
         public MyService getInstance(Class<?> clazz) {
             return myServices.get(ServiceStore.getOrdinalFromValue(clazz).getServiceName());
         }
    
          public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
              myServices.putAll(applicationContext.getBeansofType(MyService.class));
          }
     }
    

    이제 원하는 클래스 유형을 팩토리에 전달하면 필요한 인스턴스를 다시 제공 할 수 있습니다. 특히 서비스를 일반화하려는 경우 매우 유용합니다.

  5. from https://stackoverflow.com/questions/34350865/spring-choose-bean-implementation-at-runtime by cc-by-sa and MIT license