Замените bean внутри контейнера spring во время выполнения
Предположим, что я определяю bean (например, BeanA) внутри контейнера Spring, и этот bean вводится в объект. (например, BeanAUser)
Во время выполнения можно ли использовать другой экземпляр bean для замены исходного BeanA внутри контейнера Spring? А также повторно вводит этот новый экземпляр bean в BeanAUser, чтобы заменить оригинальный BeanA?
Ответы
Ответ 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 до его создания.
- Второе решение использует механизм 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
и другие. Но решение хорошо, когда вам просто нужен объект и его свойства.