java 反射

Java提供的反射機制容許你在運行時檢查類的信息java

Java的類加載

Java在真正須要使用一個類時纔會去加載類,而不是在啓動程序時就載入全部的類,由於大多數使用者都只使用到程序的部分資源,在須要某些功能時再載入某些資源,可讓系統資源運用的更高效。git

類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在Jvm的方法區內,而後在區建立一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類的加載的最終產品是位於堆區中的Class對象,Class對象封裝了類在方法區內的數據結構,而且向Java程序員提供了訪問方法區內的數據結構的接口。程序員

Java 中的全部類型包括基本類型( int, long, float等等),即便是數組都有與之關聯的 Class 類的對象。

Class對象是由Jvm自動生成的,每當一個類被載入時,Jvm就自動爲其生成一個Class對象github

Class對象

實例.getClass()

經過Object的getClass()獲取每個實例對應的Class對象shell

String name = "hello";
Class stringClass = name.getClass();
System.out.println("類的名稱:" + stringClass.getName());
System.out.println("是否爲接口:" + stringClass.isInterface());
System.out.println("是否爲基本類型:" + stringClass.isPrimitive());
System.out.println("是否爲數組:" + stringClass.isArray());
System.out.println("父類名稱:" + stringClass.getSuperclass().getName());
複製代碼
類的名稱:java.lang.String
是否爲接口:false
是否爲基本類型:false
是否爲數組:false
父類名稱:java.lang.Object
複製代碼

類名.class

你也能夠直接使用一下方式來獲取String類的Class對象數組

Class stringClass = String.class;
複製代碼

Class.forName()

在一些應用中,你沒法事先知道使用者將載入什麼類別,你可使用Class的靜態方法forName()來動態加載類別緩存

Class c = Class.forName(args[0]);
System.out.println("類的名稱:" + c.getName());
System.out.println("是否爲接口:" + c.isInterface());
System.out.println("是否爲基本類型:" + c.isPrimitive());
System.out.println("是否爲數組:" + c.isArray());
System.out.println("父類名稱:" + c.getSuperclass().getName());
複製代碼
$ java Demo1 java.util.Scanner  
類的名稱:java.util.Scanner
是否爲接口:false
是否爲基本類型:false
是否爲數組:false
父類名稱:java.lang.Object
複製代碼

Class.forName()有兩個版本,上面的版本只指定了全限定類名,而另外一個版本可讓你指定類名,載入時是否執行靜態代碼塊,執行類加載器(ClassLoader)安全

static Class forName(String name, boolean initialize, ClassLoader loader) 複製代碼
ClassLoader loader = Thread.currentThread().getContextClassLoader();
// Class.forName() 加載類 默認會執行初始化塊
Class.forName("Test2");
// Class.forName() 加載類 第二個參數 能夠控制是否執行初始化塊
Class.forName("Test2", false, loader);

class Test2 {
    static {
        System.out.println("靜態初始化塊執行了!");
    }
}
複製代碼

從Class對象中獲取信息

Class對象表示所載入的類別,獲取Class對象後,你就能夠獲取類別相關的信息,入 package, constructor, field, method等信息。 而每一種信息,都有相對應的類別bash

  • package: java.lang.reflect.Package
  • constructor: java.lang.reflect.Constructor
  • field: java.lang.reflect.Field
  • method: java.lang.reflect.Method
Class c = Class.forName(args[0]);
System.out.println("包信息package:" + c.getPackage());
System.out.println("類修飾符modifier:" + c.getModifiers());
System.out.println("構造方法constructor:");
Arrays.stream(c.getDeclaredConstructors()).forEach(System.out::println);
System.out.println("成員變量fields:");
Arrays.stream(c.getDeclaredFields()).forEach(System.out::println);
複製代碼
$ java Demo1 java.util.ArrayList
包信息package:package java.util
類修飾符modifier:1
構造方法constructor:
public java.util.ArrayList(java.util.Collection)
public java.util.ArrayList()
public java.util.ArrayList(int)
成員變量fields:
private static final long java.util.ArrayList.serialVersionUID
private static final int java.util.ArrayList.DEFAULT_CAPACITY
private static final java.lang.Object[] java.util.ArrayList.EMPTY_ELEMENTDATA
private static final java.lang.Object[] java.util.ArrayList.DEFAULTCAPACITY_EMPTY_ELEMENTDATA
transient java.lang.Object[] java.util.ArrayList.elementData
private int java.util.ArrayList.size
private static final int java.util.ArrayList.MAX_ARRAY_SIZE
複製代碼

