Добавление предварительно сконфигурированного Bean в контекст приложения Spring
Я пишу класс, который реализует следующий метод:
public void run(javax.sql.DataSource dataSource);
В рамках этого метода я хочу построить контекст приложения Spring, используя файл конфигурации, подобный следующему:
<bean id="dataSource" abstract="true" />
<bean id="dao" class="my.Dao">
<property name="dataSource" ref="dataSource" />
</bean>
Можно ли заставить Spring использовать объект DataSource, переданный моему методу, где ссылается "dataSource" bean в файле конфигурации?
Ответы
Ответ 1
Я обнаружил, что два интерфейса Spring могут использоваться для реализации того, что мне нужно. Интерфейс BeanNameAware позволяет Spring сообщать объекту его имя в контексте приложения, вызывая setBeanName (String). Интерфейс FactoryBean сообщает Spring не использовать сам объект, а скорее объект, возвращенный, когда getObject(). Объедините их, и вы получите:
public class PlaceholderBean implements BeanNameAware, FactoryBean {
public static Map<String, Object> beansByName = new HashMap<String, Object>();
private String beanName;
@Override
public void setBeanName(String beanName) {
this.beanName = beanName;
}
@Override
public Object getObject() {
return beansByName.get(beanName);
}
@Override
public Class<?> getObjectType() {
return beansByName.get(beanName).getClass();
}
@Override
public boolean isSingleton() {
return true;
}
}
Определение bean теперь сводится к:
<bean id="dataSource" class="PlaceholderBean" />
Заполнитель получает свое значение перед созданием контекста приложения.
public void run(DataSource externalDataSource) {
PlaceholderBean.beansByName.put("dataSource", externalDataSource);
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
assert externalDataSource == context.getBean("dataSource");
}
Все работает успешно!
Ответ 2
Я был в той же ситуации. Поскольку никто не предлагал мое решение (и я думаю, что мое решение более изящно), я добавлю его здесь для будущих поколений: -)
Решение состоит из двух шагов:
- создать родительский ApplicationContext и зарегистрировать в нем существующий bean.
- создать дочерний ApplicationContext (передать в родительском контексте) и загрузить beans из файла XML
Шаг # 1:
//create parent BeanFactory
DefaultListableBeanFactory parentBeanFactory = new DefaultListableBeanFactory();
//register your pre-fabricated object in it
parentBeanFactory.registerSingleton("dataSource", dataSource);
//wrap BeanFactory inside ApplicationContext
GenericApplicationContext parentContext =
new GenericApplicationContext(parentBeanFactory);
parentContext.refresh(); //as suggested "itzgeoff", to overcome a warning about events
Шаг # 2:
//create your "child" ApplicationContext that contains the beans from "beans.xml"
//note that we are passing previously made parent ApplicationContext as parent
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] {"beans.xml"}, parentContext);
Ответ 3
Второе решение вызывает исключение из-за проблемы обновления. Более элегантным способом будет добавление объектов в контекст, а затем загрузка определений xml с помощью xmlreader.
Таким образом:
ObjectToBeAddedDynamically objectInst = new ObjectToBeAddedDynamically();
DefaultListableBeanFactory parentBeanFactory = new DefaultListableBeanFactory();
parentBeanFactory.registerSingleton("parameterObject", objectInst);
GenericApplicationContext parentContext = new GenericApplicationContext(parentBeanFactory);
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(parentContext);
xmlReader.loadBeanDefinitions(new FileSystemResource("beandefinitions.xml"));
parentContext.refresh();
ObjectUsingDynamicallyAddedObject userObjectInst= (ObjectUsingDynamicallyAddedObject )parentContext.getBean("userObject");
и
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userObject" class="com.beanwiring.ObjectUsingDynamicallyAddedObject"
>
<constructor-arg ref="parameterObject" />
</bean>
</beans>
работает отлично!
Ответ 4
Вы можете создать класс-оболочку для DataSource
, который просто делегирует содержащемуся DataSource
public class DataSourceWrapper implements DataSource {
DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
@Override
public Connection getConnection(String username, String password)
throws SQLException {
return dataSource.getConnection(username, password);
}
//delegate to all the other DataSource methods
}
Затем в <контекстном файле Spring вы объявляете DataSourceWrapper
и подключаете его ко всем своим beans. Затем в вашем методе вы получите ссылку на DataSourceWrapper и установите обернутый DataSource в тот, который был передан вашему методу.
Все это зависит от того, что происходит в вашем файле Spring при его загрузке. Если bean требует, чтобы DataSource уже был доступен при загрузке контекста, вам может потребоваться написать BeanFactoryPostProcessor
, который изменяет файл контекста Spring по мере его загрузки, а не делает вещи после загрузки (хотя, возможно, ленивый -init может решить эту проблему).
Ответ 5
Если вы создаете объект, вызывая "новый", он не находится под управлением Spring factory.
Почему бы не Spring вставить объект DataSource в объект вместо передачи его в run()?
Ответ 6
Там более элегантный способ использования внешнего XML файла и загрузка его с ресурсом файловой системы, а затем встраивание beans в него в контексте вашего приложения. Таким образом:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Service;
@Service
@Order(-100)
public class XmlBeanInitializationService implements ApplicationContextAware, InitializingBean {
private ApplicationContext applicationContext;
@Value("${xmlConfigFileLocation}")
private String xmlConfigFileLocation;
@Override
public void afterPropertiesSet() throws Exception {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader((BeanDefinitionRegistry)applicationContext);
reader.loadBeanDefinitions(new FileSystemResource(xmlConfigFileLocation));
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
где ${xmlConfigFileLocation} - свойство, указанное в вашем файле application.properties, которое указывает на расположение файла в вашей системе, таким образом:
xmlConfigFileLocation="your-file-path-anywhere-in-your-system"
и ваш XML файл может содержать:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.yourpackage.YourBean1Class"></bean>
<bean class="com.yourpackage.YourBean2Class"></bean>
<bean class="com.yourpackage.YourBean3Class"></bean>
</beans>
таким образом, когда ваше приложение запускает spring загружает класс и загружает bean в контекст приложения.
Надеюсь, это поможет кому-то.