Java加载资源文件的方法

一、通过Class:getResource()加载资源

通过Class类的public java.net.URL getResource(String name)

public java.net.URL getResource(String name) {

        name = resolveName(name);

        ClassLoader cl = getClassLoader0();

        if (cl==null) {

            // A system class.

            return ClassLoader.getSystemResource(name);

        }

        returncl.getResource(name);

    }

通过Class类的getResource和通过ClassLoader的类加载资源的主要区别在于resolveName这个方法,我们来看下它的实现:

private String resolveName(String name) {

        if (name == null) {

            returnname;

        }

        if (!name.startsWith("/")) {

            Class<?> c = this;

            while (c.isArray()) {

                c = c.getComponentType();

            }

            String baseName = c.getName();

            intindex = baseName.lastIndexOf('.');

            if (index != -1) {

                name = baseName.substring(0, index).replace('.', '/')

                    +"/"+name;

            }

        } else {

            name = name.substring(1);

        }

        returnname;

    }

resolveName方法,如果是资源名是以/开头的绝对路径,例如/a/b/c,则返回a/b/cClassLoader调用;

如果资源名不是以/开头的绝对路径,例如a/b/c,则返回当前类grucee.test.Main的包路径grucee/test加上/+a/b/c,最后的结果就是grucee/test/a/b/c

我们来看两个示例:

资源文件如下图:



 
代码

publicstaticvoid main(String[] args) throws IOException {

       String absolutePath = "/test.properties";

       String relativePath = "test.properties";

      

       InputStream absoluteIn = Main.class.getResourceAsStream(absolutePath);

       InputStream relativeIn = Main.class.getResourceAsStream(relativePath);

      

       //假设编译后的文件按照包结构放置在bin目录(eclipse默认)

       //传绝对路径加载的是相对于bin目录的文件:bin/test.properties

       Properties absoluteProp = new Properties();

       absoluteProp.load(absoluteIn);

       System.out.println(absoluteProp.getProperty("path"));

      

       //传相对路径加载的是相对于Main.class所在目录的文件:bin/grucee/test/test.properties

       Properties relativeProp = new Properties();

       relativeProp.load(relativeIn);

       System.out.println(relativeProp.getProperty("path"));

    }

二、通过ClassLoader:getResource()加载资源

系统有哪些ClassLoader

1).Bootstrap Loader(引导类加载器):加载System.getProperty("sun.boot.class.path")所指定的路径或jar

2).Extended Loader(标准扩展类加载器ExtClassLoader):加载System.getProperty("java.ext.dirs")所指定的路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld

3).AppClass Loader(系统类加载器AppClassLoader):加载System.getProperty("java.class.path")所指定的路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld

4).自定义类加载器

ExtClassLoaderAppClassLoaderJVM启动后,会在JVM中保存一份,并且在程序运行中无法改变其搜索路径。如果想在运行时从其他搜索路径加载类,就要产生新的类加载器。

线程上下文类加载器

线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

前面提到的类加载器的代理模式并不能解决 Java 应用开发中会遇到的类加载器的全部问题。Java 提供了很多服务提供者接口(Service Provider InterfaceSPI),允许第三方为这些接口提供实现。常见的 SPI JDBCJCEJNDIJAXP JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP SPI 接口定义包含在 javax.xml.parsers包中。这些 SPI 的实现代码很可能是作为 Java 应用所依赖的 jar 包被包含进来,可以通过类路径(CLASSPATH)来找到,如实现了 JAXP SPI  Apache Xerces所包含的 jar 包。SPI 接口中的代码经常需要加载具体的实现类。如 JAXP 中的 javax.xml.parsers.DocumentBuilderFactory类中的 newInstance()方法用来生成一个新的 DocumentBuilderFactory的实例。这里的实例的真正的类是继承自 javax.xml.parsers.DocumentBuilderFactory,由 SPI 的实现所提供的。如在 Apache Xerces 中,实现的类是 org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。而问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题。

线程上下文类加载器正好解决了这个问题。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。

加载资源的流程

ClassLoader加载配置文件时,路径均不能以"/"开头,在查找时直接在classpath下进行查找。

查看源代码:

public URL getResource(String name) {

        URL url;

        if (parent != null) {

            url = parent.getResource(name);

        } else {

            url = getBootstrapResource(name);

        }

        if (url == null) {

            url = findResource(name);

        }

        returnurl;

    }

假设我们没有自定义类加载器,并且我们调用的是系统类加载器的getResource,那么该ClassLoader就是AppClassLoader的实例,并且通过上述方法,依次会调用:

AppClassLoader:getResource->ExtClassLoader:getResource->getBootstrapResource()。也就是分别在加载启动类、扩展类、系统类的路径下寻找资源文件,和类加载采用相同的双亲委派机制。

并不是所有的类加载都符合双亲委派机制,因此类中加载的资源文件也不是都符合双亲委派机制。所以加载资源的时候,也需要用到线程上下文类加载器。我们通常使用下面的代码加载资源文件(通常使用ClassLoadergetResource(),它和类加载思路一致,因此更好理解):

privatestatic InputStream loadConfigFile() {

       //先使用当前类的类加载器查找资源

       InputStream in = FileLogReader.class.getClassLoader().getResourceAsStream(LoggerConstants.FILE_LOG_PATH);

       if (in == null) {

           //查找不到资源时,使用线程上下文的类加载器查找资源

           in = Thread.currentThread().getContextClassLoader()

                  .getResourceAsStream(LoggerConstants.FILE_LOG_PATH);

       }

       returnin;

    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值