上下文类加载器

  1. Bootstrap ClassLoader/启动类加载器主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作。
  2. Extension ClassLoader/扩展类加载器
    主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作。
  3. System ClassLoader/系统类加载器
    主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作。
  4. User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)
    在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。

类加载器的特性:

  1. 每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类。
  2. 为了实现java安全沙箱模型顶层的类加载器安全机制, java默认采用了 " 双亲委派的加载链 " 结构。

类图中, BootstrapClassLoader是一个单独的java类, 其实在这里, 不应该叫他是一个java类。因为,它已经完全不用java实现了。它是在jvm启动时, 就被构造起来的, 负责java平台核心库。

自定义类加载器加载一个类的步骤

ClassLoader 类加载逻辑分析, 以下逻辑是除 BootstrapClassLoader 外的类加载器加载流程:

  1. // 检查类是否已被装载过  
  2. Class c = findLoadedClass(name);   
  3. if (c == null ) {   
  4.       // 指定类未被装载过  
  5.       try {   
  6.           if (parent != null ) {   
  7.               // 如果父类加载器不为空, 则委派给父类加载  
  8.              c = parent.loadClass(name,  false );   
  9.          }  else {   
  10.               // 如果父类加载器为空, 则委派给启动类加载加载  
  11.              c = findBootstrapClass0(name);   
  12.          }   
  13.      }  catch (ClassNotFoundException e) {   
  14.           // 启动类加载器或父类加载器抛出异常后, 当前类加载器将其  
  15.           // 捕获, 并通过findClass方法, 由自身加载  
  16.          c = findClass(name);   
  17.      }   
  18. }  

线程上下文类加载器
java默认的线程上下文类加载器是 系统类加载器(AppClassLoader)。

  1. // Now create the class loader to use to launch the application  
  2. try  {   
  3.     loader = AppClassLoader.getAppClassLoader(extcl);   
  4. catch (IOException e) {   
  5.      throw new InternalError(   
  6. "Could not create application class loader" );   
  7. }    
  8.   
  9. // Also set the context class loader for the primordial thread.  
  10. Thread.currentThread().setContextClassLoader(loader);  
Java代码 复制代码  收藏代码
  1. // Now create the class loader to use to launch the application  
  2. try {   
  3.     loader = AppClassLoader.getAppClassLoader(extcl);   
  4. catch (IOException e) {   
  5.     throw new InternalError(   
  6. "Could not create application class loader" );   
  7. }    
  8.   
  9. // Also set the context class loader for the primordial thread.  
  10. Thread.currentThread().setContextClassLoader(loader);  
// Now create the class loader to use to launch the application
try {
    loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
    throw new InternalError(
"Could not create application class loader" );
} 

// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);

以上代码摘自sun.misc.Launch的无参构造函数Launch()。

使用线程上下文类加载器, 可以在执行线程中, 抛弃双亲委派加载链模式, 使用线程上下文里的类加载器加载类.
典型的例子有, 通过线程上下文来加载第三方库jndi实现, 而不依赖于双亲委派.
大部分java app服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。
还有一些采用 hotswap 特性的框架, 也使用了线程上下文类加载器, 比如 seasar (full stack framework in japenese).

线程上下文从根本解决了一般应用不能违背双亲委派模式的问题.
使java类加载体系显得更灵活.

随着多核时代的来临, 相信多线程开发将会越来越多地进入程序员的实际编码过程中. 因此,
在编写基础设施时, 通过使用线程上下文来加载类, 应该是一个很好的选择。

当然, 好东西都有利弊. 使用线程上下文加载类, 也要注意, 保证多根需要通信的线程间的类加载器应该是同一个,
防止因为不同的类加载器, 导致类型转换异常(ClassCastException)。

 

为什么要使用这种双亲委托模式呢?

  1. 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
  2. 考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。

java动态载入class的两种方式:

  1. implicit隐式,即利用实例化才载入的特性来动态载入class
  2. explicit显式方式,又分两种方式:
    1. java.lang.Class的forName()方法
    2. java.lang.ClassLoader的loadClass()方法

用Class.forName加载类

Class.forName使用的是被调用者的类加载器来加载类的。
这种特性, 证明了java类加载器中的名称空间是唯一的, 不会相互干扰。
即在一般情况下, 保证同一个类中所关联的其他类都是由当前类的类加载器所加载的。

  1. public static Class forName(String className)   
  2.       throws ClassNotFoundException {   
  3.       return forName0(className, true , ClassLoader.getCallerClassLoader());   
  4. }    
  5.   
  6. /** Called after security checks have been made. */  
  7. private static native Class forName0(String name, boolean  initialize,   
  8. ClassLoader loader)   
  9.       throws ClassNotFoundException;  
Java代码 复制代码  收藏代码
  1. public static Class forName(String className)   
  2.      throws ClassNotFoundException {   
  3.      return forName0(className, true , ClassLoader.getCallerClassLoader());   
  4. }    
  5.   
  6. /** Called after security checks have been made. */  
  7. private static native Class forName0(String name, boolean initialize,   
  8. ClassLoader loader)   
  9.      throws ClassNotFoundException;  
public static Class forName(String className)
     throws ClassNotFoundException {
     return forName0(className, true , ClassLoader.getCallerClassLoader());
} 

/** Called after security checks have been made. */
private static native Class forName0(String name, boolean initialize,
ClassLoader loader)
     throws ClassNotFoundException;

上面中 ClassLoader.getCallerClassLoader 就是得到调用当前forName方法的类的类加载器

static块在什么时候执行?

  • 当调用forName(String)载入class时执行,如果调用ClassLoader.loadClass并不会执行.forName(String,false,ClassLoader)时也不会执行.
  • 如果载入Class时没有执行static块则在第一次实例化时执行.比如new ,Class.newInstance()操作
  • static块仅执行一次

各个java类由哪些classLoader加载?

  • java类可以通过实例.getClass.getClassLoader()得知
  • 接口由AppClassLoader(System ClassLoader,可以由ClassLoader.getSystemClassLoader()获得实例)载入
  • ClassLoader类由bootstrap loader载入

NoClassDefFoundError和ClassNotFoundException

  • NoClassDefFoundError:当java源文件已编译成.class文件,但是ClassLoader在运行期间在其搜寻路径load某个类时,没有找到.class文件则报这个错
  • ClassNotFoundException:试图通过一个String变量来创建一个Class类时不成功则抛出这个异常

为什么要使用Thread context classloader

 

类A调用类B,B会要求调用它的类的类加载器来加载它,也就是B会要求加载A的加载器来加载B。这就会有个问题,如果他们在一起,那没关系,肯定某个classloader会把它们俩都加载好。但是如果A在/lib/ext文件夹中,而B在Classpath中呢?过程是这样的首先加载A,那么一层层上到Bootstrap Classloader,boot没找到所以ext自己找,找到了,没问题;加载B,因为A调用了B,所以也从bootstrap来找,没找到,然后A的ext classloader来找还是没找到,但是再也不会往下调用了,于是报出ClassNotFoundException。

但是现实生活中有很多应用,比如JDBC核心方法在核心库而驱动在扩展库,是必定在两个地方的,那怎么办呢?要用到Context ClassLoader我们在建立一个线程Thread的时候,可以为这个线程通过setContextClassLoader方法来指定一个合适的classloader作为这个线程的context classloader,当此线程运行的时候,我们可以通过getContextClassLoader方法来获得此context classloader,就可以用它来载入我们所需要的Class。

默认的是system classloader。利用这个特性,我们可以“打破”classloader委托机制了,父classloader可以获得当前线程的context classloader,而这个context classloader可以是它的子classloader或者其他的classloader,那么父classloader就可以从其获得所需的 Class,这就打破了只能向父classloader请求的限制了。这个机制可以满足当我们的classpath是在运行时才确定,并由定制的 classloader加载的时候,由system classloader(即在jvm classpath中)加载的class可以通过context classloader获得定制的classloader并加载入特定的class(通常是抽象类和接口,定制的classloader中是其实现),例如web应用中的servlet就是用这种机制加载的.

Thread.currentThread().getContextClassLoader()的意义:

父Classloader可以使用当前线程Thread.currentthread().getContextLoader()中指定的classloader中加载的类。

颠覆了父ClassLoader不能使用子Classloader或者是其它没有直接父子关系的Classloader中加载的类这种情况。

这个就是Context Class Loader的意义。



 实战演练


(1)步骤一

上图进行了这样一个实验:首先一个名为Class(1)的类中启动MainThread(其实就是这个类里面有main函数的意思啦),注意这个类的名字后面标出了其所在的路径(即ClassPath),然后在里面进行测试,发现目前它的装载器和当前线程(MainThread)的ContextClassLoader都是AppClassLoader。然后Class(1)启动了一个新线程Class(2)。这里的Class(2)是一个Thread的子类,执行Class(2)代码的线程我称之为Thread-0。

(2)步骤二

上图可以看到Class(2)的装载器和ContextClassLoader同样都是AppClassLoader。随后我在Class(2)中创建了一个新的URLCLassLoader,并用这个ClassLoader来载入另一个和Class(1)不在同一个ClassPath下的类Class(3)。此时我们就可以看到变化:即载入Class(3)的装载器是URLClassLoader,而ContextClassLoader还仍然是AppClassLoader。

(2)步骤三

最后我们在Class(3)中启动了一个线程类Class(4),发现Class(4)也是由URLClassLoader载入的,而此时ContextClassLoader仍然是AppClassLoader。

    在整个过程中,装载类的ClassLoader发生了变化,由于线程类Class(4)是由Class(3)启动的,所以装载它的类装载器就变成了URLClassLoader。与此同时,所有线程的ContextClassLoader都继承了生成该线程的ContextClassLoader--AppClassLoader。

 

如果我们在第二步的结尾执行了绿色框中的代码:setContextClassLoader(),则结果就会变成下面这个样子:

我们可以清楚地看到,由于Thread-0将其ContextClassLoader设置成了URLClassLoader,而Thread-1是在Thread-0里面生成的,所以就继承了其ContextClassLoader,变成了URLClassLoader。



有三个工程

ModelA

         

public class AActivator implements IActivator{

    @Override
    public void init() {
        System.out.println("load A plugin~~~~~~~~~");
        PluginCenter center =PluginCenter.getInstance();
        center.putNotice("A", new ANotice());
        
        Thread.currentThread().setContextClassLoader(PluginCenter.getInstance().getClassLoader("B"));
        MyThread m =new MyThread();
        Thread a =new Thread(m);
        a.start();
    }

}


public class ANotice implements INotice {

    @Override
    public void noticeName() {
        System.out.println("A notice");

    }

}



public class MyThread implements Runnable {

    @Override
    public void run() {
     //Aurl,父classloader是app,MyThread是由AActivator产生的,AActivator由Aurl载入的,所以MyThread也是Aurl载入
     System.out.println("~~~@@MyThread类~~~"+MyThread.class.getClassLoader());
     //Burl,由于AActivator类里设定了上下文,如果不设定还是APP(一直继承初始的线程类装载器)
     System.out.println("~~~@@当前线程~~~"+Thread.currentThread().getContextClassLoader());
     
     try {
         Class bog =  Class.forName("com.b.Bgo", true, Thread.currentThread().getContextClassLoader());
        // Class bog  = Thread.currentThread().getContextClassLoader().loadClass("com.b.Bgo");
        // Class bog  = PluginCenter.getInstance().getClassLoader("B").loadClass("com.b.Bgo");
         CenterGo b=(CenterGo) bog.newInstance();
         System.out.println("~~**~~"+b.getGo());
       
         
         
       //报错,Bgo由当前classloader Aurl加载,它的父classloader是APP,都找不到ModleB的类  
        // Bgo pp = new Bgo();
        // Bgo  bb=(Bgo) bog.newInstance();  
    } catch (Exception e) {
         e.printStackTrace();
    }

    }


}


modelB

public class BActivator implements IActivator{

    @Override
    public void init() {
        Bgo bgo =new Bgo();
        System.out.println("load B pugin~~~~~~~~~"+bgo.getGo());
        PluginCenter center =PluginCenter.getInstance();
        center.putNotice("B", new BNotice());
    }

}




public class Bgo implements CenterGo{

    private int b = 1;

    public int getGo() {
        return b;
    }

  
    
}


public class BNotice implements INotice {

    @Override
    public void noticeName() {
        System.out.println("B notice");

    }

}

ModleCenter

public interface CenterGo {
    public int getGo();
}




public interface IActivator {
	
	/**
	 * 第一次启动插件时,调用此方法 IPluginActivator的初始化最好在这个方法中实现
	 * 因为使用无参构造方法实例化IPluginActivator时plugin的信息并不一定完整
	 */
	public void init();

}


public class Inint {

    /**
     * @param args
     */
    public static void main(String[] args) throws Exception{
 
        
        String Apath = "D:/work_AUM/ModelA/bin/";
        String Bpath = "D:/work_AUM/ModelB/bin/";
        URL aurl =  new URL("file", "", -1, Apath);
        URL burl =  new URL("file", "", -1, Bpath);
        URLClassLoader Aloader =new URLClassLoader(new URL[]{aurl});
        URLClassLoader Bloader =new URLClassLoader(new URL[]{burl});
        
        PluginCenter center =PluginCenter.getInstance();
        center.putClassLoader("A", Aloader);
        center.putClassLoader("B", Bloader);
        
        IActivator i1 = (IActivator)Aloader.loadClass("com.a.AActivator").newInstance();
        IActivator i2 = (IActivator)Bloader.loadClass("com.b.BActivator").newInstance();;
        
         //当前线程是appClassLoader
        System.out.println("~~~~~A~~~~~"+i1.getClass().getClassLoader());//Aurl
        System.out.println("~~~~~B~~~~~"+i2.getClass().getClassLoader());//Burl
        
        i1.init();
        i2.init();
        
       
        center.dispathNotice("A");
        
      
       
    }

}



public interface INotice {
   public void noticeName();
}


public class PluginCenter {

    private static PluginCenter p = new PluginCenter();
    private PluginCenter(){
        
    }
    public static PluginCenter getInstance(){
        return p;
    }
    
    private Map<String,ClassLoader> map  =new HashMap<String,ClassLoader>();
    private Map<String,INotice> map2  =new HashMap<String,INotice>();
    
    public ClassLoader getClassLoader(String name){
        return map.get(name);
    }
    
    public void putClassLoader(String name,ClassLoader l){
        this.map.put(name, l);
    }
    
    public void putNotice(String name,INotice l){
        this.map2.put(name, l);
    }
    
    public void dispathNotice(String name){
        INotice i  = map2.get(name);
        i.noticeName();
    }
    public int getNo(){
        return 1;
    }
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值