系統類加載器
系統類加載器可能都耳詳能熟,但是為了完整點,還是先簡單的說說系統的類加載器吧。
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 的實現中都會用到。