복붙노트

[SPRING] 스프링 빈을 동적으로 주입

SPRING

스프링 빈을 동적으로 주입

자바 스프링 웹 애플리케이션에서는 동적으로 콩을 주입 할 수 있기를 원합니다. 예를 들어, 나는 2 개의 다른 구현을 가진 인터페이스를 가지고있다 :

내 응용 프로그램에서 주입을 구성하는 몇 가지 속성 파일을 사용하고 있습니다 :

#Determines the interface type the app uses. Possible values: implA, implB
myinterface.type=implA

내 주사는 실제로 속성 파일의 속성 값을 조건부로 릴레이하여로드합니다. 예를 들어이 경우 myinterface.type = implA 어디서나 MyInterface를 삽입하면 삽입 될 구현은 ImplA가 될 것입니다. (조건 적 주석을 확장하여 구현했습니다).

런타임 중에 속성을 변경하면 서버를 다시 시작하지 않고 다음과 같이됩니다.

내 상황을 상쾌하게 생각했지만 문제가 생깁니다. 아마도 주입기에 setter를 사용하고 속성을 다시 구성하면 해당 setter를 다시 사용하는 것이 좋습니다. 그러한 요구 사항에 대한 실천이 있습니까?

어떤 아이디어?

최신 정보

필자는 두 가지 구현 (ImplA 및 ImplB)을 보유하고 관련 속성을 쿼리하여 올바른 것을 반환하는 factory / registry를 사용할 수 있다고 제안했습니다. 제가 그렇게한다면 저는 여전히 두 번째 도전, 즉 환경을 가지고 있습니다. 예를 들어 내 레지스트리가 다음과 같이 보이는 경우

@Service
public class MyRegistry {

private String configurationValue;
private final MyInterface implA;
private final MyInterface implB;

@Inject
public MyRegistry(Environmant env, MyInterface implA, MyInterface ImplB) {
        this.implA = implA;
        this.implB = implB;
        this.configurationValue = env.getProperty("myinterface.type");
}

public MyInterface getMyInterface() {
        switch(configurationValue) {
        case "implA":
                return implA;
        case "implB":
                return implB;
        }
}
}

속성이 변경되면 내 환경을 다시 주입해야합니다. 그 어떤 제안?

나는 env를 생성자 대신에 메서드 내에서 질의 할 수 있다는 것을 알고 있지만 이것은 성능 저하이며 또한 환경을 다시 주입하기 위해 (다시 말해 아마도 setter injection을 사용하는 것) 생각하고 싶습니다.

