I am writing a class that implements the following method:
public void run(javax.sql.DataSource dataSource);
Within this method, I wish to
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!
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.
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()?
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:
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);
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).
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!