java:读取jar包中配置文件的几种方式

概述

在编程的某些情况下,我们需要读取jar包中的文件,这种情况要区别于我们平时使用类加载器读取配置文件,这个时候配置在jar包中,就能读取到,但是配置文件也可以不在jar包中,只要放在Class-Path下就行了,所以这种情况下,我更愿意把它称之为:读取Class-Path下的配置文件。而我今天描述的比较明确,就是要读取jar包中的文件。这种需求可能不多,但是我碰见了,并且发现了几种,今天全部罗列分享一下。
目前有3种:

  1. JarFile
  2. URL
  3. ClassLoader

定义接口

因为有好几种方式,那就直接定义个接口:

public interface JarReader {
   /**
     * 读取jar包中的文件
     * @param jarPath   jar包路径
     * @param file  jar包中的文件路径
     * @return  文件内容,转换成字符串了,其它需求也可以转换成输入流。
     * @throws IOException
     */
    String readFromJar(String jarPath,String file) throws IOException;
}

jar包读取器,jar包中的文件读取出来。

通过JarFile读取

JarFile是java自带的一种读取jar包的API,很多人应该用过,我就直接贴代码了。

public class JarFileJarReader implements JarReader {

    @Override
    public String readFromJar(String jarPath,String file) throws IOException {
        JarFile jarFile=null;
        try {
            jarFile=new JarFile(jarPath);
            JarEntry jarEntry=jarFile.getJarEntry(file);
            InputStream input=jarFile.getInputStream(jarEntry);
            return IOUtils.toString(input,"UTF-8");
        } catch (IOException e) {
            throw e;
        } finally {
            IOUtils.closeQuietly(jarFile);
        }
    }
}

代码也比较简单,重点就是最后一定要把jarFile这个对象关闭一下,中间的输入流都可以不用关闭。

不过我在写这段代码之前,从我的个人经验上来说,JarFile好像更多是用来读取清单文件(MANIFEST.MF)的,可能是见这种情况比较多,当然它的用途肯定远不止如此。
因此我顺便写了一下读取清单文件的代码:

public void getManiFest(String jarPath) throws IOException {
        JarFile jarFile=null;
        try {
            jarFile=new JarFile(jarPath);
            Manifest manifest=jarFile.getManifest();
            if (manifest!=null){
                //获取Class-Path
                String classPaths = (String) manifest.getMainAttributes().get(new Attributes.Name("Class-Path"));
                if (classPaths != null && !classPaths.isEmpty()) {
                    String[] classPathArray = classPaths.split(" ");

                }
                //获取JDK版本
                String jdkVersion = (String) manifest.getMainAttributes().get(new Attributes.Name("Build-Jdk"));

                //还可以获取其它内容,比如Main-Class等等
            }

        } catch (IOException e) {
            throw e;
        } finally {
            IOUtils.closeQuietly(jarFile);
        }
    }

通过URL读取

java自带的URL是支持读取jar中的文件的,协议是jar,表示方式的话是用"/!"把jar包和文件区分一下。代码如下:

public class URLJarReader implements JarReader {
    @Override
    public String readFromJar(String jarPath, String file) throws IOException {
        JarURLConnection jarURLConnection=null;
        try {
            URL fileUrl=ParseUtil.fileToEncodedURL(new File(jarPath));
            URL jarUrl=new URL("jar", "", -1, fileUrl + "!/");
            URL moduleUrl = new URL(jarUrl, ParseUtil.encodePath(file, false));
            jarURLConnection = (JarURLConnection)moduleUrl.openConnection();
            return IOUtils.toString(jarURLConnection.getInputStream(),"UTF-8");
        } catch (IOException e) {
            throw e;
        } finally {
            if (jarURLConnection!=null){
                try {
                    jarURLConnection.getJarFile().close();
                } catch (IOException ignore) {
                }
            }
        }
    }
}

ParseUtil的几个方法是我在看java源码的时候看见的,用来处理一些不规则的文件路径。
我刚开始用URL的时候,就出现了一个内存泄漏的文件,读取完了以后,jar包被占用,死活不能删除,刚才开始把输入流给关闭了,也没有用。然后想到了类加载器里面有close方法,然后去看了一下,找到了上述代码中的finally块的代码。这样就可以把占用问题解决了,仔细看的话,会发现,getJarFile.close(),因此本质上还是关闭了JarFile,和上面是一样的。

通过ClassLoader

这个也是借鉴了我们平时读取配置文件的方式,借用一下ClassLoader来读取。

public class ClassLoaderJarReader implements JarReader {
    @Override
    public String readFromJar(String jarPath, String file) throws IOException{
        URLClassLoader urlClassLoader=null;
        try {
            URL fileUrl=ParseUtil.fileToEncodedURL(new File(jarPath));
            urlClassLoader=new URLClassLoader(new URL[]{fileUrl},null);
            InputStream inputStream=urlClassLoader.getResourceAsStream(file);
            if (inputStream==null){
                throw new FileNotFoundException("not find file:"+file+" in jar:"+jarPath);
            }else{
                return IOUtils.toString(inputStream,"UTF-8");
            }
        } catch (IOException e) {
            throw e;
        } finally {
            IOUtils.closeQuietly(urlClassLoader);
        }
    }
}

代码也是比较简单,最后把ClassLoader关闭一下就行了。
关于类加载器的读取方式,我很早之前就看过了,它本质上用的就是上面两种方式结合起来读取文件的。

总结

这几种方式的话,其实没有什么区别,从开发角度来说的话,比较推荐第三种,因为是java自带的功能,也是比较完善,也简单,也不容易出错,而且它内部用的就是前面两种。不过从资源消耗上面来说,我猜测,前面两种应该占优,不过我也不纠结这个,没去研究。

有时候我们或许有另外一种需求,读取jar中的jar中的文件,这个在一些场景下,会使用到。最起码spring-boot确实是用到了,很早之前我看过它的实现,它就是把URL重写了一下,支持了一下多个"/!"表达式,就能够支持这种情况了。

猜你喜欢

转载自blog.csdn.net/ywg_1994/article/details/104440114