Adding a Pre-constructed Bean to a Spring Application Context

后端 未结 6 1889
醉话见心
醉话见心 2020-12-24 02:24

I am writing a class that implements the following method:

public void run(javax.sql.DataSource dataSource);

Within this method, I wish to

相关标签:
6条回答
  • 2020-12-24 02:54

    The second solution causes an exception because of a refresh problem. A more elegant way will be adding objects to the context and then loading xml definitions using the xmlreader. Thus:

     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");
    

    and

        <?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>
    

    works perfect!

    0 讨论(0)
  • 2020-12-24 02:59

    There's a more elegant way in which you can use an external xml file and load it with file system resource then inject beans configured in it into your application context. Thus:

    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;
    
        }
    }
    

    where ${xmlConfigFileLocation} is a property specified in your application.properties file which points to the file location in your system thus:

    xmlConfigFileLocation="your-file-path-anywhere-in-your-system"
    

    and your xml file may contain:

    <?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>
    

    thus when your application starts spring loads the class and loads the bean into application context.

    Hope this helps someone.

    0 讨论(0)
  • 2020-12-24 03:08

    If you create an object by calling "new", it's not under the control of the Spring factory.

    Why not have Spring inject the DataSource into the object instead of passing it into run()?

    0 讨论(0)
  • 2020-12-24 03:16

    I have been in the exact same situation. As nobody proposed my solution (and I think my solution is more elegant), I will add it here for future generations :-)

    The solution consists of two steps:

    1. create parent ApplicationContext and register your existing bean in it.
    2. create child ApplicationContext (pass in parent context) and load beans from XML file

    Step #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
    

    Step #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);
    
    0 讨论(0)
  • 2020-12-24 03:16

    You can create a wrapper class for a DataSource that simply delegates to a contained 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
    }
    

    Then in you Spring context file you declare DataSourceWrapper and wire it into all your beans. Then in your method you get a reference to DataSourceWrapper and set the wrapped DataSource to the one passed in to your method.

    This all working is highly depended on what happens in your Spring context file when its being loaded. If a bean requires the DataSource to already be available when the context loads then you may have to write a BeanFactoryPostProcessor that alters the Spring context file as it loads, rather then doing things after the load (though perhaps a lazy-init could solve this issue).

    0 讨论(0)
  • 2020-12-24 03:17

    I discovered two Spring interfaces can be used to implement what I need. The BeanNameAware interface allows Spring to tell an object its name within an application context by calling the setBeanName(String) method. The FactoryBean interface tells Spring to not use the object itself, but rather the object returned when the getObject() method is invoked. Put them together and you get:

    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;
        }
    
    }
    

    The bean definition is now reduced to:

    <bean id="dataSource" class="PlaceholderBean" />
    

    The placeholder receives its value before creating the application context.

    public void run(DataSource externalDataSource) {
        PlaceholderBean.beansByName.put("dataSource", externalDataSource);
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        assert externalDataSource == context.getBean("dataSource");
    }
    

    Things appear to be working successfully!

    0 讨论(0)
提交回复
热议问题