您的位置:首页 > 编程语言 > Java开发

java加载jar包下的资源文件过程及原理分析

2017-08-19 13:41 344 查看
最近遇到一个这样的问题,项目是一个spring cloud的项目,一个主模块(记为mainMoudle)依赖了另一个子模块(记为subMoudle)。在开发过程中,在idea开发工具环境下是能正常运行的,但由于测试时,需要将模块打包,就将subMoudle工程打成了一个jar放在mainMoudle下,跑jar包时就发现不能运行了,控制台抛出了fileNotFoundException的异常信息。

通过查看subMoudle下的代码排查问题时,我发现是由于subMoudle在初始化时,需要加载mainMoudle中的配置文件。加载的代码是通过File类直接加载的,在开发环境时,运行时是直接将工程的资源文件编译到target的classes目录下的, 所以在开发环境下是可以正常运行的。而当项目打成了一个jar包时运行时,jar包中的资源文件不会再自动解压释放到目录中的,因为它已经编译好了,java也已经它成了class字节码文件了。所以再通过原来的File直接读取jar下的一个文件时是读取不到的,故问题就出现在这里。那么我们该如何去解决这个问题呢,当时为了方便我直接用的apache的commons-configuration包来解决的,将subMoudle的中对于读取配置文件的代码进行了替换,对于要读取配置文件的代码全改成了configuration的代码来读取,问题就解决了。

他们都说知道如何解决一个问题是一个初级程序员的该干的事,作为一个中高级程序员就必须得要了解其原理了,我觉得很有道理。于是我通过在idea下的断点进行了分析,找到了关键代码

static URL locateFromClasspath(String resourceName) {
URL url = null;
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader != null) {
url = loader.getResource(resourceName);
if (url != null) {
LOG.debug("Loading configuration from the context classpath (" + resourceName + ")");
}
}

if (url == null) {
url = ClassLoader.getSystemResource(resourceName);
if (url != null) {
LOG.debug("Loading configuration from the system classpath (" + resourceName + ")");
}
}

return url;
}


首先它通过获取了当前线程的一个类加载器,通过加载器的getResouce方法去类加载器找到resourceName这个文件

loader.getResouce的代码属于JDK的代码,其getResouce这个方法代码为:

// -- Resource --

/**
* Finds the resource with the given name.  A resource is some data
* (images, audio, text, etc) that can be accessed by class code in a way
* that is independent of the location of the code.
*
* <p> The name of a resource is a '<tt>/</tt>'-separated path name that
* identifies the resource.
*
* <p> This method will first search the parent class loader for the
* resource; if the parent is <tt>null</tt> the path of the class loader
* built-in to the virtual machine is searched.  That failing, this method
* will invoke {@link #findResource(String)} to find the resource.  </p>
*
* @apiNote When overriding this method it is recommended that an
* implementation ensures that any delegation is consistent with the {@link
* #getResources(java.lang.String) getResources(String)} method.
*
* @param  name
*         The resource name
*
* @return  A <tt>URL</tt> object for reading the resource, or
*          <tt>null</tt> if the resource could not be found or the invoker
*          doesn't have adequate  privileges to get the resource.
*
* @since  1.1
*/
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}

jdk的开发者还为我们留下了注释,注意这段注释:

* <p> This method will first search the parent class loader for the
* resource; if the parent is <tt>null</tt> the path of the class loader
* built-in to the virtual machine is searched.  That failing, this method
* will invoke {@link #findResource(String)} to find the resource.  </p>

通过代码和注释我们可以得知此代码会先去父节点的loader去加载资源文件,如果找不到,则会去BootstrapLoader中去找,如果还是找不到,才调用当前类的classLoader去找。这也就是我们有时说的所谓的双亲委派模型。

(双亲委派模型的工作过程为:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载)

public InputStream getInputStream(URL url) throws ConfigurationException {
File file = ConfigurationUtils.fileFromURL(url);
if (file != null && file.isDirectory()) {
throw new ConfigurationException("Cannot load a configuration from a directory");
} else {
try {
return url.openStream();
} catch (Exception var4) {
throw new ConfigurationException("Unable to load the configuration from the URL " + url, var4);
}
}
}


当资源被找到后,通过调用url的openStream()方法去获得此文件的输入流

因此,单纯地用File去去读取jar包的文件是不能的,因为!并不是文件资源定位符的格式 (jar中资源有其专门的URL形式: jar:<url>!/{entry} )。所以,如果jar包中的类源代码用File f=new File(相对路径);的形式,是不可能定位到文件资源的

为此我专门写了个测试代码:

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;

import java.io.*;
import java.net.URL;
import java.util.Iterator;

public class ResourceReader {
private static final String subMoudlePropertiesFile = "sys.properties";//jar下的配置文件
private static final String innerPropertiesFile = "own.properties";//内部配置文件

public static void main(String[] args) throws InterruptedException {
loadJarFileByConfiguration();
Thread.sleep(1000);
loadLocalFile();
Thread.sleep(1000);
loadJarFileByResource();
Thread.sleep(1000);
loadJarFileByFile();
}

/**
* 通过File类去加载jar包的资源文件
*/
private static void loadJarFileByFile() {
System.out.println("----------loadJarFileByFile---- begin------------");
URL resource = ResourceReader.class.getClassLoader().getResource(subMoudlePropertiesFile);
String path = resource.toString();
System.out.println(path);
try {
File file = new File(path);
FileInputStream fileInputStream = new FileInputStream(file);
BufferedReader br = new BufferedReader(new InputStreamReader(fileInputStream));
String s = "";
while ((s = br.readLine()) != null)
System.out.println(s);

} catch (Exception e) {
e.printStackTrace();
}
System.out.println("----------loadJarFileByFile---- end------------\n\n");
}

/**
* 通过apache configuration包读取配置文件
*/
private static void loadJarFileByConfiguration() {
System.out.println("----------loadJarFileByConfiguration---- begin------------");
try {
Configuration configuration = new PropertiesConfiguration(subMoudlePropertiesFile);
Iterator<String> keys = configuration.getKeys();
while (keys.hasNext()) {
String next = keys.next();
System.out.println("key:" + next + "\tvalue:" + configuration.getString(next));
}
} catch (ConfigurationException e) {
e.printStackTrace();
}
System.out.println("----------loadJarFileByConfiguration---- end------------\n\n");
}

/**
* 通过类加载器去的getResource方法去读取
*/
private static void loadJarFileByResource() {
System.out.println("----------loadJarFileByResource---- begin------------");
URL resource = ResourceReader.class.getClassLoader().getResource(subMoudlePropertiesFile);
String path = resource.toString();
System.out.println(path);
try {
InputStream is = resource.openStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String s = "";
while ((s = br.readLine()) != null)
System.out.println(s);

} catch (Exception e) {
e.printStackTrace();
}
System.out.println("----------loadJarFileByResource---- end------------\n\n");

}

/**
* 读取当前工程中的配置文件
*/
private static void loadLocalFile() {
System.out.println("----------loadLocalFile---- begin------------");
String path = ResourceReader.class.getClassLoader().getResource(innerPropertiesFile).getPath();
System.out.println(path);

try {
FileReader fileReader = new FileReader(path);
BufferedReader bufferedReader = new BufferedReader(fileReader);
String strLine;
while ((strLine = bufferedReader.readLine()) != null) {
System.out.println("strLine:" + strLine);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("----------loadLocalFile---- begin------------\n\n");
}
}


子模块结构为:



sys.properties位于subMoudle的jar中

以上代码运行结果为:

----------loadJarFileByConfiguration---- begin------------

log4j:WARN No appenders could be found for logger (org.apache.commons.configuration.PropertiesConfiguration).

log4j:WARN Please initialize the log4j system properly.

key:username value:haiyangge

key:password value:haiyangge666

----------loadJarFileByConfiguration---- end------------

----------loadLocalFile---- begin------------

/E:/idea_space/spring_hello/target/classes/own.properties

strLine:db.username=9527

strLine:db.password=0839

----------loadLocalFile---- begin------------

----------loadJarFileByResource---- begin------------

jar:file:/E:/idea_space/spring_hello/libs/subMoudle-1.0-SNAPSHOT.jar!/sys.properties

username=haiyangge

password=haiyangge666

----------loadJarFileByResource---- end------------

----------loadJarFileByFile---- begin------------

java.io.FileNotFoundException: jar:file:\E:\idea_space\spring_hello\libs\subMoudle-1.0-SNAPSHOT.jar!\sys.properties (文件名、目录名或卷标语法不正确。)

jar:file:/E:/idea_space/spring_hello/libs/subMoudle-1.0-SNAPSHOT.jar!/sys.properties

----------loadJarFileByFile---- end------------
at java.io.FileInputStream.open0(Native Method)

at java.io.FileInputStream.open(FileInputStream.java:195)

at java.io.FileInputStream.<init>(FileInputStream.java:138)
at javafile.read.ResourceReader.loadJarFileByFile(ResourceReader.java:35)
at javafile.read.ResourceReader.main(ResourceReader.java:22)

从运行结果可以看出,通过file类去加载本项目中的资源文件是可以成功的,但加载jar下的资源文件是不可以的,因为jar!sys.properties不是文件资源定位符的格式,而是jar中的.

故加载jar包内的资源文件时,应该用classLoader的getResource方法去加载,获取到URL后,用openStream()方法打开流,不应该原生的file去加载。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: