Java运行时的查找路径

一直使用IDE或者基于web容器来运行Java程序,所以很少去考虑一些基础的,但是非常重要的问题——Java运行时的文件查找路径。

最近由于需要运行一个自己开发的小系统,需要独立运行Java进程,这个时候才发现,其实IDE和web容器为我们默默的做的很多事情。

1、首先一个路径问题就是——class类的查找


classpath相对来说到时我们经常遇到的问题,用来指定系统运行时需要加载的类文件的路径(自定义查找的可以不依赖这个),通常在web容器中会将 WEB-INF/lib 作为依赖包的路径,而classes作为未打包的class文件的默认路径。

其实这个属性值在运行JVM时可以通过-classpath或者-cp来指定,具体的用法如下:

-classpath / -cp 后接一个空格,然后是要被指定的路径,路径分为两种类型:第一种是压缩包(.jar或者.zip文件),另一种是文件夹,每个路径之间使用分号(:)分隔File.pathSeparator(window上是“;”,Linux上是“:”)来分隔。

如果是压缩包,需要指定完整的文件路径,例如有一个jms.jar文件,放在 /data/project/lib 下,则需要指定为/data/project/lib/jms.jar,这样造成一个问题是,基本上每个项目都会有很多依赖包,手动写太麻烦,其实可以使用另外一种方式指定,例如: /data/project/lib/* ,这样指定 /data/project/lib 下的所有压缩包都可以在运行时被查找。

如果是为压缩的class文件夹,则只需要指定到根目录,例如:编译后的class文件都在 /data/classes/com/google/ 下,其中com/google是包的路径,则只需要指定classpath为 /data/classes/,注意这里没有 “*” 号,所以是查找未压缩的类文件。

具体的一个classpath可以像这个:

java -cp .:/data/project/lib/*:/data/project/ext/jms.jar:\
/data/project/classes Test
java -cp .:/data/project/lib/*:/data/project/ext/jms.jar:\
/data/project/classes Test


2、接下来是程序运行时查找资源文件


Java程序运行时查找资源一般有两种方式可以获得:

第一种是使用File类,new File(String fileName)可以通过相对路径获得指定文件,但是这个“相对路径”是一个困扰人的地方,如果能够指定绝对路径倒是可以高枕无忧的使用这个方法,但是使用相对路径的时候,往往会让人很迷惑。例如:

public class Test {   
public static void main(String[] args) {
File file = new File(".");
System.out.println(file.getAbsolutePath());
}
}
public class Test {
public static void main(String[] args) {
File file = new File(".");
System.out.println(file.getAbsolutePath());
}
}
这段代码返回的路径为你运行代码是路径,例如:在/data/test/ 目录下运行,输出为/data/test/.;在/data/other/ 目录运行,输出则为/data/other/. 。这说明File的默认路径其实是随着运行的路径在变化的,其实默认值就是执行命令的路径。

但是用惯了在classes下面直接查找ApplicationResources.properties文件的人,代码里查找时一般会只用相对路径直接查找,这个时候不稳定的根目录就是一个大问题。不可能真的每次都期待启动的时候先到正确的目录下(用脚本处理还是可以的),所以尝试下改变默认的跟路径吧。

user.dir,作为一个Java程序的运行时参数,可以指定默认的File路径,指定方式为

java -Duser.dir=/data/project/classes/ Test
java -Duser.dir=/data/project/classes/ Test
执行下Test类会发现输出的确变成了/data/project/classes。

但是如果在classes目录下方一个ApplicationResources.properties文件, 修改file 为 new File("ApplicationResources.properties"); 理论上我们认为这样可以找到对应的文件,而且程序执行后输出的路径是: /data/project/classes/ApplicationResources.properties ,这个和实际的路径完全相同的,不过如果尝试读写文件,或者判断file.exists(), 却会发现文件找不到。这个问题由于牵涉到native的代码,还没有搞清楚为什么,这种情况下回发现调用file.getAbsolutePath()返回的路径虽然是对的,但是file.createNewFile()却依旧在执行程序的路径下生成文件,说明JVM使用的虚拟文件系统和实际的系统发生了偏差。

经过搜索和跟踪代码,可以肯定的一点是,user.dir属性看起来是修改的,但是File类在获得绝对路径和文件流时使用了不同的路径查找方式,前者的确使用了user.dir属性,而且不严重是否存在,而后者使用的还是一个看不到的(所以也没法修改的)属性,代表启动JVM的实际路径,并查找文件。相关的讨论可以参考:http://stackoverflow.com/questions/2275362/java-file-exists-inconsistencies-when-setting-user-dir

如果一定要避免这个相对路径的问题,可以使用绝对路径的方式

view plaincopy to clipboardprint?
//使用user.dir指定资源文件的路径
File file = new File("ApplicationResources.properties"); //使用相对路径先查找文件
file = new File(file.getAbsolutePath()); //使用前面查找到的绝对路径重新创建File实例
System.out.println(file.exists()); //这个时候会发现file已经可以找到了
//使用user.dir指定资源文件的路径
File file = new File("ApplicationResources.properties"); //使用相对路径先查找文件
file = new File(file.getAbsolutePath()); //使用前面查找到的绝对路径重新创建File实例
System.out.println(file.exists());

这个时候会发现file已经可以找到了不过这样的代码看起来不是那么的优雅。如果直接指定绝对路径的情况下可以使用File的方式第二种方式是使用ClassLoader类加载。

ClassLoader类提供了几种方法获得资源:

URL getResource(String name);

Enumeration<URL> getResources(String name);

URL findResource(String name);

这里只列出几个,其他的比较类似。getResource方法其实是先委托ClassLoader的父类去依次加载,如果没有找到,才使用自己的findResource方法去查找,所以findResouce方法决定了自定义的资源文件是否可以找到。使用方法如下

//获得URL后使用   
URL url = Test.class.getClassLoader()
.getResource("ApplicationResources.properties");
//获得直接获得文件流
InputStream propertyInput = Test.class.getClassLoader()
.getResourceAsStream("ApplicationResources.properties");
//获得URL后使用
URL url = Test.class.getClassLoader()
.getResource("ApplicationResources.properties");
//获得直接获得文件流
InputStream propertyInput = Test.class.getClassLoader()
.getResourceAsStream("ApplicationResources.properties");findResource()


方法其实和File的构造方法比较类似,都可以使用绝对路径和相对路径,绝对路径和URL格式的相对路径这里不讨论,这里只说明在当前机器上的相对路径下如何查找到自定义的资源文件。使用ClassLoader加载资源时,如果找不到,则直接返回NULL的URL,或者抛出异常。如果我们将ApplicationResources.properties放到classes目录下,然后修改启动参数如下

java -cp .:/data/project/lib/*:/data/project/classes/ Test
java -cp .:/data/project/lib/*:/data/project/classes/ Test则可以正常的找到ApplicationResources.properties文件,因为ClassLoader会从classpath中去查找资源文件,就想查找class类文件一样(注意classes添加方式是没有使用*号的,即认为这个目录下是为压缩的class文件),查找的优先级按classpath中声明的顺序。

如果设置了user.dir参数会怎样影响ClassLoader的查找呢?

将ApplicationResources.properties文件复制到新的目录/data/project/temp/下,并修改启动参数:

java -cp .:/data/project/lib/*:/data/project/classes/
-Duser.dir=/data/project/temp/ Test
java -cp .:/data/project/lib/*:/data/project/classes/
-Duser.dir=/data/project/temp/ Test如果输出获得的URL会发现ApplicationResources.properties文件是在temp目录下被找到的,说明ClassLoader会优先查找user.dir路径。

所以综合以上的分析,获得一个资源文件的最优方式是使用ClassLoader来查找,这样只需要指定classpath就可以做到,而且查找到的文件时正确的、可用的,而使用File会需要修改user.dir,可能带来的影响还不详细(至少已经影响到ClassLoader了),更主要的是修改了以后会造成虚拟的文件系统和实际系统不同步,要么自己痛苦的修改为绝对路径,要么就会出现不可预知的错误。

PS:通过输出执行时的环境和properties信息发现,IDE的确只指定了classpath来完成这个任务,web容器的以后补上。查看的代码为

System.out.println("\n\nEnvs:\n");   
for (Entry entry : System.getenv().entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}

System.out.println("\n\nProperties:\n");
for (Entry entry : System.getProperties().entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
System.out.println("\n\nEnvs:\n");
for (Entry entry : System.getenv().entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}

System.out.println("\n\nProperties:\n");
for (Entry entry : System.getProperties().entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}


在最近的工作中发现ClassLoader获取资源的一个小问题,就是无法获取jar文件,因为在ClassLoader中所有的jar包会作为资源文件的目录看待(个人理解),所以jar包里的class文件可以直接通过包路径找到,例如:java.util.Date 就是直接以rt.jar为跟路径开始查找的。要获得一个jar文件似乎只能尝试使用File类,不过可以通过ClassLoader先获取一个jar包里的class文件的URL(getResource方法返回的就是URL),然后通过url获取path,这个path会以file:**/*.jar!为前缀,需要自己解析出jar文件的绝对路径,这样才能保证jar文件可以被找到。不过幸好一般情况下不需要直接访问jar文件的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值