ClassLoader 類加載器

Java在須要使用類的時候纔會將類載入,Java中類的載入是由Class Loader來實現的.數據結構

當你嘗試執行java xxx命令時,java會嘗試找到JRE的安裝目錄,而後尋找 jvm.dll,接着啓動JVM並進行初始化操做,接着產生 BootstrapLoader,Bootstrap Loader會載入Extended Loader, 並設定Extended Loader 的parent 爲 BootstrapLoader, 接着Bootstrap Loader 會載入 Application Loader, 並將Application Loader 的parent 設定爲 Extended Loader

啓動類加載器

BootstrapLoader搜尋 sun.boot.library.path中指定的類, 你可使用 System.getProperty("sun.boot.library.path")來獲取

/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib
複製代碼

擴展類加載器

Extended Loader(sun.misc.Launcher$ExtClassLoader) 是由Java編寫的,會搜尋系統參數java.ext.dirs中指定的類別,能夠經過System.getProperty("java.ext.dirs")來獲取

/Users/dsying/Library/Java/Extensions:
/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext:
/Library/Java/Extensions:
/Network/Library/Java/Extensions:
/System/Library/Java/Extensions:/usr/lib/java
複製代碼

應用程序類加載器

Application Loader (sun.misc.Launcher$AppClassLoader) 是由Java編寫的,會搜尋系統參數java.class.path中指定的類別,能夠經過System.getProperty("java.class.path")來獲取, 在使用java xxx命令執行.class字節碼文件時,能夠經過-cp參數設定classpath

java –cp ./classes SomeClass
複製代碼

類加載器之間的關係

ClassLoader loader Thread.currentThread().getContextClassLoader();
// sun.misc.Launcher$AppClassLoader@18b4aac2 應用類加載器
System.out.println(loader);
// sun.misc.Launcher$ExtClassLoader@610455d6 擴展類加載器
System.out.println(loader.getParent());
// Bootstrap ClassLoader 啓動類加載器(用C語言實現,因此此處返回null)
System.out.println(loader.getParent().getParent());
複製代碼
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@610455d6
null
複製代碼

類加載有三種方式:

  1. 命令行啓動應用時候由JVM初始化加載
  2. 經過Class.forName()方法動態加載
  3. 經過ClassLoader.loadClass()方法動態加載
Class.forName()和ClassLoader.loadClass()區別
  • Class.forName():將類的.class文件加載到jvm中以外,還會對類進行解釋,執行類中的static塊;
  • ClassLoader.loadClass():只幹一件事情,就是將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance纔會去執行static塊。
  • Class.forName(name, initialize, loader)帶參函數也可控制是否加載static塊。而且只有調用了newInstance()方法採用調用構造函數,建立類的對象。

JVM類加載機制

  • 全盤負責,當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其餘Class也將由該類加載器負責載入,除非顯示使用另一個類加載器來載入
  • 父類委託,先讓父類加載器試圖加載該類,只有在父類加載器沒法加載該類時才嘗試從本身的類路徑中加載該類
  • 緩存機制,緩存機制將會保證全部加載過的Class都會被緩存,當程序中須要使用某個Class時,類加載器先從緩存區尋找該Class,只有緩存區不存在,系統纔會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩存區。這就是爲何修改了Class後,必須重啓JVM,程序的修改纔會生效

雙親委派模型

雙親委派模型的工做流程是:若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把請求委託給父加載器去完成,依次向上,所以,全部的類加載請求最終都應該被傳遞到頂層的啓動類加載器中,只有當父加載器在它的搜索範圍中沒有找到所需的類時,即沒法完成該加載,子加載器纔會嘗試本身去加載該類。

雙親委派機制:

  1. AppClassLoader加載一個class時,它首先不會本身去嘗試加載這個類,而是把類加載請求委派給父類加載器ExtClassLoader去完成。
  2. ExtClassLoader加載一個class時,它首先也不會本身去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader去完成。
  3. 若是BootStrapClassLoader加載失敗(例如在$JAVA_HOME/jre/lib裏未查找到該class),會使用ExtClassLoader來嘗試加載;
  4. ExtClassLoader也加載失敗,則會使用AppClassLoader來加載,若是AppClassLoader也加載失敗,則會報出異常ClassNotFoundException

