类加载器&线程上下文加载器

Java魔法堂:类加载器入了个门

一、前言                            

  《Java魔法堂:类加载机制入了个门》中提及整个类加载流程中只有加载阶段作为码农的我们可以入手干预,其余均由JVM处理。本文将记录加载阶段的核心组件——类加载器的相关信息,以便日后查阅。若有纰漏请大家指正,谢谢。

  注意:以下内容基于JDK7和HotSpot VM。

 

二、类加载器种类及其关系                    

类加载器&线程上下文加载器_第1张图片

从上图可知Java主要有4种类加载器

1. Bootstrap ClassLoader(引导类加载器):作为JVM的一部分无法在应用程序中直接引用,由C/C++实现(其他JVM可能通过Java来实现)。负责加载/jre/lib目录 或 -Xbootclasspath参数所指定的目录 或 统属性sun.boot.class.path指定的目录 中特定名称的jar包。在JVM启动时将通过Bootstrap ClassLoader加载rt.jar,并初始化sun.misc.Launcher从而创建Extension ClassLoader和System ClassLoader实例,和将System ClassLoader实例设置为主线程的默认Context ClassLoader(线程上下文加载器)。

  注意:Bootstrap ClassLoader只会加载特定名称的类库,如rt.jar等。假如我们自己定义一个jar类库丢进/jre/lib目录下也不会被加载的!

  下面我们看看Bootstrap ClassLoader到底加载了哪些jar包吧!

复制代码
import java.net.*;
import sun.misc.*;

class Main{
  public static void main(String[] args){
    URL[] urls = Launcher.getBootstrapClassPath().getURLs();
    for (URL url : urls)
      System.out.println(url.toExternalForm());
  }
}
/* vim:!javac % & java Main 后输出
 * lib/resources.jar
 * lib/rt.jar
 * lib/sunrsasign.jar
 * lib/jsse.jar
 * lib/jce.jar
 * lib/charsets.jar
 * lib/jfr.jar
 * lib/classe
 */
复制代码

2. Extension ClassLoader(扩展类加载器):仅含一个实例,由 sun.misc.Launcher$ExtClassLoader 实现,负责加载/jre/lib/ext目录 或 系统属性java.ext.dirs所指定的目录 中的所有类库。

3. App/System ClassLoader(系统类加载器):仅含一个实例,由 sun.misc.Launcher$AppClassLoader 实现,可通过 java.lang.ClassLoader.getSystemClassLoader 获取。负责加载 ①系统环境变量ClassPath 或 ②-cp 或 系统属性java.class.path 所指定的目录下的类库。

4. Custom ClassLoader(用户自定义类加载器):可同时存在多个用户自定义的类加载器,具体如何定义请参考后文。

 

除了上面的4种类加载器外,JDK1.2开始引入了另一个类加载器——Context ClassLoader(线程上下文加载器)

5. Context ClassLoader(线程上下文加载器):默认为System ClassLoader,可通过Thread.currentThread().setContextClassLoader(ClassLoader)来设置,可通过ClassLoader Thread.currentThread().getContextClassLoader()来获取。每个线程均将Context ClassLoader预先设置为父线程的Context ClassLoader。该类加载器主要用于打破双亲委派模型,容许父类加载器通过子类加载器加载所需的类库。

 

三、双亲委派模型                        

  在介绍双亲委派模型前先看看以下示例:

复制代码
/*
 * Main.java文件
 */
import java.net.*;
import java.lang.reflect.*;

