Loading a List from a file in Spring Application Context
Have you ever wanted to load a List in your application context from a file? Perhaps you want to define a variable number of settings to be injected in, or in my case I wanted to pull in the Hibernate mapping files into an AnnotationSessionFactoryBean. Spring doesn't appear to have a way to do this out of the box, so here is how I have done it.
And that's about all. Hope this is of help to someone.
So you want to load a list of strings defined in a file called names.txt such as:
James Adam Peter PaulYour application context would look something like:
<bean id="resourceList" class="com.fizzpod.spring.ResourceListFactoryBean">
<property name="targetListClass">
<value>java.util.ArrayList</value>
</property>
<property name="locations">
<list>
<value>classpath:names.txt</value>
</list>
</property>
</bean>
The ResourceListFactoryBean is (obviously) a factory bean for creating a list from a Spring resource. Now this uses some of Springs magic to turn the classpath:names.txt into a spring Resource object. To take advantage of the normal ListFactoryBean by extending it so that you can define a list using the normal ListFactoryBean (why you would want to do that rather than using the util:list tag). Anyway the class looks like this:
/**
* Creates a List of values from the specified resources.
*/
public class ResourceListFactoryBean extends ListFactoryBean {
/**
* The List implementation class to create
*/
private Class targetListClass = null;
/**
* The resources to create the list from
*/
private Resource[] locations = null;
/**
* Flag for whether the source list is set
*/
private boolean sourceListSet = false;
@Override
/**
* Set the source List, just used to detect whether
* a sourceList is set
* @param sourceList the source list to create the list from.
*/
public void setSourceList(List sourceList) {
super.setSourceList(sourceList);
sourceListSet = true;
}
@Override
/**
* To allow the creation of the list class
* @param targetListClass the class of the list to instantiate
*/
public void setTargetListClass(Class targetListClass) {
super.setTargetListClass(targetListClass);
this.targetListClass = targetListClass;
}
/**
* Set a location of the list file to load.
* @param location the resource to load the settings f
*/
public void setLocation(Resource location) {
this.locations = new Resource[] {location};
}
/**
* Set locations of list files to be loaded.
* @param locations the locations of the resources
*/
public void setLocations(Resource[] locations) {
this.locations = locations;
}
@Override
@SuppressWarnings("unchecked")
/**
* Create a list from the resources, delegating the
* super class if any source list has been set.
* @return the list instantiated from the context definition
*/
protected List createInstance() {
List result = null;
if(this.sourceListSet) {
result = super.createInstance();
}
if(this.locations != null) {
List resourceList = null;
if(result == null) {
result = instantiateListClass();
}
Class valueType = null;
for(Resource resource: locations) {
List contents = this.resolveList(resource);
if (this.targetListClass != null) {
valueType = GenericCollectionTypeResolver.getCollectionType(this.targetListClass);
}
if (valueType != null) {
TypeConverter converter = getBeanTypeConverter();
for (Object elem : contents) {
result.add(converter.convertIfNecessary(elem, valueType));
}
} else {
result.addAll(contents);
}
}
}
if(result == null) {
throw new IllegalArgumentException("You must set either the sourceList, locations or location parameters");
} return result;
}
/**
* Instantiates the list class
* @return the list class as defined by the context
*/
private List instantiateListClass() {
List result;
if (this.targetListClass != null) {
result = (List) BeanUtils.instantiateClass(this.targetListClass);
} else {
result = new ArrayList();
}
return result;
}
@SuppressWarnings("unchecked")
/**
* Loads the resource into a list of strings
* @return the list of strings loaded from the resource
*/
private List resolveList(Resource resource) {
InputStream inStream = null;
List lines = null;
try {
inStream = resource.getInputStream();
lines = IOUtils.readLines(inStream);
} catch (IOException e) {
throw new IllegalArgumentException("Could not read resource " + resource.getDescription(), e);
} finally {
IOUtils.closeQuietly(inStream);
}
return lines;
}
}
Note that the above code relies on the apache commons io to parse and load the resource into a list. Also note that the some of the ListFactoryBean methods are overriden so that we can detect if the user is just defining a normal list. Why, well like I said above if you just want a list use the util:list tag, but by doing this in the above code it allows for a mixed list of items from a resource and sources defined in the xml to be merged together. The following example shows that and as a bonus how to define a single resource for example:
<bean id="resourceList" class="com.fizzpod.ResourceListFactoryBean">
<property name="targetListClass">
<value>java.util.ArrayList</value>
</property>
<property name="sourceList">
<list>
<value>Sarah</value>
<value>Helen</value>
<value>Debbie</value>
</list>
</property>
<property name="locations" value="classpath:names.txt"></property>
</bean>
And that's about all. Hope this is of help to someone.
Comments
Post a Comment