从jar中拷贝资源文件

原创 2016年12月10日 15:18:26

why?

在代码中读取各种各样的资源文件对程序猿们来说是屡见不鲜的事情,对于集成环境下(eclipse、idea)这些代码也总是能运行得好好的,一点都不会让程序猿操心,因此很多人会就此打住。直到这些代码被打成jar包A,然后模块B依赖A时可能就会出现找不到资源文件等各种各样的问题。比如说,我们使用System.load来装载库文件的话,即便我们能得到存放资源文件的路径/D:/project/demo.jar!/resource/test.dll,但是这种路径并不能成功的加载。我们可能马上会想到以下的解决办法:
1、把配置文件拷贝一份到模块B里面来呗,简单粗暴。
2、把配置文件放在硬盘某一固定位置,代码里面直接写死路径(或者根据当前环境dev/beta/product读取对应的路径)呗,一劳永逸。
以上方案似乎能解决燃眉之急,然后程序又运行得好好的,一点问题都没有,瞥一眼电脑右下角,原来这么晚了,关机下班走人。。。

对于1方案,我们既然把它独立出来一个模块,肯定是有其原因的,比如这个A要被BCDEFG等模块依赖,难道要我们在BCDEFG等模块都放一份资源文件?如果资源文件经常变动,到时就要各个模块去更新文件,想想都觉得很可怕,有木有?
对于2方案,生产环境上的每台服务器都得丢一份文件上去,会不会有的服务器忘记呢?可能有人说我们用共享。不管怎样,当资源文件变了之后,项目发布之前,你依旧得去更新一下,很难说哪天真的忘记了…

综上,上面两种方案只能适合救急,绝非长久之计。要是程序运行时能自动把jar包里我们需要的资源文件拷贝到某一位置,这样代码就能正常的读取资源了。不管你有几个依赖模块A,不管你最终是war包,还是jar包,只管修改模块A的资源,发布项目的自动拷贝资源,这是就完全不需要额外的操作了。
java.net.URL和java.net.URLConnection类就可以帮我们拿到jar中的资源文件的输入流,然后我们把这个输入流写到指定位置就可以了。

how?

假设部署后模块A的路径在/usr/local/project/demo.jar

1、首先要确定的这个指定位置
getClass().getProtectionDomain().getCodeSource().getLocation()如果直接执行.class文件那么会得到当前class的绝对路径。如果封装在jar包里面执行jar包那么会得到当前jar包的绝对路径。

URL url = getClass().getProtectionDomain().getCodeSource().getLocation();
recourseFolder = java.net.URLDecoder.decode(url.getPath(), "utf-8");

此时我们得到的recourseFolder为/usr/local/project/demo.jar

if (recourseFolder.endsWith(".jar")) {
    recourseFolder = recourseFolder.substring(0, recourseFolder.lastIndexOf('/') + 1);
}

取它的前部分路径/usr/local/project/,此时我们得到的recourseFolder为/usr/local/project/

if (System.getProperty("os.name").toLowerCase().indexOf("linux") >= 0) {
    recourseFolder += EXT;
    /*其他你需要做的事情,比如只拷贝so文件,loadRecourseFromJar("filename.so")*/
} else {
    recourseFolder = recourseFolder.substring(1) + EXT;     
    /*其他你需要做的事情,比如只拷贝dll文件,loadRecourseFromJar("filename.dll")*/
}

因为得到的路径是以/开头的,如果是Windows环境则去掉开头的/,linux的路径就完整保留了,然后创建的EXT文件夹来存放待”释放”出来的资源文件,比如EXT=recourseFromJar,此时我们得到的recourseFolder为/usr/local/project/recourseFromJar。这个路径就是最终存放jar包里面资源文件的位置了。

