背景
專案裡需要獲取一個excle檔案,然後對其裡的內容進行修改,這個檔案在jar包裡,怎麼嘗試都讀取不成功,但是覺得肯定可以做到,因為專案裡的配置檔案就可以讀取到,於是開始了探索之路。
報錯的程式碼
ExcelWriter excelWriter = EasyExcel.write("to.xlsx").withTemplate(t).build();
我想要成功呼叫以上的方法,需要讀取一個檔案的內容,然後寫入到另一個檔案中
withTemplate
的引數可以是String型別
的檔案路徑,File型別
和InputStream型別
的,具體如下:
現在的問題就是如何傳入一個正確的引數,使它正常工作
原先的寫法
有兩種獲取資源的方法,第一種就是Class.getResource()
,第二種就是ClassLoader.getResource()
於是有了如下的第一版寫法:
public class Main {
public static void main(String[] args) throws IOException {
URL url = Main.class.getResource("/Template.xlsx");
File file = new File(url.getFile());
ExcelWriter excelWriter = EasyExcel.write("to.xlsx").withTemplate(file).build();
excelWriter.finish();
}
}
檔案的位置如下:
本地測試是沒有問題的,但是專案需要打包,以jar包的形式執行,打包執行後就報如下的錯誤:
Caused by: java.io.FileNotFoundException: File 'file:/Users/xxx/spring-boot-study/target/classes/Template.xlsx' does not exist
定位到問題,如下:
編寫測試類
public class JarFileMain {
public static void main(String[] args) {
printFile(JarFileMain.class.getResource("/Template.xlsx"));
}
private static void printFile(URL url) {
if (url == null) {
System.out.println("null null");
return;
}
File file1 = new File(url.getFile());
System.out.println(file1 + " " + file1.exists());
File file2 = new File(url.toString());
System.out.println(file2 + " " + file2.exists());
}
}
直接這樣執行是沒有問題的,輸出結果如下:
/Users/xxx/spring-boot-study/target/classes/Template.xlsx true
file:/Users/xxx/spring-boot-study/target/classes/Template.xlsx false
但是打成jar包後,執行結果如下:
java -cp /Users/xxx/spring-boot-study/target/spring-boot-study-1.0-SNAPSHOT.jar cn.eagleli.java.resource.JarFileMain
file:/Users/xxx/spring-boot-study/target/spring-boot-study-1.0-SNAPSHOT.jar!/Template.xlsx false
jar:file:/Users/xxx/spring-boot-study/target/spring-boot-study-1.0-SNAPSHOT.jar!/Template.xlsx false
可以看出沒有打成包的時候,檔案是存在的;但是打包後執行時,檔案就不存在了。
找原因
因為專案裡的config.xml
檔案裡有一些配置資訊的,而且是可以讀取成功的,所以肯定有辦法讀取到內容的,於是就看了這部分的程式碼。
核心程式碼如下:
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
public class ClassPathConfigPropertySource implements PropertySource {
private XMLConfiguration config;
public ClassPathConfigPropertySource() {
try {
config = new XMLConfiguration(ClassPathConfigPropertySource.class.getClassLoader().getResource("config.xml"));
config.setReloadingStrategy(new FileChangedReloadingStrategy());
} catch (ConfigurationException e) {
}
}
@Override
public void setProperty(String key, Object value) {
if (config != null) {
config.setProperty(key, value);
}
}
@Override
public Object getProperty(String key) {
if (config != null) {
return config.getProperty(key);
}
return null;
}
}
可以看出,這個類的工作其實是XMLConfiguration
這個類來完成的,看看它的建構函式是如何初始化的
讀取檔案內容的核心程式碼如下:
public abstract class AbstractFileConfiguration extends BaseConfiguration implements FileConfiguration {
public void load(URL url) throws ConfigurationException
{
if (sourceURL == null)
{
if (StringUtils.isEmpty(getBasePath()))
{
// ensure that we have a valid base path
setBasePath(url.toString());
}
sourceURL = url;
}
// throw an exception if the target URL is a directory
File file = ConfigurationUtils.fileFromURL(url);
if (file != null && file.isDirectory())
{
throw new ConfigurationException("Cannot load a configuration from a directory");
}
InputStream in = null;
try
{
in = url.openStream();
load(in);
}
catch (ConfigurationException e)
{
throw e;
}
catch (Exception e)
{
throw new ConfigurationException("Unable to load the configuration from the URL " + url, e);
}
finally
{
// close the input stream
try
{
if (in != null)
{
in.close();
}
}
catch (IOException e)
{
getLogger().warn("Could not close input stream", e);
}
}
}
}
可見它沒有把URL
轉為File
,而是直接用的InputStream in = url.openStream();
最終程式碼
public class Main {
public static void main(String[] args) throws IOException {
URL url = Main.class.getResource("/Template.xlsx");
//File file = new File(url.getFile());
ExcelWriter excelWriter = EasyExcel.write("to.xlsx").withTemplate(url.openStream()).build();
excelWriter.finish();
}
}