Java 加载资源文件

自闭症网瘾萝莉.ら 提交于 2019-11-27 11:15:06

简介

Java中获取资源的最常用的2中方式就是使用Class的getResource和使用ClassLoader的getResource方法,当然还有它们相关的方法。这里就介绍一下使用这2中方式的区别,和它们搜索使用的路径。 这里先说结论(hotspot): ClassLoader的getResource(name)方法会依次查找:

  1. 在"sun.boot.class.path"指定的路径问根目录下查找name资源
  2. 在"java.ext.dirs"指定的路径为根目录下查找name资源
  3. 在"java.class.path"指定的路径为根目录下查找name资源
  4. 利用ClassLoader(自定义的,重写了findResource)的findResource(name)获取URL

Class的getResource(name)方法是调用的ClassLoader的getResource(name)方法,但是它做了2点处理:

  1. 如果name以"/"开头,就把name中开头的"/"去掉,然后调优ClassLoader的getResource(name)方法。然后在ClassLoader的getResource(name)方法搜索方式搜索。
  2. 如果name不以"/"开头,那么就用Class的包名+name作为新的name来调用ClassLoader的getResource(name)方法。cn.freemethod.start.BaseName.class.getResource("config.properties")最终调用的就是ClassLoader的getResource("cn/freemethod/start/config.properties")可能ClassLoader的类型不同,但是不影响ClassLoader的getResource(name)搜索套路。(前提是没有破坏父类委托机制)

注意:这些路径包括了jar包中的资源,例如,你的classpath中包含了springmvc的jar包,你就可以通过下面的方式来加载DispatcherServlet.properties文件。

import java.net.URL;
import org.junit.Test;

public class ResourceTest {
    
    private static final String DISPATCHER_SERVLET_PROPERTIES_PKG = 
"org/springframework/web/servlet/DispatcherServlet.properties";

    @Test
    public void testResource()
    {
        URL url = null;
        ClassLoader loader = ResourceTest.class.getClassLoader();
        url = loader.getResource(DISPATCHER_SERVLET_PROPERTIES_PKG);
        System.out.println(url);
        url = ClassLoader.getSystemResource(DISPATCHER_SERVLET_PROPERTIES_PKG);
        System.out.println(url);
    }

}

另一点值得注意的是在IDE中,比如Eclipse中运行的时候Eclipse常常是添加了新的classpath路径的,比如,如果是Java project工程,Eclipse就会把项目根目录下的bin目录添加到classpath中,如果是maven工程,Eclipse就会把项目根目录下的target目录下的classes和pom中的jar包加到classpath中,运行JUnit,Eclipse会把test-classes和pom中的jar包加到classpath中。 要知道指定的路径有没有在搜索路径中,可以输出3个系统属性看一下就可以了:

System.out.println(System.getProperty("java.class.path"));
System.out.println(System.getProperty("sun.boot.class.path"));
System.out.println(System.getProperty("java.ext.dirs"));

其实用的最多的还是"java.class.path",一般注意一下"java.class.path"这个就可以了,知道了上面的内容基本上遇到getResource相关的内容的问题基本都能解决了。接下来我们就通过代码来看一些细节的东西。

ClassLoader#getResource(String name)

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

上面就是ClassLoader的getResource方法了,看上去感觉逻辑很简单,其实还是比较绕的。如果没有破坏父类委托机制,那么调用栈中应该先通过getBootstrapResource(name)这个方法查找,为了专注与ClassLoader的getResource方法的逻辑,我们先不细说getBootstrapResource(name),我们先看一下它干了什么,getBootstrapResource(name)方法做的工作就是在"sun.boot.class.path"指定的路径和其他通过接口添加到启动类加载器搜索路径中查找name。一般我们把资源放在classpath路径下,所以,一般是查找不到的。那么接下来执行到的将是getBootstrapResource(name)方法,层层委托先执行启动类加载器的getBootstrapResource(name),然后扩展类加载器的getBootstrapResource(name),然后系统类加载器的getBootstrapResource(name),最后自定义类加载器的getBootstrapResource(name)(这里假设的使用的自定义或者系统类加载器,如果不是,就不搜索下层的加载路径)。扩展类加载器的getBootstrapResource(name)干的事情就是在"java.ext.dirs"指定的路径和其他通过接口添加到扩展类加载器搜索路径中查找name。系统类加载器的getBootstrapResource(name)干的事情就是在"java.class.path"指定的路径和其他通过接口添加到系统类加载器搜索路径中查找name。 如果有兴趣的朋友可以看一下Launcher,URLClassLoader,URLClassPath这3个类的源码,如果深入一点还可以看一下MetaIndex缓存、URLStreamHandler相关的类等。

  public Enumeration<URL> getResources(String name) throws IOException {
        Enumeration[] tmp = new Enumeration[2];
        if (parent != null) {
            tmp[0] = parent.getResources(name);
        } else {
            tmp[0] = getBootstrapResources(name);
        }
        tmp[1] = findResources(name);

        return new CompoundEnumeration<>(tmp);
    }

ClassLoader的getResource是找到一个就不找了,而getResources把所有搜索路径中能找到的name资源都找出来。

ClassLoader提供的一下静态的查找资源方法

 public static URL getSystemResource(String name) {
        ClassLoader system = getSystemClassLoader();
        if (system == null) {
            return getBootstrapResource(name);
        }
        return system.getResource(name);
    }

这个方法其实还是调用的还是按ClassLoader的套路来查找,不过调用的加载器已经确定了是系统类加载器(AppclassLoader)而已。

private static URL getBootstrapResource(String name) {
        URLClassPath ucp = getBootstrapClassPath();
        Resource res = ucp.getResource(name);
        return res != null ? res.getURL() : null;
    }

只在"sun.boot.class.path"路径下查找name资源。

static URLClassPath getBootstrapClassPath() {
        return sun.misc.Launcher.getBootstrapClassPath();
    }
 public static Enumeration<URL> getSystemResources(String name)
        throws IOException
    {
        ClassLoader system = getSystemClassLoader();
        if (system == null) {
            return getBootstrapResources(name);
        }
        return system.getResources(name);
    }

和getSystemResource(String name)差不多,只是查找搜索路径下所有的name资源而已。

private static Enumeration<URL> getBootstrapResources(String name)
        throws IOException
    {
        final Enumeration<Resource> e =
            getBootstrapClassPath().getResources(name);
        return new Enumeration<URL> () {
            public URL nextElement() {
                return e.nextElement().getURL();
            }
            public boolean hasMoreElements() {
                return e.hasMoreElements();
            }
        };
    }

和getBootstrapResource(String name),只是查找"sun.boot.class.path"下所有的name资源而已。

Class#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);
        }
        return cl.getResource(name);
    }

我们一般使用Class的获取资源的时候都是使用的我们自定义的类,所以ClassLoader一般都是系统类加载器或者我们自定义的类加载器,所以getResource(String name)基本上就是按照ClassLoader的基本的套路来搜索的。最常见的用法就是加载和Class在同一个包下的资源,这样就不用拼接路径了。Class#getResource("/xxx"),加上"/"这个样的方式基本上就和ClassLoader#getSystemResource(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
                name = baseName.substring(0, index).replace('.', '/')
                    +"/"+name;
            }
        } else {
            name = name.substring(1);
        }
        return name;
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!