2、开始拷贝资源文件到指定位置

    public void loadRecourseFromJar(String path) throws IOException {
        if (!path.startsWith("/")) {
            throw new IllegalArgumentException("The path has to be absolute (start with '/').");
        }

        if(path.endsWith("/")){
            throw new IllegalArgumentException("The path has to be absolute (cat not end with '/').");
        }

        int index = path.lastIndexOf('/');

        String filename = path.substring(index + 1);
        String folderPath = recourseFolder + path.substring(0, index + 1);

        // If the folder does not exist yet, it will be created. If the folder
        // exists already, it will be ignored
        File dir = new File(folderPath);
        if (!dir.exists()) {
            dir.mkdirs();
        }

        // If the file does not exist yet, it will be created. If the file
        // exists already, it will be ignored
        filename = folderPath + filename;
        File file = new File(filename);

        if (!file.exists() && !file.createNewFile()) {
            log.error("create file :{} failed", filename);
            return;
        }

        // Prepare buffer for data copying
        byte[] buffer = new byte[1024];
        int readBytes;

        // Open and check input stream
        URL url = getClass().getResource(path);
        URLConnection urlConnection = url.openConnection();
        InputStream is = urlConnection.getInputStream();

        if (is == null) {
            throw new FileNotFoundException("File " + path + " was not found inside JAR.");
        }

        // Open output stream and copy data between source file in JAR and the
        // temporary file
        OutputStream os = new FileOutputStream(file);
        try {
            while ((readBytes = is.read(buffer)) != -1) {
                os.write(buffer, 0, readBytes);
            }
        } finally {
            // If read/write fails, close streams safely before throwing an
            // exception
            os.close();
            is.close();
        }

    }

比如我们想”释放”/usr/local/project/demo.jar里面的myrecourse/aaa.txt文件,我们只需要

loadRecourseFromJar("/myrecourse/aaa.txt");

如果还需要myrecourse/bbb.txt,myrecourse/ccc.txt,你只需要多写两行代码

loadRecourseFromJar("/myrecourse/aaa.txt");
loadRecourseFromJar("/myrecourse/bbb.txt");
loadRecourseFromJar("/myrecourse/ccc.txt");

执行一下代码,你会发现/usr/local/project/recourseFromJar目录下多出一个文件夹myrecourse,而且myrecourse文件夹里面有aaa.txt、bbb.txt、ccc.txt三个文件。
到这里,革命已经成功了一大半。因为资源文件已经拷贝出来了,代码也能正常跑了,看似很完美。但是如果经常往myrecourse里面添加文件X,是不是每次都得为对应的文件添加一行代码loadRecourseFromJar(“/myrecourse/X”);这并不是我们所能接受的,我们需要的是能够拷贝整个文件夹下面的所有文件的工具。

3、拷贝目录下所有文件
其实就是文件夹的遍历问题了…直接上代码…

    public void loadRecourseFromJarByFolder(String folderPath) throws IOException {
        URL url = getClass().getResource(folderPath);
        URLConnection urlConnection = url.openConnection();
        if(urlConnection instanceof FileURLConnection){
            copyFileResources(url,folderPath);
        }else if(urlConnection instanceof JarURLConnection){
            copyJarResources((JarURLConnection)urlConnection);
        }
    }

    /**
     * 当前运行环境资源文件是在文件里面的
     * @param url
     * @param folderPath
     * @throws IOException
     */
    private void copyFileResources(URL url,String folderPath) throws IOException{
        File root = new File(url.getPath());
        if(root.isDirectory()){
            File[] files = root.listFiles();
            for (File file : files) {
                if (file.isDirectory()) {
                    loadRecourseFromJarByFolder(folderPath+"/"+file.getName());
                } else {
                    loadRecourseFromJar(folderPath+"/"+file.getName());
                }
            }
        }
    }

    /**
     * 当前运行环境资源文件是在jar里面的
     * @param jarURLConnection
     * @throws IOException
     */
    private void copyJarResources(JarURLConnection jarURLConnection) throws IOException{
        JarFile jarFile = jarURLConnection.getJarFile();
        Enumeration<JarEntry> entrys = jarFile.entries();
        while(entrys.hasMoreElements()){
            JarEntry entry = entrys.nextElement();
            if (entry.getName().startsWith(jarURLConnection.getEntryName()) && !entry.getName().endsWith("/")) {
                loadRecourseFromJar("/"+entry.getName());
            }
        }
        jarFile.close();
    }