class Main{
  public static void main(String[] args) 
      throws ClassNotFoundException, MalformedURLException, IllegalAccessException, NoSuchMethodException, InstantiationException, InvocationTargetException{
    ClassLoader pClassLoader = ClassLoader.getSystemClassLoader(); // 以System ClassLoader作为父类加载器
    URL[] baseUrls = {new URL("file:/d:/testLib/")}; // 搜索类库的目录
    final String binaryName = "com.fsjohnhuang.HelloWorld"; // 需要加载的类的二进制名称

    ClassLoader userClassLoader1 = new URLClassLoader(baseUrls, pClassLoader);
    ClassLoader userClassLoader2 = new URLClassLoader(baseUrls, pClassLoader);
    Class clazz1 = userClassLoader1.loadClass(binaryName);
    Class clazz2 = userClassLoader2.loadClass(binaryName);
    Object instance1 = clazz1.newInstance();
    Object instance2 = clazz2.newInstance();
    // 调用say方法
    clazz1.getMethod("say").invoke(instance1);
    clazz2.getMethod("say").invoke(instance2);
    // 输出类的二进制名称
    System.out.println(clazz1.toString());
    System.out.println(clazz2.toString());

    // 比较两个类的地址是否相同
    System.out.println(clazz1 == clazz2);
    // 比较两个类是否相同或是否为继承关系
    System.out.println(clazz1.isAssignableFrom(clazz2));
    // 查看类型转换是否成功
    boolean ret = true;
    try{
        clazz2.cast(instance1);
    }
    catch(ClassCastException e){
        ret = false;
    }
    System.out.println(ret);
  } 
}
复制代码

   结果:

复制代码
Hello World!
Hello World!
class com.fsjohnhuang.HelloWorld
class com.fsjohnhuang.HelloWorld
false
false
false
复制代码

  奇了个怪了,为什么两个类的Class实例不一样呢?这是因为 对于任意一个类,都需要由加载它的类加载器和该类本身一同确立其在JVM中的唯一性。也就是说对于同一个类文件,通过不同的类加载器加载那么在JVM中就生成了不同的类。

  那现在问题来了,我们知道由java.lang.*(打包到rt.jar中)是由Bootstrap ClassLoader加载的,现在我闲着蛋疼自定义一个类加载器来加载java.lang.String,按照上面的定义那JVM中就有两个java.lang.String类了,然后出现下列问题:

复制代码
if (myString.newInstance() instanceof String){
  System.out.println("1"); // 绝对不会执行这一句
}
else{
   System.out.println("2");
}
复制代码

  注意:由于类会通过自身对应的类加载器加载其引用的其他类。若myString中还引用了其他类,那么将会通过我自定的类加载器来加载一次哦!

  假如会发生上述情况,真实项目中发生的问题就更大了。(注意:上述代码在真实环境绝对无法成立,自定义的类加载器本身就被限制为无法加载java.*的类哦!)

  双亲委派模型就是用于解决上述问题,越基础的类由越上层的类加载器进行加载,如Java API类库则有Bootstrap ClassLoader加载。具体如下:

  当一个类加载器收到类加载的请求,首先会将请求委派给父类加载器,这样一层一层委派到Bootstrap ClassLoader。然后加载器根据请求尝试搜索和加载类,若搜索失败则向子类加载器反馈信息(抛出ClassNotFoundException),然后子类加载器才尝试自己去加载。JAVA中采用组合的方式实现双亲委派模型,而不是继承的方式。

  不难发现Bootstrap、Extension和System三种类加载器默认的加载类的目录路径均是不同的,也可以说 类的来源地与类加载器应该是一一对应。位于同一来源地的类应该由相同的类加载器加载,而不是由其他类加载来加载,或者通过双亲委派模型将加载请求传递给相应的类加载器。因最基础的类库通过Bootstrap加载,其次则由Extension加载,应用程序的则由System来加载,应用程序动态依赖的功能模块则通过用户自定义类加载器加载。

 

四、非双亲委派模型                        

  双亲委派模型解决了类重复加载的乱象。但现在问题又来了,双亲委派模型仅限于子类加载器将加载请求转发到父类加载器,请求是单向流动的,那如果通过父类加载器加载一个在子类加载器管辖类来源的类,那怎么办呢?再说真的有这样的场景吗?

  首先我们将 “通过父类加载器加载一个在子类加载器管辖类来源的类” 具体化为 “在一个由Bootstrap ClassLoader加载的类中动态加载其他目录路径下的类库”,这样我们就轻松地找到JNDI、JAXP等SPI(Service Provider Interface)均符合这种应用场景。以下就以JAXP来介绍吧!

  JAXP(Java API for XML Processing),用于处理XML文档的API,接口和默认实现位于rt.jar中,但增强型的具体实现则由各个厂家提供且以第三方jar包的形式部署在项目的CLASSPATH下。其中抽象类 javax.xml.parsers.DocumentBuilderFactory的类方法newInstance(String factoryClassName, ClassLoader classLoader) 可根据二进制名称获取由各厂家具体实现的DocumentBuilderFactory实例。现在以 javax.xml.parsers.DocumentBuilderFactory.newInstance(" org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"null) 的调用形式来深入下去。

  首先假设newInstance内部是以以下方式加载类的

Class.forName("org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");
//
this.getClass().getClassLoader.loadClass("org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");

  由于DocumentBuilderFactory是由Boostrap ClassLoader加载的,因此上述操作结果是通过Bootstrap ClassLoader来加载第三方类库,结果必须是ClassNotFoundException的。也就是说我们需要获取System ClassLoader或它的子类加载器才能成功加载这个类。

  首先想到的是通过ClassLoader.getSystemClassLoader()方法来获取System ClassLoader。然而JDK1.2又引入了另一个更灵活的方式,那就是Context ClassLoader(线程上下文类加载器,默认为System ClassLoader),通过Context ClassLoader我们可以获取System ClassLoader或它的子类加载器,从而可以加载CLASSPATH和其他路径下的类库。

  newInstance(String, ClassLoader)的实际实现是调用FactoryFinder.newInstance方法,而该方法则调用getProviderClass方法来获取Class实例,getProviderClass方法中则通过SecuritySupport的实例方法getContextClassLoader()来获取类加载器,代码片段如下:(意味着:是java核心库或框架代码主动使用使用上下文类加载器)

复制代码
 ClassLoader getContextClassLoader()
    throws SecurityException
  {
    return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction()
    {
      public Object run() {
        ClassLoader cl = null;

        cl = Thread.currentThread().getContextClassLoader();

        if (cl == null) {
          cl = ClassLoader.getSystemClassLoader();
        }
        return cl;
      }
    });
  }
复制代码

  注意:Context ClassLoader可是要慎用哦!因为可以通过setContextClassLoader方法动态设置线程上下文类加载器,也就是有可能每次调用时的类加载器均不相同(所管辖的目录路径也不相同),在并发环境下就更容易出问题了。

 补充例子:

破坏委派模型

问题:SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题

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

上下文类加载器为同样在 J2SE 中引入的类加载代理机制提供了 后门。 通常 JVM 中的类加载器是按照层次结构组织的,目的是每个类加载器(除了启动整个 JVM 的原初类加载器)都有一个父类加载器。当类加载请求到来时,类加载器通常首先将请求代理给父类加载器。只有当父类加载器失败后,它才试图按照自己的算法查找并定义当前类。 让核心 JNDI 类使用线程上下文类加载器,从而有效的打通类加载器层次结构,逆着代理机制的 方向使用类加载器
例子:

