java類加載器-系統類加載器


系統類加載器

 系統類加載器可能都耳詳能熟,但是為了完整點,還是先簡單的說說系統的類加載器吧。

 

public class Test {

	public static void main(String[] args) {
		ClassLoader cl1 = Test.class.getClassLoader().getParent().getParent();
		System.out.println(cl1);
		
		ClassLoader cl2 = Test.class.getClassLoader().getParent();
		System.out.println(cl2);
		
		ClassLoader cl3 = Test.class.getClassLoader();
		System.out.println(cl3);
	}
}

 

  打印的結果是:

    null
    sun.misc.Launcher$ExtClassLoader@dc6a77
    sun.misc.Launcher$AppClassLoader@1016632

 其實這就是jdk系統中用到的三個類加載器,其中null就是bootstrap classloader,因為是有c++實現,所以在此打印出null。ExtClassLoader就是Extension ClassLoader。AppClassLoader就是App ClassLoader或者叫做system classloader。他們負責加載各自指定位置下的類:

  1)Bootstrap ClassLoader

    負責加載$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++實現,不是ClassLoader子類

  2)Extension ClassLoader

    負責加載java平台中擴展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包

  3)App ClassLoader

    負責加載classpath中指定的jar包及目錄中class

這三個類加載器的關系通過代碼就能看出來:App ClassLoader的parent是Extension ClassLoader,Extension ClassLoader的parent是Bootstrap ClassLoader。

委托機制

所謂委托機制就是,當加載一個類時,App ClassLoader委托他的parent(Extension ClassLoader)去加載,Extension ClassLoader又委托他的parent加載直到Bootstrap ClassLoader,如果parent沒有加載成功,再由自身去加載。

以上三個類加載器,除了Bootstrap ClassLoader都是間接繼承自ClassLoader類(是一個抽象類不能實例化,但沒有抽象方法),委托機制的實現就被loadClass方法作為模板方法實現。以下就通過loadClass的源碼分析一下委托機制:

定制類加載器

   有時我們可能需要定制我們的類加載器以滿足我們的特殊需求。而且我們一般也不需要破壞委托機制(后邊會介紹一個破壞了委托機制的類加載器的例子)通常可以有兩種方法實現

 一、繼承URLClassLoader,比如

public class TestClassLoader extends URLClassLoader {

	public TestClassLoader(URL[] urls) {
		super(urls);
	}
	public TestClassLoader(URL[] urls,ClassLoader parent) {
		super(urls,parent);
	}
	
}

 你只需要調用父類構造方法,其它方法你一概不用重寫。這也是最簡單的實現。使用時只需要調用loadClass。缺點是你只能通過URL定位你要加載的class。

二、繼承ClassLoader類,重寫findClass方法。比如

public class TestClassLoader extends ClassLoader {

	private String basedir;
	
	public TestClassLoader(String basedir,ClassLoader parent){
		super(parent);
		this.basedir = basedir;
	}
	
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		Class<?> cls = null; 
        StringBuffer sb = new StringBuffer(basedir); 
        String classname = name.replace('.', File.separatorChar) + ".class";
        sb.append(File.separator + classname); 
        File classF = new File(sb.toString()); 
        if(!classF.exists()){
        	throw new ClassNotFoundException();
        }
        
        byte[] raw = new byte[(int) classF.length()];
		try {
			InputStream fin = new FileInputStream(classF);
			fin.read(raw);
	        fin.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
        
        cls = defineClass(name,raw,0,raw.length);
        return cls; 
	}
}

  在findClass方法里我們可以采用任意方式查找我們要加載的類,這里采用了讀文件的方式。使用時同樣是調用loadClass方法。

線程上下文類加載器

  線程上下文類加載器(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 的實現中都會用到。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM