一、通过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/c给ClassLoader调用;
如果资源名不是以/开头的绝对路径,例如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).自定义类加载器
ExtClassLoader和AppClassLoader在JVM启动后,会在JVM中保存一份,并且在程序运行中无法改变其搜索路径。如果想在运行时从其他搜索路径加载类,就要产生新的类加载器。
线程上下文类加载器
线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()和setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。
前面提到的类加载器的代理模式并不能解决 Java 应用开发中会遇到的类加载器的全部问题。Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 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()。也就是分别在加载启动类、扩展类、系统类的路径下寻找资源文件,和类加载采用相同的双亲委派机制。
并不是所有的类加载都符合双亲委派机制,因此类中加载的资源文件也不是都符合双亲委派机制。所以加载资源的时候,也需要用到线程上下文类加载器。我们通常使用下面的代码加载资源文件(通常使用ClassLoader的getResource(),它和类加载思路一致,因此更好理解):
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; } |