Регистрация beans (прототип) во время выполнения в Spring
Просто нужно что-то оценивать сообществом. Ниже приведен фрагмент кода, который представляет собой простой factory, который создает экземпляры определенного типа. Метод зарегистрирует bean в контексте в качестве прототипа и вернет экземпляр. Это первый раз, когда я настраиваю beans во время выполнения. Могли бы вы любезно оценить и предоставить обратную связь? заранее спасибо.
package au.com.flexcontacts.flexoperations;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.AbstractApplicationContext;
import au.com.flexcontacts.exceptions.SyncClassCreactionError;
/**
* @author khushroo.mistry
* Class purpose: Simple Factory to create an
* instance of SynchroniseContactsService and register it in the Spring IoC.
*/
public final class FLEXSyncFactory implements ApplicationContextAware {
private static AbstractApplicationContext context;
/**
* @param username
* @param password
* @param syncType
* @return the correct service class
* @throws SyncClassCreactionError
* The method registers the classes dynamically into the Spring IoC
*/
public final SynchroniseContactsService createSyncService(String username, String password, SyncType syncType) throws SyncClassCreactionError {
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();
try {
//Register the bean in the IoC
BeanDefinition bdb = new GenericBeanDefinition();
bdb.setBeanClassName(syncType.getClassName());
bdb.setScope("prototype");
ConstructorArgumentValues constructor = bdb.getConstructorArgumentValues();
constructor.addIndexedArgumentValue(0, username);
constructor.addIndexedArgumentValue(1, password);
beanFactory.registerBeanDefinition(syncType.getInstanceName(), bdb);
//Return instance of bean
return (SynchroniseContactsService) beanFactory.getBean(syncType.getInstanceName());
} catch (Exception e) {
e.printStackTrace();
throw new SyncClassCreactionError("Error: Illegal Handler");
}
}
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
context = (AbstractApplicationContext) applicationContext;
}
}
FLEX Sync factory был сконфигурирован в контейнере IoC как однострочный. Поэтому для создания нового диспетчера синхронизации я делаю следующее:
flexSyncFactory.createSyncService(userName, password, SyncType.FULL);
Я использую Spring 3.1. Пожалуйста, просмотрите и предоставите ценную информацию.
С уважением.
Ответы
Ответ 1
Это чисто мое мнение, а не экспертное мнение:
Spring предоставляет два механизма пользовательской модификации контекста приложения - с помощью BeanFactoryPostProcessor, который позволяет модифицировать существующие определения bean или добавления новых определений bean и BeanPostProcessors, который позволяет модифицировать экземпляры bean (обертывать их вокруг прокси и т.д.).
Spring не предоставляет другого родного способа динамически добавлять определения bean или bean экземпляры во время выполнения, но, как вы это сделали, получив базовые bean factory экземпляры и добавив в bean Определения - один из способов. Он работает, но есть риски:
-
Что произойдет, если вы перезапишете существующее имя bean новым типом, как места, где этот bean уже обрабатывается. Кроме того, что произойдет, если существующее имя bean будет перезаписано совершенно другим типом!
-
Этот недавно зарегистрированный bean не будет иметь никаких полей, которые были бы автоматически добавлены и не будут также введены в другой beans, поэтому по существу bean factory просто выступает в качестве реестра для хранения bean, на самом деле не функциональность инъекций зависимостей!
-
если в контексте приложения вызывается refresh()
, тогда резервная копия bean factory будет перезаписана и будет создана новая, поэтому любые экземпляры bean, зарегистрированные в bean factory напрямую будет потерян.
Если цель заключается в создании beans, который был автопрограммирован Spring, я бы выбрал что-то вроде @Configurable. Если вышеприведенные риски приемлемы, ваш подход должен работать.
Ответ 2
Это сработало для меня:
http://random-thoughts-vortex.blogspot.com/2009/03/create-dynamically-spring-beans.html
Объявите один выделенный Spring контекст bean, который будет реализовывать интерфейсы ApplicationContextAware и BeanFactoryPostProcessor:
public class MyContextWrapper implements ApplicationContextAware,
BeanFactoryPostProcessor {
private ApplicationContext appContext;
private ConfigurableListableBeanFactory factory;
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory)
throws BeansException {
this.factory = factory;
}
public void setApplicationContext(ApplicationContext c)
throws BeansException {
this.appContext = c;
}
//setters and getters
}
Пусть Spring загрузит этот bean в его контекст, объявив bean в файле конфигурации XML:
<bean id="appContext" class="my.package.MyContextWrapper">
</bean>
Теперь этот bean может быть загружен в любой другой bean приложения посредством ссылки на него:
<bean id="myBeanFactory" class="my.package.MyBeanFactory">
<property name="springContext" ref="appContext">
</property>
</bean>
Используйте GenericBeanDefinition для загрузки определения bean:
BeanDefinitionRegistry registry = ((BeanDefinitionRegistry )factory);
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(MyBeanClass.class);
beanDefinition.setLazyInit(false);
beanDefinition.setAbstract(false);
beanDefinition.setAutowireCandidate(true);
beanDefinition.setScope("session");
registry.registerBeanDefinition("dynamicBean",beanDefinition);
Bean создается в области сеанса и будет храниться в сеансе пользователя. Кандидат с автопроводом свойств сообщает Spring, если зависимость bean, такая как параметр setter или getter или constructor, должна обрабатываться автоматически с помощью Spring. Свойство lazy init сообщает Spring, если этот bean должен быть создан при необходимости.
Чтобы получить дескриптор Spring bean, используйте контекст приложения Spring следующим образом:
Object bean=
getApplicationContext().getBean("dynamicBean");
if(bean instanceof MyBeanClass){
MyBeanClass myBean = (MyBeanClass) bean;
// do with the bean what ever you have to do.
}
Ответ 3
Ваше решение выглядит хорошо. Я считаю, что мы также можем достичь, создав bean реализацию интерфейсов BeanNameAware и FactoryBean, а затем установим значение перед созданием контекста.
xxxxBean.beansByName.put("synTable", synTable);
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
assert externalDataSource == context.getBean("synTable");
Вот реализация bean
public class xxxxBean implements BeanNameAware, FactoryBean {
public static Map<String, Object> beans = new HashMap<String, Object>();
private String beanName;
@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}
@Override
public Object getObject() {
return beans.get(beanName);
}
@Override
public Class<?> getObjectType() {
return beans.get(beanName).getClass();
}
@Override
public boolean isSingleton() {
return true;
}
}