tags:

views:

2308

answers:

5

I am writing a class that implements the following method:

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

Within this method, I wish to construct a Spring application context using a configuration file similar to the following:

<bean id="dataSource" abstract="true" />

<bean id="dao" class="my.Dao">
  <property name="dataSource" ref="dataSource" />
</bean>

Is it possible to force Spring to use the DataSource object passed to my method wherever the "dataSource" bean is referenced in the configuration file?

A: 

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

duffymo
Unfortunately, our code is not responsible for the creation of the data source in question. Therefore, it cannot be defined as a bean in the XML file. The data source is being provided to our code via another framework that schedules our code for execution.
Adam Paynter
+1  A: 

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).

Patrick
Thanks! I like your solution, it is quite elegant. Unfortunately, our code does require the data source during the initialization phase, so it makes things trickier. You have certainly piqued my interest, however, in the BeanFactoryPostProcessor interface. I will have to take a look at it!
Adam Paynter
+2  A: 

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!

Adam Paynter
Nice solution. I learn something new about Spring everyday.
Patrick
+2  A: 

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

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);
Neeme Praks
+1  A: 

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"&gt;

    <bean id="userObject" class="com.beanwiring.ObjectUsingDynamicallyAddedObject"
      >
      <constructor-arg ref="parameterObject" />

</bean>

</beans>

works perfect!

ruhsuzbaykus