从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包中读取资源文件

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

复制JAR包中的文件到磁盘

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

Android将Activity打成jar包供第三方调用(解决资源文件不能打包的问题)

原地址:http://blog.csdn.net/xiaanming/article/details/9257853 最近有一个需要,我们公司做了一个apk客户端,然后其...

commons-configuration.jar读取资源文件

Apache Commons-configuration 学习笔记 博客分类: java ApacheXML工作  Apache Commons-configuration 学习笔记 ...

Android将Activity打成jar包供第三方调用(解决资源文件不能打包的问题)

 最近有一个需要,我们公司做了一个apk客户端,然后其他的公司可以根据自己的需要来替换里面的资源图片,文字等一些资源文件问题,我本来想这个简单,用两个工程直接替换里面的资源文件就行,老大说,这样...

android 开发,eclipse生成jar包,分离资源文件并混淆

sdk开发中,用eclipse进行开发带有资源文件的library库,封装混淆成jar包并分离出资源文件。1、预备工作设备:MacBook 环境:mac在这里我们采取的是所有的java类打包成jar...

将Activity打成jar包供第三方调用(解决资源文件不能打包的问题)

最近有一个需要,我们公司做了一个apk客户端,然后其他的公司可以根据自己的需要来替换里面的资源图片,文字等一些资源文件问题,我本来想这个简单,用两个工程直接替换里面的资源文件就行,老大说,这样子不好,...

android 生成jar包里面包含资源文件

只作收藏,原文:http://blog.sina.com.cn/s/blog_5da93c8f010108df.html android 关于生成jar包的问题 (2012-03-20 11:42:...

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

Java或者JAR包获取读取资源文件的路径的问题总结             这里分为具体两种: 第一种:资源文件为一般后缀文件 第二种:资源文件为图片文件 【N...

Android将Activity打成jar包供第三方调用(解决资源文件不能打包的问题)

转载注明地址:http://blog.csdn.net/xiaanming/article/details/9257853 最近有一个需要,我们公司做了一个apk客户端,然后其他的公司可以根据自...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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