Reputation: 339
I have a simple Java Spring IO project where a class should read from a csv file and for each record read, the parameters are stored into a list of account objects. I'm using Force IDE Luna and the Class CsvAccountDao that reads the file is in the same package as the csv file which is defined in the first bean of the xml file. The xml file is also located under the same package. Here is the bean file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://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-3.1.xsd">
<bean id="accountDao"
class="com.springinpractice.ch01.model.dao.csv.CsvAccountDao">
<property name="csvResource" value="accounts.csv"></property>
</bean>
<bean id="accountService"
class="com.springinpractice.ch01.service.AccountService">
<property name="accountDao" ref="accountDao"</property>
</bean>
</beans>
and here is the class file CscAccountDao:
package com.springinpractice.ch01.model.dao.csv;
import java.io.BufferedReader;
import java.io.FileReader;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.springframework.core.io.Resource;
import com.springinpractice.ch01.model.Account;
import com.springinpractice.ch01.model.dao.AccountDao;
public class CsvAccountDao implements AccountDao {
private Resource csvResource;
public CsvAccountDao() {
// TODO Auto-generated constructor stub
}
public void setCsvResource(Resource csvFile){
this.csvResource = csvFile;
}
@Override
public List<Account> findAll() throws Exception {
List<Account> results = new ArrayList<Account>();
DateFormat fmt = new SimpleDateFormat("MMddyyyy");
BufferedReader br = new BufferedReader(
new FileReader(csvResource.getFile()));
String line;
while((line = br.readLine()) != null){
String[] fields = line.split(",");
String accountNo = fields[0];
BigDecimal balance = new BigDecimal(fields[1]);
Date lastPaidOn = fmt.parse(fields[2]);
Account account =
new Account(accountNo, balance, lastPaidOn);
results.add(account);
}
br.close();
return results;
}
}
Note where method setCsvResource assigns the csv file to the resource object is where the exception problem begins. I get an exception error in the stack trace that says:
Jun 20, 2015 7:59:42 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@177e4b0: defining beans [accountDao,accountService]; root of factory hierarchy
Exception in thread "main" java.io.FileNotFoundException: class path resource [accounts.csv] cannot be resolved to URL because it does not exist
Q) Is there a problem where I would have to updated the XML file where the first bean uses accounts.csv by adding a full path? Since the both the class,xml, and the csv files are within the same package I thought I didn't need to be more specific.
Upvotes: 1
Views: 5891
Reputation: 3273
Some background on resource loading.
In Java, to load a classpath resource, you call getResource
or getResourceAsStream
on a classloader with a resource name. A resource name is:
The name of a resource is a '/'-separated path name that identifies the resource.
So to get a resource from a package you use:
classloader.getResource("org/com/resource.jpg")
A java class also has convenience methods to load resources from it's class loader. If you use ClassA.class.getResource("resource.jpg")
, it will prefix the '/'-joined package of ClassA to "resource.jpg" and delegate to the classloader. If your resource string starts with a '/' it is interpreted as an absolute path, the starting '/' is removed and then it delegates to the classloader.
So then we get to spring where you are supplying accounts.csv
as a value to be converted into a resource. Spring uses DefaultResourceLoader
to find your resource. Here is the logic it uses to find your resource:
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
In your case it falls all the way through to the catch(MalformedURLException ex)
and then tries to look it up as a classpath resource. When the ClasspathResource is eventually asked for an URL or input stream it passes "account.csv"
to the classloader. The classloader is looking for it in the root of the classpath.
So in your spring xml you need to tell it what package it is in. It will not be relative to the xml. So change it to "/package/account.csv" or "classpath:/package/account.csv"
For reference: I'm looking at Spring 4.0.9.RELEASE and oracle jdk8.
Upvotes: 2