Подтвердить что ты не робот

Замените bean внутри контейнера spring во время выполнения

Предположим, что я определяю bean (например, BeanA) внутри контейнера Spring, и этот bean вводится в объект. (например, BeanAUser)

Во время выполнения можно ли использовать другой экземпляр bean для замены исходного BeanA внутри контейнера Spring? А также повторно вводит этот новый экземпляр bean в BeanAUser, чтобы заменить оригинальный BeanA?

4b9b3361

Ответ 1

Его можно легко достичь с помощью прокси. Создайте делегирующую реализацию вашего интерфейса и переключите объект, который он делегирует.

@Component("BeanA")
public class MyClass implements MyInterface {
  private MyInterface target;

  public void setTarget(MyInterface target) {
    this.target = target;
  }

  // now delegating implementation of MyInterface methods
  public void method1(..) {
    this.target.method1(..);
  }

  ..
}

Ответ 2

То, как я это сделаю, - это использовать систему, называемую заменой произвольного метода.

Создайте класс, реализующий org.springframework.beans.factory.support.MethodReplacer, это заставит вас создать такой метод

public Object reimplement(Object o, Method m, Object[] args) throws Throwable

Параметры означают следующее:

  • o - экземпляр bean, который вы заменяете методом
  • m - метод meta мы заменяем
  • args - аргументы метода, предоставленные (если есть)

Итак, я бы предположил, что ваш класс выглядит примерно так:

public BeanAUserHelper implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {

        if (some expression){
            return beanA;
        }
        else {
            return beanB;
        }
    }
}

В конфигурации bean вы затем инструктируете Spring заменить метод getBeanX() на BeanAUser так, чтобы

<!-- this is the bean who needs to get a different instance -->
<bean id="beanAUser" class="a.b.c.BeanAUser">
    <!-- arbitrary method replacement -->
    <replaced-method name="getBeanX" replacer="beanAUserHelper"/>
</bean>

<!-- this is your 'dynamic bean getter' -->
<bean id="beanAUserHelper" class="a.b.c.BeanAUserHelper"/>

Надеюсь, я правильно понял вашу проблему:)

Ответ 3

Spring представил новый RefreshScope для замены компонента во время выполнения. Внутренне прокси создается, как описано в ответе mrembisz.

@RefreshScope
@Component
public class MyBean { ... }

Ответ 4

Предполагая, что MyClass в ответе mrembisz не является окончательным, не нужно вручную внедрять шаблон декоратора, и его можно реализовать автоматически, используя BeanPostProcessor. Сначала определите интерфейс расширения для внедрения новой делегирующей реализации:

public interface Wrapper extends MyInterface {
    void setTarget(MyInterface target);
}

Затем создайте BeanPostProcessor, который переносит все реализации MyInterface в прокси CGLIB. Прокси действует как MyClass (который позволяет вводить его в поля типа MyClass) и Wrapper (что позволяет ему изменять цель). Прокси перенаправляет все исходные вызовы на MyClass target (который изначально установлен в значение, объявленное в Spring), вызов Wrapper.setTarget приводит к изменению цели.

@Component
public static class MyPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Object result = bean;
        if (bean instanceof MyInterface) {
            final MyInterface myInterface = (MyInterface)bean;
            Class<? extends MyInterface> clazz = myInterface.getClass();
            if (!isFinal(clazz.getModifiers())) {
                result = Enhancer.create(clazz, new Class[] {MyInterface.class}, new MethodInterceptor() {
                    private MyInterface target = myInterface;
                    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                        if (method.getName().equals("setTarget") && method.getDeclaringClass().equals(Wrapper.class) && method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(MyInterface.class)) {
                            this.target = (MyInterface)args[0];
                            return null;
                        } else {
                            Object result = proxy.invoke(this.target, args);
                            return result;
                        }
                    }
                });
            }
        }
        return result;
    }

}

Просто идея: define bean в Spring, поскольку это был обычный bean, настроить его после инициализации.

Ответ 5

Есть способы манипулирования контекстом spring до создания.

  • Способ заключается в использовании классов GenericApplicationContext и GenericBeanDefinition для управления контекстом. Следующий пример кода показал это решение:

    GenericApplicationContext context = new GenericApplicationContext();
    
    XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(context);
    xmlReader.loadBeanDefinitions(new ClassPathResource(original-context));
    BeanDefinitionRegistry registry = ((BeanDefinitionRegistry) context);
    
    GenericBeanDefinition myBean = new GenericBeanDefinition();
    myBean.setBeanClass(MyCustomClass.class);
    myBean.getPropertyValues().add("name", "My-Name");
    registry.registerBeanDefinition("my_bean_name", myBean);
    
    context.refresh();
    

с помощью этого кода фрагмента, который вы можете добавить или удалить или изменить beans до его создания.

  1. Второе решение использует механизм BeanPostProcessor в spring. Для деталей см. Этот url: http://www.roseindia.net/tutorial/spring/spring3/ioc/beanpostprocessor.html или Почему этот BeanPostProcessor необходим в дополнение к UserDetailsService в этом примере проверки подлинности spring 3.0?

Ответ 6

Вы пересекаете тонкую линию здесь. В основном вы пытаетесь поместить логику приложения в контейнер Spring. Избегайте программирования с вашими конфигурационными файлами и используйте Spring (или любые рамки DI) только для базовой проводки.

Прокси-предложение @mrembisz делает это для предпочтительного. Таким образом, логика приложения и конфигурация разделены.

Ответ 7

Другим простым подходом может быть использование Atomic Reference вашего класса в виде Бина вместо непосредственного использования класса. Затем вы можете внедрить эту атомную ссылку в любое место и обновить ее. Все остальные сервисы будут использовать последнюю версию вашего класса после того, как вы обновите ее в атомарной ссылке. И я думаю, что это имеет смысл.

Прочтите это для атомарного использования: fooobar.com/info/32880/...

Одним из недостатков этого подхода является потеря того, что пружина не может обрабатывать аннотации вашего класса. Например, вы не можете использовать @Cachable, @Async, @PreDestroy и другие. Но решение хорошо, когда вам просто нужен объект и его свойства.