해결법

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

    1.나는이 작업을 가능한 한 간단하게 유지할 것이다. 조건부로 시작시 MyInterface 인터페이스의 조건부 구현을로드 한 다음 동일한 인터페이스의 다른 구현을 동적으로로드하는 이벤트를 실행하는 대신이 문제를 다른 방식으로 처리하고 구현하고 유지 관리하는 것이 훨씬 간단합니다.

    나는이 작업을 가능한 한 간단하게 유지할 것이다. 조건부로 시작시 MyInterface 인터페이스의 조건부 구현을로드 한 다음 동일한 인터페이스의 다른 구현을 동적으로로드하는 이벤트를 실행하는 대신이 문제를 다른 방식으로 처리하고 구현하고 유지 관리하는 것이 훨씬 간단합니다.

    우선, 가능한 모든 구현을로드합니다.

    @Component
    public class MyInterfaceImplementationsHolder {
    
        @Autowired
        private Map<String, MyInterface> implementations;
    
        public MyInterface get(String impl) {
            return this.implementations.get(impl);
        }
    }
    

    이 빈은 MyInterface 인터페이스의 모든 구현을위한 홀더입니다. 아무것도 여기에 마법, 그냥 일반적인 봄 autowiring 동작.

    이제 MyInterface의 특정 구현을 삽입해야하는 곳이면 인터페이스를 사용하여 구현할 수 있습니다.

    public interface MyInterfaceReloader {
    
        void changeImplementation(MyInterface impl);
    }
    

    그런 다음 구현 변경을 통보 받아야하는 모든 클래스에 대해 MyInterfaceReloader 인터페이스를 구현하게하십시오. 예를 들면 :

    @Component
    public class SomeBean implements MyInterfaceReloader {
    
        // Do not autowire
        private MyInterface myInterface;
    
        @Override
        public void changeImplementation(MyInterface impl) {
            this.myInterface = impl;
        }
    }
    

    마지막으로, MyInterface를 속성으로 가지는 모든 bean의 구현을 실제로 변경하는 bean이 필요하다.

    @Component
    public class MyInterfaceImplementationUpdater {
    
        @Autowired
        private Map<String, MyInterfaceReloader> reloaders;
    
        @Autowired
        private MyInterfaceImplementationsHolder holder;
    
        public void updateImplementations(String implBeanName) {
            this.reloaders.forEach((k, v) -> 
                v.changeImplementation(this.holder.get(implBeanName)));
        }
    }
    

    이것은 MyInterfaceReloader 인터페이스를 구현하는 모든 bean을 간단히 autowire하고 각각을 holder에서 가져 와서 인수로 전달 된 새로운 구현으로 갱신합니다. 다시 말하지만 일반적인 스프링 autowiring 규칙.

    구현을 변경하고자 할 때마다 새로운 구현의 bean 이름을 사용하여 updateImplementations 메소드를 호출해야합니다. 즉, 클래스의 하위 낙타 케이스 단순 이름, 즉 MyImplA 및 MyImplB 클래스의 myImplA 또는 myImplB입니다.

    시작할 때이 메소드를 호출하여 MyInterfaceReloader 인터페이스를 구현하는 모든 bean에 초기 구현을 설정해야합니다.

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

    2.나는 org.apache.commons.configuration.PropertiesConfiguration과 org.springframework.beans.factory.config.ServiceLocatorFactoryBean을 사용하여 유사한 문제를 해결했다 :

    나는 org.apache.commons.configuration.PropertiesConfiguration과 org.springframework.beans.factory.config.ServiceLocatorFactoryBean을 사용하여 유사한 문제를 해결했다 :

    VehicleRepairService가 인터페이스가되도록합시다.

    public interface VehicleRepairService {
        void repair();
    }
    

    CarRepairService 및 TruckRepairService는이를 구현하는 두 가지 클래스가 있습니다.

    public class CarRepairService implements VehicleRepairService {
        @Override
        public void repair() {
            System.out.println("repair a car");
        }
    }
    
    public class TruckRepairService implements VehicleRepairService {
        @Override
        public void repair() {
            System.out.println("repair a truck");
        }
    }
    

    나는 서비스 팩토리를위한 인터페이스를 만든다 :

    public interface VehicleRepairServiceFactory {
        VehicleRepairService getRepairService(String serviceType);
    }
    

    구성을 구성 클래스로 사용하십시오.

    @Configuration()
    @ComponentScan(basePackages = "config.test")
    public class Config {
        @Bean 
        public PropertiesConfiguration configuration(){
            try {
                PropertiesConfiguration configuration = new PropertiesConfiguration("example.properties");
                configuration
                        .setReloadingStrategy(new FileChangedReloadingStrategy());
                return configuration;
            } catch (ConfigurationException e) {
                throw new IllegalStateException(e);
            }
        }
    
        @Bean
        public ServiceLocatorFactoryBean serviceLocatorFactoryBean() {
            ServiceLocatorFactoryBean serviceLocatorFactoryBean = new ServiceLocatorFactoryBean();
            serviceLocatorFactoryBean
                    .setServiceLocatorInterface(VehicleRepairServiceFactory.class);
            return serviceLocatorFactoryBean;
        }
    
        @Bean
        public CarRepairService carRepairService() {
            return new CarRepairService();
        }
    
        @Bean
        public TruckRepairService truckRepairService() {
            return new TruckRepairService();
        }
    
        @Bean
        public SomeService someService(){
            return new SomeService();
        }
    }
    

    FileChangedReloadingStrategy를 사용하면 등록 정보 파일을 변경할 때 구성이 다시로드됩니다.

    service=truckRepairService
    #service=carRepairService
    

    서비스에 구성 및 팩토리가 있으면, 해당 특성의 현재 값을 사용하여 공장에서 적절한 서비스를 얻을 수 있습니다.

    @Service
    public class SomeService  {
    
        @Autowired
        private VehicleRepairServiceFactory factory;
    
        @Autowired 
        private PropertiesConfiguration configuration;
    
    
        public void doSomething() {
            String service = configuration.getString("service");
    
            VehicleRepairService vehicleRepairService = factory.getRepairService(service);
            vehicleRepairService.repair();
        }
    }
    

    희망이 도움이됩니다.

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

    3.내가 올바르게 이해한다면 주입 된 객체 인스턴스를 대체하는 것이 아니라 인터페이스 메소드 호출 중에 다른 구현을 사용하는 것이 런타임에 어떤 조건에 달려 있다는 것입니다.

    내가 올바르게 이해한다면 주입 된 객체 인스턴스를 대체하는 것이 아니라 인터페이스 메소드 호출 중에 다른 구현을 사용하는 것이 런타임에 어떤 조건에 달려 있다는 것입니다.

    그렇다면 ProxyFactoryBean과 함께 Sring TargetSource 메커니즘을 살펴볼 수 있습니다. 요점은 프록시 객체가 여러분의 인터페이스를 사용하는 빈에 주입되고 모든 인터페이스 메소드 호출이 TargetSource 타겟에 보내질 것이라는 점이다.

    아래의 예를 살펴보십시오.

    ConditionalTargetSource.java

    @Component
    public class ConditionalTargetSource implements TargetSource {
    
        @Autowired
        private MyRegistry registry;
    
        @Override
        public Class<?> getTargetClass() {
            return MyInterface.class;
        }
    
        @Override
        public boolean isStatic() {
            return false;
        }
    
        @Override
        public Object getTarget() throws Exception {
            return registry.getMyInterface();
        }
    
        @Override
        public void releaseTarget(Object target) throws Exception {
            //Do some staff here if you want to release something related to interface instances that was created with MyRegistry.
        }
    
    }
    

    applicationContext.xml

    <bean id="myInterfaceFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces" value="MyInterface"/>
        <property name="targetSource" ref="conditionalTargetSource"/>
    </bean>
    <bean name="conditionalTargetSource" class="ConditionalTargetSource"/>
    

    SomeService.java

    @Service
    public class SomeService {
    
      @Autowired
      private MyInterface myInterfaceBean;
    
      public void foo(){
          //Here we have `myInterfaceBean` proxy that will do `conditionalTargetSource.getTarget().bar()`
          myInterfaceBean.bar();
      }
    
    }
    

    또한 MyInterface 구현을 모두 Spring Bean으로 설정하고 Spring 컨텍스트에 두 인스턴스를 동시에 포함 할 수없는 경우 대상 구현 클래스에서 프로토 타입 대상 bean 범위 및 조건부 주석으로 ServiceLocatorFactoryBean을 사용하려고 할 수 있습니다. 이 접근법은 MyRegistry 대신 사용할 수 있습니다.

    추신 응용 프로그램 컨텍스트 새로 고침 작업도 원하는 작업을 수행 할 수 있지만 성능 오버 헤드와 같은 다른 문제가 발생할 수 있습니다.

  4. ==============================

    4.이것은 중복 된 질문 일 수도 있고 적어도 유사 할 수도 있습니다. 어쨌든 저는 이런 종류의 질문에 대답했습니다. 스프링 빈 부분 autowire 프로토 타입 생성자

    이것은 중복 된 질문 일 수도 있고 적어도 유사 할 수도 있습니다. 어쨌든 저는 이런 종류의 질문에 대답했습니다. 스프링 빈 부분 autowire 프로토 타입 생성자

    런타임시 의존성을 위해 다른 빈을 원할 때 프로토 타입 범위를 사용해야한다. 그런 다음 구성을 사용하여 프로토 타입 bean의 다른 구현을 리턴 할 수 있습니다. 자신을 되 돌리려면 구현 로직을 처리해야합니다 (두 개의 다른 싱글 톤 빈을 반환 할 수도 있습니다).하지만 새 빈을 원한다고 말하면 구현을 반환하는 로직은 SomeBeanWithLogic이라는 빈에 있습니다. .isSomeBooleanExpression (), 다음 구성을 만들 수 있습니다 :

    @Configuration
    public class SpringConfiguration
    {
    
        @Bean
        @Autowired
        @Scope("prototype")
        public MyInterface createBean(SomeBeanWithLogic someBeanWithLogic )
        {
            if (someBeanWithLogic .isSomeBooleanExpression())
            {
                return new ImplA(); // I could be a singleton bean
            }
            else
            {
                return new ImplB();  // I could also be a singleton bean
            }
        }
    }
    

    컨텍스트를 다시로드 할 필요가 없습니다. 예를 들어, 런타임시 bean의 구현을 변경하려면 위의 것을 사용하십시오. 이 bean이 singleton bean이나 뭔가 이상한 것의 생성자에서 사용 되었기 때문에 정말로 애플리케이션을 다시로드 할 필요가 있다면, 당신은 당신의 디자인을 다시 생각할 필요가있다. 컨텍스트를 다시로드하여 다른 런타임 동작을 달성하기 위해 싱글 톤 빈을 다시 작성하면 안됩니다. 즉, 필요하지 않습니다.

    편집이 답변의 첫 번째 부분은 콩을 동적으로 주입하는 것에 대한 질문에 대답했습니다. 묻는대로,하지만 질문은 더 많은 것 같아요 : '런타임에 싱글 톤 빈의 구현을 어떻게 바꿀 수 있습니까?' 프록시 디자인 패턴을 사용하여이를 수행 할 수 있습니다.

    interface MyInterface 
    {
        public String doStuff();
    }
    
    @Component
    public class Bean implements MyInterface
    {
        boolean todo = false; // change me as needed
    
        // autowire implementations or create instances within this class as needed
        @Qualifier("implA")
        @Autowired
        MyInterface implA;
    
        @Qualifier("implB")
        @Autowired
        MyInterface implB;
    
        public String doStuff()
        {
            if (todo)
            {
                return implA.doStuff();
            }
            else
            {
                return implB.doStuff();
            }
        }   
    }
    
  5. ==============================

    5.FileChangedReloadingStrategy는 프로젝트를 배포 조건에 크게 의존하게 만듭니다. WAR / EAR을 컨테이너로 분해해야하며 모든 상황에서 항상 충족되지 않는 조건이 파일 시스템에 직접 액세스해야합니다. 및 환경.

    FileChangedReloadingStrategy는 프로젝트를 배포 조건에 크게 의존하게 만듭니다. WAR / EAR을 컨테이너로 분해해야하며 모든 상황에서 항상 충족되지 않는 조건이 파일 시스템에 직접 액세스해야합니다. 및 환경.

  6. ==============================

    6.속성 값에 Spring @Conditional을 사용할 수 있습니다. 두 콩에 동일한 이름을 지정하면 단 하나의 인스턴스 만 생성되므로 작동됩니다.

    속성 값에 Spring @Conditional을 사용할 수 있습니다. 두 콩에 동일한 이름을 지정하면 단 하나의 인스턴스 만 생성되므로 작동됩니다.

    서비스 및 구성 요소에 @ 조건을 사용하는 방법에 대해 살펴보십시오. http://blog.codeleak.pl/2015/11/how-to-register-components-using.html

  7. from https://stackoverflow.com/questions/40105044/inject-spring-bean-dynamically by cc-by-sa and MIT license