取得ClassLoader的路徑
1. 取得目前類別的ClassLoader
clazz.getClassLoader()
2.取得目前執行緒上下文的ClassLoader
Thread.currentThread().getContextClassLoader();
3. 取得系統的ClassLoader
ClassLoader.getSystemClassLoader()
4. 取得呼叫者的ClassLoader
DriverManager.getCallerClassLoader
#ClassLoader原始碼解析
#(建議學習:Java視訊教學)
#概述
類別載入器是用來載入類別的對象,ClassLoader是一個抽象類別。如果我們給定了一個類別的二進位名稱,類別載入器應嘗試去定位或產生構成定義類別的資料。典型的策略是將給定的二進位名稱轉換為檔案名,然後去檔案系統中讀取這個檔案名稱所對應的class檔案。
每個Class物件都會包含一個定義它的ClassLoader的一個引用。
陣列類別的Class對象,不是由類別載入器去創建的,而是在Java運行期JVM根據需要自動建立的。對於數組類別的類別載入器來說,是透過Class.getClassLoader()傳回的,與數組當中元素類型的類別載入器是一樣的;如果數組當中的元素類型是一個原生類型,數組類別是沒有類別載入器的【程式碼一】。
應用實作了ClassLoader的子類別是為了擴充JVM動態載入類別的方式。
類別載入器典型情況下時可以被安全管理器所使用去標識安全性域問題。
ClassLoader類別使用了委託模型來尋找類別和資源,ClassLoader的每個實例都會有一個與之關聯的父ClassLoader,當ClassLoader被要求尋找一個類別或資源的時候,ClassLoader實例在自身嘗試尋找類別或資源之前會委託它的父類別載入器去完成。虛擬機器內建的類別載入器,稱為啟動類別載入器,是沒有父載入器的,但是可以作為一個類別載入器的父類別載入器【雙親委託機制】。
支援並發類別載入的類別載入器叫做並行類別載入器,要求在初始化期間透過ClassLoader.registerAsParallelCapable 方法註冊自身,ClassLoader類別預設被註冊為可以並行,但是如果它的子類別也是並行載入的話需要單獨去註冊子類別。
在委託模型不是嚴格的層次化的環境下,類別載入器需要並行,否則類別載入會導致死鎖,因為載入器的鎖在類別載入過程中是一直被持有的。
通常情況下,Java虛擬機以平台相關的形式從本地的檔案系統載入類別,例如在UNIX系統,虛擬機器從CLASSPATH環境所定義的目錄載入類別。
然而,有些類別並不是來自於檔案;它們是從其它來源得到的,例如網絡,或是由應用程式本身建構【動態代理】。定義類別(defineClass )方法會將位元組陣列轉換為Class的實例,而這個新定義類別的實例可以由Class.newInstance建立。由類別載入器建立的物件的方法和建構方法可能會引用其它的類,為了確定被引用的類,Java虛擬機會呼叫最初建立類別的類別載入器的loadClass方法。
二進位名稱:以字串參數的形式提供給CalssLoader的任一個類別名,必須是一個二進位的名稱,包含以下四種情況
- "java.lang. String" 正常類別
- "javax.swing.JSpinner$DefaultEditor" 內部類別
- "java.security.KeyStore\(Builder\)FileBuilder$1" KeyStore的內部類別Builder的內部類別FileBuilder的第一個匿名內部類別
- "java.net.URLClassLoader$3$1" URLClassLoader類別的第三個匿名內部類別的第一個匿名內部類別
#代碼一:
public class Test12 { public static void main(String[] args) { String[] strings = new String[6]; System.out.println(strings.getClass().getClassLoader()); // 运行结果:null Test12[] test12s = new Test12[1]; System.out.println(test12s.getClass().getClassLoader()); // 运行结果:sun.misc.Launcher$AppClassLoader@18b4aac2 int[] ints = new int[2]; System.out.println(ints.getClass().getClassLoader()); // 运行结果:null } }
loadClass方法
#loadClass的原始碼如下,loadClass方法載入擁有指定的二進位名稱的Class,預設依照以下順序尋找類別:
a)呼叫findLoadedClass(String)檢查這個類別是否被載入
b)呼叫父類別載入器的loadClass方法,如果父類別載入器為null,就會調用啟動類別載入器
c)呼叫findClass(String)方法尋找
使用上述步驟如果類別被找到且resolve為true,就會去呼叫resolveClass(Class)方法
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
findClass方法
#findClass的源码如下,findClass寻找拥有指定二进制名称的类,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委托机制,该方法会在检查完父类加载器之后被loadClass方法调用,默认返回ClassNotFoundException异常。
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
defineClass方法
defineClass的源码如下,defineClass方法将一个字节数组转换为Class的实例。
protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError { protectionDomain = preDefineClass(name, protectionDomain); String source = defineClassSourceLocation(protectionDomain); Class<?> c = defineClass1(name, b, off, len, protectionDomain, source); postDefineClass(c, protectionDomain); return c; }
自定义类加载器
/** * 继承了ClassLoader,这是一个自定义的类加载器 * @author 夜的那种黑丶 */ public class ClassLoaderTest extends ClassLoader { public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); Class<?> clazz = loader.loadClass("classloader.Test01"); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); } //------------------------------以上为测试代码--------------------------------- /** * 类加载器名称,标识作用 */ private String classLoaderName; /** * 从磁盘读物字节码文件的扩展名 */ private String fileExtension = ".class"; /** * 创建一个类加载器对象,将系统类加载器当做该类加载器的父加载器 * @param classLoaderName 类加载器名称 */ private ClassLoaderTest(String classLoaderName) { // 将系统类加载器当做该类加载器的父加载器 super(); this.classLoaderName = classLoaderName; } /** * 创建一个类加载器对象,显示指定该类加载器的父加载器 * 前提是需要有一个类加载器作为父加载器 * @param parent 父加载器 * @param classLoaderName 类加载器名称 */ private ClassLoaderTest(ClassLoader parent, String classLoaderName) { // 显示指定该类加载器的父加载器 super(parent); this.classLoaderName = classLoaderName; } /** * 寻找拥有指定二进制名称的类,重写ClassLoader类的同名方法,需要自定义加载器遵循双亲委托机制 * 该方法会在检查完父类加载器之后被loadClass方法调用 * 默认返回ClassNotFoundException异常 * @param className 类名 * @return Class的实例 * @throws ClassNotFoundException 如果类不能被找到,抛出此异常 */ @Override protected Class<?> findClass(String className) throws ClassNotFoundException { byte[] data = this.loadClassData(className); /* * 通过defineClass方法将字节数组转换为Class * defineClass:将一个字节数组转换为Class的实例,在使用这个Class之前必须要被解析 */ return this.defineClass(className, data, 0 , data.length); } /** * io操作,根据类名找到对应文件,返回class文件的二进制信息 * @param className 类名 * @return class文件的二进制信息 * @throws ClassNotFoundException 如果类不能被找到,抛出此异常 */ private byte[] loadClassData(String className) throws ClassNotFoundException { InputStream inputStream = null; byte[] data; ByteArrayOutputStream byteArrayOutputStream = null; try { this.classLoaderName = this.classLoaderName.replace(".", "/"); inputStream = new FileInputStream(new File(className + this.fileExtension)); byteArrayOutputStream = new ByteArrayOutputStream(); int ch; while (-1 != (ch = inputStream.read())) { byteArrayOutputStream.write(ch); } data = byteArrayOutputStream.toByteArray(); } catch (Exception e) { throw new ClassNotFoundException(); } finally { try { if (inputStream != null) { inputStream.close(); } if (byteArrayOutputStream != null) { byteArrayOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return data; } }
以上是一段自定义类加载器的代码,我们执行这段代码
classloader.Test01@7f31245a sun.misc.Launcher$AppClassLoader@18b4aac2
可以看见,这段代码中进行类加载的类加载器还是系统类加载器(AppClassLoader)。这是因为jvm的双亲委托机制造成的,private ClassLoaderTest(String classLoaderName)
将系统类加载器当做我们自定义类加载器的父加载器,jvm的双亲委托机制使自定义类加载器委托系统类加载器完成加载。
改造以下代码,添加一个path属性用来指定类加载位置:
public class ClassLoaderTest extends ClassLoader { public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); } //------------------------------以上为测试代码--------------------------------- /** * 从指定路径加载 */ private String path; ...... /** * io操作,根据类名找到对应文件,返回class文件的二进制信息 * @param className 类名 * @return class文件的二进制信息 * @throws ClassNotFoundException 如果类不能被找到,抛出此异常 */ private byte[] loadClassData(String className) throws ClassNotFoundException { InputStream inputStream = null; byte[] data; ByteArrayOutputStream byteArrayOutputStream = null; className = className.replace(".", "/"); try { this.classLoaderName = this.classLoaderName.replace(".", "/"); inputStream = new FileInputStream(new File(this.path + className + this.fileExtension)); byteArrayOutputStream = new ByteArrayOutputStream(); int ch; while (-1 != (ch = inputStream.read())) { byteArrayOutputStream.write(ch); } data = byteArrayOutputStream.toByteArray(); } catch (Exception e) { throw new ClassNotFoundException(); } finally { try { if (inputStream != null) { inputStream.close(); } if (byteArrayOutputStream != null) { byteArrayOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return data; } public void setPath(String path) { this.path = path; } }
运行一下
class:class classloader.Test01 classloader.Test01@7f31245a sun.misc.Launcher$AppClassLoader@18b4aac2
修改一下测试代码,并删除工程下的Test01.class文件
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/桌面/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz); Object object = clazz.newInstance(); System.out.println(object); System.out.println(object.getClass().getClassLoader()); }
运行一下
class:class classloader.Test01 classloader.Test01@135fbaa4 classloader.ClassLoaderTest@7f31245a
分析
改造后的两块代码,第一块代码中加载类的是系统类加载器AppClassLoader,第二块代码中加载类的是自定义类加载器ClassLoaderTest。是因为ClassLoaderTest会委托他的父加载器AppClassLoader加载class,第一块代码的path直接是工程下,AppClassLoader可以加载到,而第二块代码的path在桌面目录下,所以AppClassLoader无法加载到,然后ClassLoaderTest自身尝试加载并成功加载到。如果第二块代码工程目录下的Test01.class文件没有被删除,那么依然是AppClassLoader加载。
再来测试一块代码
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance(); System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest("loader"); loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/"); Class<?> clazz2 = loader2.loadClass("classloader.Test01"); System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance(); System.out.println(object2.getClass().getClassLoader()); }
结果显而易见,类由系统类加载器加载,并且clazz和clazz2是相同的。
class:2133927002 sun.misc.Launcher$AppClassLoader@18b4aac2 class:2133927002 sun.misc.Launcher$AppClassLoader@18b4aac2
再改造一下
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/桌面/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance(); System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest("loader2"); loader2.setPath("/home/fanxuan/桌面/"); Class<?> clazz2 = loader2.loadClass("classloader.Test01"); System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance(); System.out.println(object2.getClass().getClassLoader()); }
运行结果
class:325040804 classloader.ClassLoaderTest@7f31245a class:621009875 classloader.ClassLoaderTest@45ee12a7
ClassLoaderTest是显而易见,但是clazz和clazz2是不同的,这是因为类加载器的命名空间的原因。
我们可以通过设置父类加载器来让loader和loader2处于同一命名空间
public static void main(String[] args) throws Exception { ClassLoaderTest loader = new ClassLoaderTest("loader"); loader.setPath("/home/fanxuan/桌面/"); Class<?> clazz = loader.loadClass("classloader.Test01"); System.out.println("class:" + clazz.hashCode()); Object object = clazz.newInstance(); System.out.println(object.getClass().getClassLoader()); ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2"); loader2.setPath("/home/fanxuan/桌面/"); Class<?> clazz2 = loader2.loadClass("classloader.Test01"); System.out.println("class:" + clazz2.hashCode()); Object object2 = clazz2.newInstance(); System.out.println(object2.getClass().getClassLoader()); }
运行结果
class:325040804 classloader.ClassLoaderTest@7f31245a class:325040804 classloader.ClassLoaderTest@7f31245a
扩展:命名空间
1. 每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成
2. 在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
3. 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类
php中文网,大量的免费Java入门教程,欢迎在线学习!
以上是java類別載入器ClassLoader詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!