现在我们想”释放”/usr/local/project/demo.jar里面的myrecourse文件夹下的所有文件,我们只需要这样

loadRecourseFromJarByFolder("/myrecourse");

执行一下代码,你会发现/usr/local/project/recourseFromJar目录下多出一个文件夹myrecourse,而且myrecourse文件夹里面有aaa.txt、bbb.txt、ccc.txt、X等N个文件。

when

资源的拷贝肯定不需要每次都去操作的,一般在项目启动时执行就行了,等你更新了项目代码,下次启动会自己更新资源文件,然后只需要返回存放资源文件的recourseFolder,其他地方去使用就可以了。比如还是System.load来装载库文件,我们就可以使用System.load(recourseFolder+”/resource/test.dll”);recourseFolder是固定的,”/resource/test.dll”也是固定的,因此每次上线根本不需要额外的去修改现有的代码,尽情的修改资源文件吧,I dont care…

the end

解决问题的方法很多种,你会选哪一种?
如果你的工具只有一柄铁锤,你就可能认为所有的问题都是铁钉。 ——马斯乐

版权声明:本文为博主原创文章,未经博主允许不得转载。

java中jar包内的类访问jar包内部的资源文件的路径问题

在本地项目中,若我们要访问项目中的资源文件,则一般使用相对路径或者用System.getProperities("user.dir")得到项目根目录,然后再访问资源文件,但是在将该工程和资源文件打包为...
  • mm_bit
  • mm_bit
  • 2015年12月21日 16:37
  • 12778

获取jar包内部的资源文件

这里记录了由于读取jar文件内部资源问题而引起的两个需求,一个可以通过类加载器的getResourceAsStream绕开,另一个可以利用类加载器的getResource("/")方法永远返回当前工程...

从jar包中读取资源文件

:【解惑】深入jar包:从jar包中读取资源文件 精华帖 (3) :: 良好帖 (15) :: 新手帖 (9) :: 隐藏帖 (0) 作者 正文 Heart...

java读取jar包中的资源文件或properties配置文件路径的方法

没打jar包之前,是通过 String rootPath = Thread.currentThread().getContextClassLoader().getResource("").get...
  • five824
  • five824
  • 2015年09月07日 12:16
  • 10239

Java或者JAR包获取读取资源文件的路径的问题总结

这里分为具体两种:  第一种:资源文件为一般后缀文件  第二种:资源文件为图片文件  【NO1】第一种    使用这行代码可以获取class类的根目录的路径    String path =...
  • jdsjlzx
  • jdsjlzx
  • 2013年11月23日 22:45
  • 24942

java 从jar包中读取资源文件

(转载)http://blog.csdn.net/b_h_l/article/details/7767829  在代码中读取一些资源文件(比如图片,音乐,文本等等),在集成环境(Eclipse)中运...

maven学习系列8----将resources目录下的文件打包到jar包外

maven默认情况下会把src/main/resources下的文件和class文件一起打到jar包内部,但是有很多场景下都需要把resources下的文件打包到jar包外面,这样修改resource...

复制JAR包中的文件到磁盘

最近写一个将DB2中数据备份到磁盘上ACCESS的备份工具,其中需要在开始备份的时候将一个模板ACCESS文件从项目资源路径中复制到用户选定的磁盘路径。         用MyEclipse写的代码...

java 从jar包中读取资源文件

在代码中读取一些资源文件(比如图片,音乐,文本等等),在集成环境(Eclipse)中运行的时候没有问题。但当打包成一个可执行的jar包(将资源文件一并打包)以后,这些资源文件找不到,如下代码: Ja...
  • B_H_L
  • B_H_L
  • 2012年07月20日 15:52
  • 38874

Java文件路径(getResource)

getResourceAsStream ()返回的是inputstream getResource()返回:URL Class.getResource("")    返回的是当前Class这个类所...
  • ak913
  • ak913
  • 2012年03月27日 15:16
  • 32554
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:从jar中拷贝资源文件
举报原因:
原因补充:

(最多只允许输入30个字)