你知道怎麼從jar包裡獲取一個檔案的內容嗎

eaglelihh發表於2021-10-30

背景

專案裡需要獲取一個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();
    }
}

相關文章