ClassLoader源碼分析:

public Class<?> loadClass(String name)throws ClassNotFoundException {
    return loadClass(name, false);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
    // 首先判斷該類型是否已經被加載
    Class c = findLoadedClass(name);
    if (c == null) {
        //若是沒有被加載,就委託給父類加載或者委派給啓動類加載器加載
        try {
            if (parent != null) {
                //若是存在父類加載器,就委派給父類加載器加載
                c = parent.loadClass(name, false);
            } else {
                //若是不存在父類加載器,就檢查是不是由啓動類加載器加載的類,經過調用本地方法native Class findBootstrapClass(String name)
                c = findBootstrapClass0(name);
            }
        } catch (ClassNotFoundException e) {
                //若是父類加載器和啓動類加載器都不能完成加載任務,才調用自身的加載功能
                c = findClass(name);
            }
        }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}
複製代碼

雙親委派模型意義:

  • 系統類防止內存中出現多份一樣的字節碼
  • 保證Java程序安全穩定運行

自定義加載器

自定義類加載器通常都是繼承自ClassLoader類,從上面對loadClass方法來分析來看,咱們只須要重寫 findClass 方法便可。下面咱們經過一個示例來演示自定義類加載器的流程:

package com.github.hcsp.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MyClassLoader extends ClassLoader {
    // 存放字節碼文件的目錄
    private final File bytecodeFileDirectory;

    public MyClassLoader(File bytecodeFileDirectory) {
        this.bytecodeFileDirectory = bytecodeFileDirectory;
    }

    // 還記得類加載器是作什麼的麼?
    // "從外部系統中,加載一個類的定義(即Class對象)"
    // 請實現一個自定義的類加載器,將當前目錄中的字節碼文件加載成爲Class對象
    // 提示,通常來講,要實現自定義的類加載器,你須要覆蓋如下方法,完成:
    //
    // 1.若是類名對應的字節碼文件存在,則將它讀取成爲字節數組
    // 1.1 調用ClassLoader.defineClass()方法將字節數組轉化爲Class對象
    // 2.若是類名對應的字節碼文件不存在,則拋出ClassNotFoundException
    //
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getByteArrayFromFile(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        return defineClass(name, classData, 0, classData.length);
    }

    byte[] getByteArrayFromFile(String className) throws ClassNotFoundException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        File file = new File(bytecodeFileDirectory, className + ".class");
        int len = 0;
        try {
            byte[] bufferSize = new byte[1024];
            FileInputStream fis = new FileInputStream(file);
            while ((len = fis.read(bufferSize)) != -1) {
                bos.write(bufferSize, 0, len);
            }
        } catch (FileNotFoundException e) {
            throw new ClassNotFoundException();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bos.toByteArray();
    }

    public static void main(String[] args) throws Exception {
        File projectRoot = new File(System.getProperty("basedir", System.getProperty("user.dir")));
        MyClassLoader myClassLoader = new MyClassLoader(projectRoot);

        Class testClass = myClassLoader.loadClass("com.github.hcsp.MyTestClass");
        Object testClassInstance = testClass.getConstructor().newInstance();
        String message = (String) testClass.getMethod("sayHello").invoke(testClassInstance);
        System.out.println(message);
    }
}
複製代碼

自定義類加載器的核心在於對字節碼文件的獲取,若是是加密的字節碼則須要在該類中對文件進行解密。因爲這裏只是演示,我並未對class文件進行加密,所以沒有解密的過程.

其它

使用反射建立對象

你可使用Class的newInstance()方法來實例化

Class c = Class.forName(className);
Object obj = c.newInstance();
複製代碼

調用方法

使用反射能夠取回類中的方法,方法對應的類爲 java.lang.reflect.Method, 你可使用它的 invoke()方法來調用指定的方法

Class testClass = myClassLoader.loadClass("com.github.hcsp.MyTestClass");
Object testClassInstance = testClass.getConstructor().newInstance();
String message = (String) testClass.getMethod("sayHello").invoke(testClassInstance);
複製代碼
相關文章
相關標籤/搜索