DriverManager

 //  Worker method called by the public getConnection() methods.
    private static Connection getConnection(
String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {
java.util.Vector drivers = null;
        /*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
synchronized(DriverManager.class) { 
 // synchronize loading of the correct classloader.
 if(callerCL == null) {
     callerCL = Thread.currentThread().getContextClassLoader();
  }    

 
if(url == null) {
   throw new SQLException("The url cannot be null", "08001");
}
    
println("DriverManager.getConnection(\"" + url + "\")");
    
if (!initialized) {
   initialize();
}


synchronized (DriverManager.class){ 
            // use the readcopy of drivers
   drivers = readDrivers;  
        }


// Walk through the loaded drivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for (int i = 0; i < drivers.size(); i++) {
   DriverInfo di = (DriverInfo)drivers.elementAt(i);
      
   // If the caller does not have permission to load the driver then 
   // skip it.
   if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {
println("    skipping: " + di);
continue;
   }
   try {
println("    trying " + di);
Connection result = di.driver.connect(url, info);
if (result != null) {
   // Success!
   println("getConnection returning " + di);
   return (result);
}
   } catch (SQLException ex) {
if (reason == null) {
   reason = ex;
}
   }
}


private static ClassgetCallerClass(ClassLoader callerClassLoader, 
String driverClassName)
 {
Class callerC = null;


try {
   callerC = Class.forName(driverClassName, true, callerClassLoader);
}
catch (Exception ex) {
   callerC = null;           // being very careful 
}


return callerC;
    }

如红色标注:DriverManager由BootstrapLoader加载,调用的Driver具体实现线程上下文类加载器加载,BootstrapLoader调用线程上下文类加载器,反过来,破坏了委托模型,“打通”


五、从源码理解                          

  首先我们看看ExtClassLoader和AppClassLoader是如何创建的,目光移到sun/misc/Launcher.java文件中,而ExtClassLoader和AppClassLoader则以Luancher的内部类的形式实现。在Launcher类进入初始化阶段时会创建一个Launcher实例,其构造函数中会实例化ExtClassLoader,然后以ExtClassLoader实例作为父类加载器来实例化AppClassLoader,并将AppClassLoader实例设置为主线程默认的Context ClassLoader。

复制代码
public Launcher()
  {
    ExtClassLoader localExtClassLoader;
    try
    {
      // 实例化ExtClassLoader
      localExtClassLoader = ExtClassLoader.getExtClassLoader();
    } catch (IOException localIOException1) {
      throw new InternalError("Could not create extension class loader");
    }

    try
    {
      // 实例化AppClassLoader
      this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);
    } catch (IOException localIOException2) {
      throw new InternalError("Could not create application class loader");
    }
    // 主线程的默认Context ClassLoader
    Thread.currentThread().setContextClassLoader(this.loader);

    String str = System.getProperty("java.security.manager");
    if (str != null) {
      SecurityManager localSecurityManager = null;
      if (("".equals(str)) || ("default".equals(str)))
        localSecurityManager = new SecurityManager();
      else
        try {
          localSecurityManager = (SecurityManager)this.loader.loadClass(str).newInstance();
        } catch (IllegalAccessException localIllegalAccessException) {
        } catch (InstantiationException localInstantiationException) {
        } catch (ClassNotFoundException localClassNotFoundException) {
        }
        catch (ClassCastException localClassCastException) {
        }
      if (localSecurityManager != null)
        System.setSecurityManager(localSecurityManager);
      else
        throw new InternalError("Could not create SecurityManager: " + str);
    }
  }
复制代码

   ExtClassLoader和AppClassLoader均继承了java.net.URLClassLoader,并且仅对类的加载、搜索目录路径作修改而已。如AppClassLoader的getAppClassLoader方法:

复制代码
 static class AppClassLoader extends URLClassLoader
  {
    public static ClassLoader getAppClassLoader(final ClassLoader paramClassLoader)
      throws IOException
    {
      // 获取搜索、加载类的目录路径
      String str = System.getProperty("java.class.path");
      final File[] arrayOfFile = str == null ? new File[0] : Launcher.getClassPath(str);

      return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction()
      {
        public Launcher.AppClassLoader run() {
          URL[] arrayOfURL = this.val$s == null ? new URL[0] : Launcher.pathToURLs(arrayOfFile);
          // 设置类加载器的搜索、加载类的目录路径,并创建一个类加载器实例
          return new Launcher.AppClassLoader(arrayOfURL, paramClassLoader);
        }
      });
    }
复制代码

  在研究URLClassLoader之前我们先看看java.lang.ClassLoader,除Bootstrap ClassLoader外所有类加载器必须继承ClassLoader。还记得 ClassLoader.getSystemClassLoader().loadClass("org.apache.xerces.jaxp.DocumentBuilderFactoryImpl") 吧,现在我们就从loadClass出发,看看整个类加载机制吧!

复制代码
protected Class loadClass(String paramString, boolean paramBoolean)
    throws ClassNotFoundException
  {
    synchronized (getClassLoadingLock(paramString))
    {
      // 检查该类是否已加载过,若已加载过则返回缓存中的Class实例
      Class localClass = findLoadedClass(paramString);
      // 下面是双亲委派模型的具体实现
      if (localClass == null) {
        long l1 = System.nanoTime();
        try {
          // 若有父类加载器则将加载请求传递到父类加载器
          // 若parent变量为null则表示父类加载器是Bootstrap ClassLoader,同样将加载请求传递到父类加载器
          if (this.parent != null)
            localClass = this.parent.loadClass(paramString, false);
          else {
            localClass = findBootstrapClassOrNull(paramString);
          }
        }
        catch (ClassNotFoundException localClassNotFoundException)
        {
            // 父类加载器无法加载给类时则抛出异常
        }

        if (localClass == null)
        {
          long l2 = System.nanoTime();
          // 开始加载类了!
          localClass = findClass(paramString);

          PerfCounter.getParentDelegationTime().addTime(l2 - l1);
          PerfCounter.getFindClassTime().addElapsedTimeFrom(l2);
          PerfCounter.getFindClasses().increment();
        }
      }
      // 对类执行解析操作
      if (paramBoolean) {
        resolveClass(localClass);
      }
      return localClass;
    }
  }
复制代码

  可以看到loadClass方法内部主要为双亲委派模型的实现,实际的类加载操作是在findClass方法中实现的。另外由于不允许同一个类加载器重复加载同一个类,因此当对同一个类重复进行加载操作时,则通过findLoadedClass方法来返回已有的Class实例。

  ClassLoader中指提供findClass的定义,具体实现由子类提供。而URLClassLoader的findClass则是通过URLClassPath实例来获取类的二进制数据,然后调用defineClass对二进制数据进行初步验证,然后在由ClassLoader的defineClass进行其余的验证后生成Class实例返回。

复制代码
protected Class findClass(final String paramString)
    throws ClassNotFoundException
  {
    try
    {
      return (Class)AccessController.doPrivileged(new PrivilegedExceptionAction()
      {
        public Class run() throws ClassNotFoundException {
          // ucp为URLClassPath实例
          // 通过URLClassPath实例获取类的二进制数据
          String str = paramString.replace('.', '/').concat(".class");
          Resource localResource = URLClassLoader.this.ucp.getResource(str, false);
          if (localResource != null) {
            try {
              // 调用URLClassLoader的defineClass方法验证类的二进制数据并返回Class实例
              return URLClassLoader.this.defineClass(paramString, localResource);
            } catch (IOException localIOException) {
              throw new ClassNotFoundException(paramString, localIOException);
            }
          }
          throw new ClassNotFoundException(paramString);
        }
      }
      , this.acc);
    }
    catch (PrivilegedActionException localPrivilegedActionException)
    {
      throw ((ClassNotFoundException)localPrivilegedActionException.getException());
    }
  }
复制代码

  总结一下, 类加载过程为loadClass -> findClass -> defineClass。loadClass为双亲委派的实现,defineClass为类数据验证和生成Class实例,findClass为获取类的二进制数据。

  那么我们自定义类加载器时只需重写findClass就可以加载不同路径下的类库了!

 

六、手动加载类吧,骚年!                   

  手动加载类的形式是多样的,具体如下:

  1. 利用现有的类加载器

复制代码
// 通过当前类的类加载器加载(会执行初始化)
Class.forName("二进制名称");
Class.forName("二进制名称", true, this.getClass().getClassLoader());

// 通过当前类的类加载器加载(不会执行初始化)
Class.forName("二进制名称", false, this.getClass().getClassLoader());
this.getClass().loadClass("二进制名称");

// 通过系统类加载器加载(不会执行初始化)
ClassLoader.getSystemClassLoader().loadClass("二进制名称");

// 通过线程上下文类加载器加载(不会执行初始化)
Thread.currentThread().getContextClassLoader().loadClass("二进制名称");
复制代码

  2. 利用URLClassLoader

URL[] baseUrls = {new URL("file:/d:/testLib/")};
URLClassLoader loader = new URLClassLoader(baseUrl, ClassLoader.getContextClassLoader());
Class clazz = loader.loadClass("com.fsjohnhuang.HelloWorld");

  3. 继承ClassLoader自定义类加载器

复制代码
public class MyClassLoader extends ClassLoader{
  private String dir;

  public MyClassLoader(String dir, ClassLoader parent){
    super(parent);
    this.dir = dir;
  }
  
  @Override
  protect Class findClass(String binaryName) throws ClassNotFoundException{
    String pathSegmentSeperator = System.getProperty("file.separator");
    String path = binaryName.replace(".", pathSegmentSeperator ).concat(".class");

    FileInputStream fis = new FileInputStream(dir + pathSegmentSeperator  + path);
    byte[] b = new byte[fis.available()];
    fis.read(b, 0, b.length);
    fis.close();
    return defineClass(binaryName, b, 0, b.length);
  }
}
复制代码

 

七、如何卸载类?                        

类卸载实质上就是GC对方法区(HotSpot中可称为永久代)的类数据进行垃圾回收。

虚拟机规范:
  A class or interface may be unloaded if and only if its class loader is unreachable. The bootstrap class loader is always reachable; as a result , system classes may never be unloaded.
  只有当加载该类型的类加载器实例( 非类加载器类型) 为unreachable 状态时,当前被加载的类型才被卸载. 启动类加载器实例永远为reachable 状态,由启动类加载器加载的类型可能永远不会被卸载。
Unreachable状态的解释:
  1 、A reachable object is any object that can be accessed in any potential continuing computation from any live thread.
  2 、finalizer-reachable: A finalizer-reachable object can be reached from some finalizable object through some chain of references, but not from any live thread. An unreachable object cannot be reached by either means.
也就是说 

1. 加载器的类实例已经被回收。

2. 类的实例已经被回收。

3. 类的Class实例没有被任何地方引用,无法在任何地方通过反射访问该类。

  对于Bootstrap、Ext和Sys类加载器来说正常情况下是不会被回收的,只有用户自定义类加载器才可以。通过 $ java -verbose:classMain 执行以下代码。

复制代码
import java.net.*;
import java.io.*;

public class Main{
    public  static class MyURLClassLoader  extends  URLClassLoader { 
       public  MyURLClassLoader() { 
          super (getMyURLs()); 
       } 

       private   static  URL[] getMyURLs() { 
        try  { 
           return   new  URL[]{ new  File ("d:/").toURL()}; 
        }  catch  (Exception e) { 
           e.printStackTrace(); 
           return   null ; 
        } 
      } 
    } 

    public static void main(String[] args) throws IOException{
     try  { 
            MyURLClassLoader classLoader =  new  MyURLClassLoader(); 
            Class classLoaded = classLoader.loadClass("RMDIR"); 
            System.out.println(classLoaded.getName()); 
   
            classLoaded =  null ; 
            classLoader =  null ; 
  
           System.out.println(" 开始GC"); 
           System.gc(); 
           System.out.println("GC 完成"); 
       System.in.read();
         }  catch  (Exception e) { 
             e.printStackTrace(); 
         } 
    }
}
复制代码

 

八、加载图片、视频等非类资源                   

  ClassLoader除了用于加载类外,还可以用于加载图片、视频等非类资源。同样是采用双亲委派模型将加载资源的请求传递到顶层的Bootstrap ClassLoader,在其管辖的目录下搜索资源,若失败才逐层返回逐层搜索。

相关的实例方法如下:

URL getResource(String name)
InputStream getResourceAsStream(String name)
Enumeration getResources(String name)

而相关的类方法均是调用系统类加载器的上述方法而已。

 



补充:

(转)Java获取CLASSPATH路径

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命令行调试就可成功。

可能很难,但是相信,坚持总会看到奇迹。 在结果还没有的时候,我唯一可以选择的就是努力奋斗,静下心来。 结果会慢慢的浮出水面。

九、总结                            

  若有纰漏请大家指正,谢谢!

  尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/4284515.html  ^_^肥仔John

 

十、参考                            

http://www.ibm.com/developerworks/cn/java/j-lo-classloader/

http://blog.csdn.net/zhoudaxia/article/details/35897057

《深入理解Java虚拟机 JVM高级特性与最佳实践》

 

你可能感兴趣的:(java)