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

最近遇到一个这样的问题,项目是一个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下的断点进行了分析,找到了关键代码

staticURL 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 + ")");}

}

returnurl;}

首先它通过获取了当前线程的一个类加载器,通过加载器的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.**

The name of a resource is a '/'-separated path name that* identifies the resource.**

This method will first search the parent class loader for the* resource; if the parent isnullthe 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.

**@apiNoteWhen 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.**@paramname* The resource name**@returnA URLobject for reading the resource, or* nullif the resource could not be found or the invoker* doesn't have adequate privileges to get the resource.**@since1.1*/publicURL getResource(String name) {

URL url;if(parent!= null) {

url = parent.getResource(name);} else{

url = getBootstrapResource(name);}

if(url == null) {

url = findResource(name);}

returnurl;}

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

*

This method will first search the parent class loader for the* resource; if the parent isnullthe 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.

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

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

publicInputStream getInputStream(URL url) throwsConfigurationException {

File file = ConfigurationUtils.fileFromURL(url);if(file != null&& file.isDirectory()) {

throw newConfigurationException("Cannot load a configuration from a directory");} else{

try{

returnurl.openStream();} catch(Exception var4) {

throw newConfigurationException("Unable to load the configuration from the URL "+ url,var4);}

}

}

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

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

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

importorg.apache.commons.configuration.Configuration;importorg.apache.commons.configuration.ConfigurationException;importorg.apache.commons.configuration.PropertiesConfiguration;importjava.io.*;importjava.net.URL;importjava.util.Iterator;public classResourceReader {

private static finalString subMoudlePropertiesFile= "sys.properties";//jar下的配置文件private static finalString innerPropertiesFile= "own.properties";//内部配置文件public static voidmain(String[] args) throwsInterruptedException {

loadJarFileByConfiguration();Thread.sleep(1000);loadLocalFile();Thread.sleep(1000);loadJarFileByResource();Thread.sleep(1000);loadJarFileByFile();}

/***通过File类去加载jar包的资源文件*/private static voidloadJarFileByFile() {

System.out.println("----------loadJarFileByFile---- begin------------");URL resource = ResourceReader.class.getClassLoader().getResource(subMoudlePropertiesFile);String path = resource.toString();System.out.println(path);try{

File file = newFile(path);FileInputStream fileInputStream = newFileInputStream(file);BufferedReader br = newBufferedReader(newInputStreamReader(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 voidloadJarFileByConfiguration() {

System.out.println("----------loadJarFileByConfiguration---- begin------------");try{

Configuration configuration = newPropertiesConfiguration(subMoudlePropertiesFile);Iterator 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 voidloadJarFileByResource() {

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 = newBufferedReader(newInputStreamReader(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 voidloadLocalFile() {

System.out.println("----------loadLocalFile---- begin------------");String path = ResourceReader.class.getClassLoader().getResource(innerPropertiesFile).getPath();System.out.println(path);try{

FileReader fileReader = newFileReader(path);BufferedReader bufferedReader = newBufferedReader(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");}

}

子模块结构为:

e20907d580512c0c6d99dd5006727517.png

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:usernamevalue:haiyangge

key:passwordvalue: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.(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去加载。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值