Java读取资源文件

写java代码时常常需要加载一些外部的资源,通常我们会使用全路径名加载一份资源,比如:C:\Users\Yukai\Desktop\abc.jpg . 但是,有些时候我们需要加载的是源代码路径下的资源或者配置文件等等,更习惯于使用相对路径,或者直接给一个文件名,就希望能够找到我们需要的配置文件。如何做到?常见的方法是使用了 class.getResource 或 classloader.getResource

class.getResource && classloader.getResource ?

这两个方法看起来很相似,他们直接有什么区别?

直接上网搜索能够得到一些答案,但都不如查看源代码来的直接:

Class.getResourceAsStream(String name)

public InputStream getResourceAsStream(String name) {
    name = resolveName(name);
    ClassLoader cl = getClassLoader0();
    if (cl==null) {
        // A system class.
        return ClassLoader.getSystemResourceAsStream(name);
    }
    return cl.getResourceAsStream(name);
}

上面的代码可以看出, Class.getResourceAsStream(String name) 最终还是调用了 classloader.getResourceAsStream(String name) 。但是两者还是有一些区别的,注意 name =resolveName(name)这一行, Class.getResourceAsStream(String name)在这里做了一些处理:

Class.resolveName(String name)

private String resolveName(String name) {
    if (name == null) {
        return name;
    }
    if (!name.startsWith("/")) {
        Class<?> c = this;
        while (c.isArray()) {
            c = c.getComponentType();
        }
        String baseName = c.getName();
        int index = baseName.lastIndexOf('.');
        if (index != -1) {
            name = baseName.substring(0, index).replace('.', '/')
                +"/"+name;
        }
    } else {
        name = name.substring(1);
    }
    return name;
}

现在看这段代码还有点云里雾绕,不妨写几行代码测试一下,看看这段代码到底在干嘛:

public class App {
    public static void main(String[] args) {
        System.out.println("App.class.getClassLoader().getResource(\"\") : " + App.class.getClassLoader().getResource(""));
        System.out.println("App.class.getClassLoader().getResource(\"/\") : " + App.class.getClassLoader().getResource("/"));
        System.out.println("App.class.getResource(\"\") : " + App.class.getResource(""));
        System.out.println("App.class.getResource(\"/\") : " + App.class.getResource("/"));
    }
}

输出:

App.class.getClassLoader().getResource("") : file:/D:/workspace/eclipse/cluster/TestClassloader/bin/
App.class.getClassLoader().getResource("/") : null
App.class.getResource("") : file:/D:/workspace/eclipse/cluster/TestClassloader/bin/space/yukai/
App.class.getResource("/") : file:/D:/workspace/eclipse/cluster/TestClassloader/bin/

虽然上面的代码使用了getResource,但与getResourceAsStream大同小异。

可以看到,

calssloder.getResource("")方法返回了classpath根路径(eclipse工程中,编译生成的类文件存放在/bin目录下);

calssloder.getResource("/")方法返回null,说明calssloder.getResource不支持以"/"开头的参数;

class.getResource("")方法返回了App.class所在的路径;

class.getResource("/")calssloder.getResource("")表现一致,返回了classpath的根路径

再回顾上面的代码,是否有一点明白了呢?

工程目录结构如下图:

image.png
  • 读取根目录下的tmp(src/):
// 第一种方法
InputStream in = App.class.getResourceAsStream("/tmp");
// 第二种方法
InputStream in =ClassLoader.getSystemClassLoader().getResourceAsStream("tmp");
  • 读取App类同级目录下的tmp(src/space/yukai)
// 第一种方法
InputStream in = App.class.getResourceAsStream("tmp");
// 第二种方法
InputStream in =ClassLoader.getSystemClassLoader().getResourceAsStream("space/yukai/tmp");
//第三种方法
InputStream in = App.class.getResourceAsStream("/space/yukai/tmp");
  • 读取MyClassloader同级目录下的tmp(src/space/yukai/classloader):
// 第一种方法
InputStream in = App.class.getResourceAsStream("classloader/tmp");
// 第二种方法
InputStream in =ClassLoader.getSystemClassLoader().getResourceAsStream("space/yukai/classloader/tmp");
//第三种方法
InputStream in = App.class.getResourceAsStream("/space/yukai/classloader/tmp");

classloader.getResource

上面提到了Class.getResourceAsStream(String name) 最终还是调用了classloader.getResourceAsStream(String name),那么classloader.getResourceAsStream(String name)是如何寻找我们要读取的资源呢?

classloadergetResourceAsStream(String name)

public InputStream getResourceAsStream(String name) {
    URL url = getResource(name);
    try {
        return url != null ? url.openStream() : null;
    } catch (IOException e) {
        return null;
    }
}

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;
}

上面getResource(String name)中的代码是否有中熟悉的感觉?这不就是classloader的双亲委托机制么?

首先使用自己的父类加载器寻找资源,如果父类加载器为null,表示此时的类加载器是启动类加载器,故调用getBootstrapResource(name)方法查询资源。如果所有的祖先类加载器都找不到指定的资源,那么调用该类加载器的findResource(name)方法。

那么这些类加载器是去哪查询资源是否存在呢?与加载类时查询的路径一致,对于我们的应用来说,我们应该关心AppClassLoader,我们自定义的资源往往存放于其查询的路径上,也就是classpath指定的路径。

classpath

在上文的例子中,classpath指向eclipse自动生成的/bin目录。我们如何在eclipse中添加我们自定义的classpath呢?

有两种方法:

  • 工程右键->Build Path->Configure Build Path->Source标签->点击右侧AddFolder 可以将工程目录下的文件夹设置为Source目录

比如常见的Maven中的resources目录,就是Source目录。

设置为Source目录之后,eclipse在编译源文件时,会自动将Source目录下的文件拷贝到/bin目录,自然也就是classpath下了。

这种方法的限制就是只能把工程目录下的文件夹添加进去。

  • 设置运行时classpath

在菜单栏点击run->Run Configurations->Classpath

image.png

如图所示,点击右侧Advanced按钮,可以添加文件夹到运行时classpath。

当然,如果是在命令行下直接运行java程序的话,可以使用-classpath选项指定classpath

在maven中,可以使用下面的方法指定jar包的classpath:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <finalName>ReleaseTool</finalName>
        <archive>
            <manifest>
                <!-- 为依赖包添加路径, 这些路径会写在MANIFEST文件的Class-Path下 -->
                <addClasspath>true</addClasspath>
                <classpathPrefix>lib/</classpathPrefix>
                <mainClass>com.oscar.releasetool.app.App</mainClass>
            </manifest>
            <manifestEntries>
                <!-- 在Class-Path下添加配置文件的路径 -->
                <Class-Path>./</Class-Path>
            </manifestEntries>
        </archive>
        <excludes>
            <exclude>config.json</exclude>
        </excludes>
    </configuration>
</plugin>

我们可以将配置文件放到任何需要的地方,然后将配置文件所在的目录添加到classpath,使用classloader.getResourceAsStream方法来读取。利用这种方法可以做到配置文件与jar包分离,并且配置文件所在的目录是可以自定义的。Spring读取application.properties使用的是同样的原理。

可以使用下面的代码查看当前classpath:

String classpath = System.getProperty("java.class.path");
String[] classpathEntries = classpath.split(File.pathSeparator);
for (String str1 : classpathEntries) {
    System.out.println(str1);
}

读取jar包所在的位置

有时候需要知道jar包所在的位置,比如我们的项目需要一个默认的日志文件输出路径,这个路径就可以是运行时jar包所在的目录。如何获取jar包所在的目录?

URL url = App.class.getProtectionDomain().getCodeSource().getLocation();
String path = url.toURL().getPath();

注意,上面的方法仅适用jdk1.5及以上

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,601评论 18 139
  • 在编写 Java 程序时,我们所编写的 .java 文件经编译后,生成能被 JVM 识别的 .class 文件,....
    EricAlpha阅读 3,523评论 0 6
  • 类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到...
    CHSmile阅读 1,596评论 0 12
  • ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见...
    时待吾阅读 1,060评论 0 1
  • 20171104 熬过了一天,自己还活着,真的很幸福。想想明天的课,整个人处于头大的状态。最近很忙,忙起来好,这样...
    梦家小妖阅读 196评论 0 0