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

Добавление предварительно сконфигурированного 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 в файле конфигурации?

4b9b3361

Ответ 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 в контекст приложения.

Надеюсь, это поможет кому-то.