`

Java目录总结(七.路径的获取二——ClassLoader的使用)

    博客分类:
  • Java
阅读更多

ClassLoader提供了两个方法用于从装载的类路径中取得资源:

        public URL getResource(String name); 
        public InputStream getResourceAsStream(String name); 

       这里name是资源的类路径,它是相对与“/”根路径下的位置。getResource得到的是一个URL对象来定位资源,而getResourceAsStream取得该资源输入流的引用保证程序可以从正确的位置抽取数据。
       但 是真正使用的不是ClassLoader的这两个方法,而是Class的 getResource和getResourceAsStream方法,因为 Class对象可以从你的类得到(如YourClass.class或 YourClass.getClass()),而ClassLoader则需要再 调用一次YourClass.getClassLoader()方法,不过根据JDK文档的说法,Class对象的这两个方法其实是“委托” (delegate)给装载它的ClassLoader来做的,所以只需要使用 Class对象的这两个方法就可以了。

       因此,直接调用 this.getClass().getResourceAsStream(String name);获取流,静态化方法中则使用ClassLoader.getSystemResourceAsStream(String name); 。

      下面是一些得到classpath和当前类的绝对路径的一些方法。你可能需要使用其中的一些方法来得到你需要的资源的绝对路径。

1.this.getClass().getResource("")
得到的是当前类class文件的URI目录。不包括自己!
如:file:/D:/workspace/jbpmtest3/bin/com/test/

2.this.getClass().getResource("/")
得到的是当前的classpath的绝对URI路径
如:file:/D:/workspace/jbpmtest3/bin/

3.this.getClass().getClassLoader().getResource("")
得到的也是当前ClassPath的绝对URI路径
如:file:/D:/workspace/jbpmtest3/bin/

4.ClassLoader.getSystemResource("")
得到的也是当前ClassPath的绝对URI路径
如:file:/D:/workspace/jbpmtest3/bin/

5.Thread.currentThread().getContextClassLoader().getResource("")
得到的也是当前ClassPath的绝对URI路径
如:file:/D:/workspace/jbpmtest3/bin/

6.ServletActionContext.getServletContext().getRealPath(“/”)
Web应用程序中,得到Web应用程序的根目录的绝对路径。这样,我们只需要提供相对于Web应用程序根目录的路径,就可以构建出定位资源的绝对路径。
如:file:/D:/workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/WebProject


注意点:

1.尽量不要使用相对于System.getProperty("user.dir")当前用户目录的相对路径。这是一颗定时炸的弹,随时可能要你的命。

2.尽量使用URI形式的绝对路径资源。它可以很容易的转变为URI,URL,File对象。

3. 尽量使用相对classpath的相对路径。不要使用绝对路径。使用上面ClassLoaderUtil类的 public static URL getExtendResource(String relativePath)方法已经能够使用相对于 classpath的相对路径定位所有位置的资源。

4.绝对不要使用硬编码的绝对路径。因为,我们完全可以使用ClassLoader类的getResource("")方法得到当前classpath的绝对路径。如果你一定要指定一个绝对路径,那么使用配置文件,也比硬编码要好得多!

获得CLASSPATH之外路径的方法:
URL base = this.getClass().getResource(""); //先获得本类的所在位置,如/home/popeye/testjava/build/classes/net/ 
      String path = new File(base.getFile(), "……/……/……/"+name).getCanonicalPath(); //就可以得到/home/popeye/testjava/name

另外,如果从ANT启动程序,this.getClass().getResource("")取出来的比较怪,直接用JAVA命令行调试就可成功。

 

 

例如:

工程结构:

Java-learning

       |---- src/main/java

       |---- src/test/java

       |---- src/main/resources/META-INF/spring

       |---- src/main/ resources

       |---- 引用的jar包和maven的pom.xml等

测试代码包路径:

src/main/java

       |---- com.lee.java.learning

              |---- compiler

                     |---- Test.java

              |---- AnClass.java

测试代码:

AnClass.java

view plain
package com.lee.java.learning; 
 
public class AnClass { 
    public static void main(String[] args) { 
    } 

Test.java

view plain
package com.lee.java.learning.compiler; 
 
import java.net.URL; 
 
public class Test { 
 
    public static void main(String[] args) { 
        Test test = new Test(); 
        test.test(); 
    } 
 
    public void test() { 
        URL root = this.getClass().getResource("/"); 
        URL current1 = this.getClass().getResource(""); 
        URL current2 = this.getClass().getResource("."); 
        URL parent = this.getClass().getResource(".."); 
        URL self1 = this.getClass().getResource("Test.class"); 
        URL self2 = this.getClass().getResource("./Test.class"); 
        URL brother = this.getClass().getResource("../AnClass.class"); 
 
        System.out.println("root = "+root); 
        System.out.println("current1 = "+current1); 
        System.out.println("current2 = "+current2); 
        System.out.println("parent = "+parent); 
        System.out.println("self1 = "+self1); 
        System.out.println("self2 = "+self2); 
        System.out.println("brother = "+brother); 
    } 
 

测试输出结果:

root = file:/D:/Java/workspace_1/java-learning/target/test-classes/
current1 = file:/D:/Java/workspace_1/java-learning/target/test-classes/com/lee/java/learning/compiler/
current2 = file:/D:/Java/workspace_1/java-learning/target/test-classes/com/lee/java/learning/compiler/
parent = file:/D:/Java/workspace_1/java-learning/target/test-classes/com/lee/java/learning/
self1 = file:/D:/Java/workspace_1/java-learning/target/classes/com/lee/java/learning/compiler/Test.class
self2 = file:/D:/Java/workspace_1/java-learning/target/classes/com/lee/java/learning/compiler/Test.class
brother = file:/D:/Java/workspace_1/java-learning/target/classes/com/lee/java/learning/AnClass.class

分析应该是classpath的问题。eclipse下调试程序,观察命令行参数:

view plain
E:\java6\bin\javaw.exe -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:4584 -Dfile.encoding=UTF-8 -classpath D:\java\eclipse\my_workspace\java-learning\target\test-classes;D:\java\eclipse\my_workspace\java-learning\target\classes;E:\working\maven_lib\commons-logging\commons-logging\1.1.1\commons-logging-1.1.1.jar;E:\working\maven_lib\junit\junit\4.7\junit-4.7.jar;E:\working\maven_lib\org\springframework\spring\2.5.6\spring-2.5.6.jar com.sdo.lee.java.learning.compiler.Test 
再查看了一下工程的.classpath文件:

view plain
<?xml version="1.0" encoding="UTF-8"?> 
<classpath> 
    <classpathentry kind="output" path="target/classes"/> 
    <classpathentry including="**/*.java" kind="src" path="src/main/java"/> 
    <classpathentry including="**/*.java" kind="src" output="target/test-classes" path="src/test/java"/> 
    <classpathentry excluding="**/*.java" kind="src" path="src/main/resources/META-INF/spring"/> 
    <classpathentry excluding="META-INF/spring/|**/*.java" kind="src" path="src/main/resources"/> 
    <classpathentry kind="var" path="M2_REPO/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar"/> 
    <classpathentry kind="var" path="M2_REPO/junit/junit/4.7/junit-4.7.jar"/> 
    <classpathentry kind="var" path="M2_REPO/org/springframework/spring/2.5.6/spring-2.5.6.jar"/> 
    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> 
</classpath> 
发现更改上面的其中一条<classpathentry>如下,即可得到预期结果。

<classpathentry including="**/*.java" kind="src" output="target/classes" path="src/main/java"/>

同时,更改后的eclipse命令行参数如下:

view plain
E:\java6\bin\javaw.exe -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:4785 -Dfile.encoding=UTF-8 -classpath D:\java\eclipse\my_workspace\java-learning\target\classes;D:\java\eclipse\my_workspace\java-learning\target\test-classes;E:\working\maven_lib\commons-logging\commons-logging\1.1.1\commons-logging-1.1.1.jar;E:\working\maven_lib\junit\junit\4.7\junit-4.7.jar;E:\working\maven_lib\org\springframework\spring\2.5.6\spring-2.5.6.jar com.sdo.lee.java.learning.compiler.Test 
eclipse在运行时是如何根据.classpath文件来添加命令行参数的-classpath属性不得而知。

(初步实践发现:eclipse可能是逐条读取classpathentry,一旦发现指定output属性,即将其path属性指定的路径添加 到命令行参数的-classpath属性,并在最后将kind="output"指定的的默认path属性路径也添加到-classpath属性中。至于 是否正确,有待验证。)

对于相对路径的解析('/','.','..',''等),java的源代码如下:

Class.class

view plain
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); 
    } 
view plain
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; 
    } 
ClassLoader.class

view plain
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; 
    } 
view plain
public static URL getSystemResource(String name) { 
    ClassLoader system = getSystemClassLoader(); 
    if (system == null) { 
        return getBootstrapResource(name); 
    } 
    return system.getResource(name); 
    } 
view plain
protected URL findResource(String name) { 
    return null; 
    } 
view plain
private static URL getBootstrapResource(String name) { 
        try { 
            // If this is a known JRE resource, ensure that its bundle is  
            // downloaded.  If it isn't known, we just ignore the download 
            // failure and check to see if we can find the resource anyway 
            // (which is possible if the boot class path has been modified). 
            sun.jkernel.DownloadManager.getBootClassPathEntryForResource(name); 
        } catch (NoClassDefFoundError e) { 
            // This happens while Java itself is being compiled; DownloadManager 
            // isn't accessible when this code is first invoked.  It isn't an 
            // issue, as if we can't find DownloadManager, we can safely assume 
            // that additional code is not available for download. 
        } 
    URLClassPath ucp = getBootstrapClassPath(); 
    Resource res = ucp.getResource(name); 
    return res != null ? res.getURL() : null; 
    } 
从上面的源代码可以看到,Class类的getResource方法先是进行一个简单的路径解析,然后将其委托给其装载器去处理,装载器则是委托 其父装载器处理,一直到bootstrap装载器。bootstrap装载器是非java实现的,sun没有开源,具体如何实现不得可知,但有一点可以肯 定的是根据classpath来进行装载。java的官方文档说明中讲到:classloader装载类时的优先级(-jar命令参数 > -classpath命令参数 > CLASSPATH环境变量 > 默认的'.'指定的当前路径)。详细可参考官方文档How Classes are Found一文。

在相对路径解析时,我们发现所有的相对路径是以'/'指定的.class文件根目录来进行解析。观察上面的eclipse命令行参数猜想:类装载 器默认将-classpath参数指定的第一个目录作为'/'指定的根目录,即getResource('/')返回的结果,而一旦/后面跟有具体的文件 时(如'./Test.class'),则会顺序遍历classpath参数指定的所有路径,找到相应的Test.class文件,返回其绝对路径的 URL,如有多个,则返回第一个找到的Test.class文件的路径。

为求证,在纯命令行环境下进行实践,实践结果与猜想的一致。

总结:对于这种采用构建工具进行构建的项目,往往指定有多个.class文件输出目录(对于只有一个.class文件输出目录时,不存在本文开始 处描述的问题),因此在采用getResource(String name)之类的涉及到相对路径的方法调用,要稍加注意,有时候结果可能与预期出现偏差。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics