Java:反射機制 機製

2020-08-08 20:36:37

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L2VQe5KA-1596890077591)(F:\JianShu_material\Java\图片\反射\反射机制.png)]

1. Java反射機制 機製概述

1.1 動態語言與反射

動態語言

是一類在執行時可以改變其結構的語言:例如新的函數、物件、甚至程式碼可以被引進,已有的函數可以被刪除或是其他結構上的變化。通俗點說就是在執行時程式碼可以根據某些條件改變自身結構

主要動態語言:Object-C、C#、JavaScript、PHP、Python、Erlang

反射

Reflection(反射)是被視爲動態語言的關鍵,反射機制 機製允許程式在執行期藉助於Reflection API取得任何類的內部資訊,並能直接操作任意物件的內部屬性及方法

載入完類之後,在堆記憶體的方法區中就產生了一個Class型別的物件(一個類只有一個Class物件),這個物件就包含了完整的類的結構資訊,我們可以通過這個物件看到類的結構。

這個物件就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象的稱之爲:反射

1.2 反射提供功能

  • 在執行時判斷任意一個物件所屬的類
  • 在執行時構造任意一個類的物件
  • 在執行時判斷任意一個類所具有的成員變數和方法
  • 在執行時獲取泛型資訊
  • 在執行時呼叫任意一個物件的成員變數和方法,包括私有
  • 在執行時處理註解
  • 生成動態代理

1.3 反射相關API

  • java.lang.Class:代表一個類
  • java.lang.reflect.Method:代表類的方法
  • java.lang.reflect.Field:代表類的成員變數
  • java.lang.reflect.Constructor:代表類的構造器
  • … …

2. 理解Class類並獲取Class範例

2.1 java.lang.Class類的理解

  1. 類的載入過程
    • 程式經過javac.exe命令以後,會生成一個或多個位元組碼檔案(.class結尾)
    • 接着我們使用java.exe命令對某個位元組碼檔案進行解釋執行,即把某個位元組碼檔案載入到記憶體中,此過程稱爲類的載入
    • 載入到記憶體中的類,我們就稱爲執行時類,此執行時類,就作爲Class的一個範例
  2. 換句話說,Class的範例就對應着一個執行時類
  3. 載入到記憶體中的執行時類,會快取一定的時間,在此時間之前,我們可以通過不同的方式來獲取此執行時類

2.2 獲取Class範例

四種方法獲取Class類的範例:

  • 方式一:呼叫執行時類的屬性:.class
  • 方式二:通過執行時類的物件,呼叫getClass()
  • 方式三:呼叫Class的靜態方法:forName(String classPath)
  • 方式四:使用類的載入器:ClassLoader (瞭解)
//匯入的包有:import org.junit.Test;

public class ReflectionTest1 {
    @Test
    public void test1() throws ClassNotFoundException {
        //方式一:呼叫執行時類的屬性:.class
        Class clazz1 = String.class;
        System.out.println(clazz1);//class java.lang.String

        //方式二:通過執行時類的物件,呼叫getClass()
        String s1 = new String();
        Class clazz2 = s1.getClass();
        System.out.println(clazz2);//class java.lang.String

        //方式三:呼叫Class的靜態方法:forName(String classPath)
        Class clazz3 = Class.forName("java.lang.String");
        System.out.println(clazz3);//class java.lang.String

        System.out.println(clazz1 == clazz2);//true
        System.out.println(clazz1 == clazz3);//true

        //方式四:使用類的載入器:ClassLoader (瞭解)
        ClassLoader classLoader = ReflectionTest1.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("java.lang.String");
        System.out.println(clazz4);//class java.lang.String
        System.out.println(clazz1 == clazz4);//true
    }
}

除了類以外,介面、陣列、列舉、註解基本數據型別、void也可以作爲Class類的範例

3. 類的載入與ClassLoader的理解

3.1 類的載入

當程式主動使用某個類時,如果該類還未被載入到記憶體中,則系統會通過如下三個步驟來對該類進行初始化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-61uN2r6E-1596890077594)(F:\JianShu_material\Java\图片\反射\001.png)]

3.2 ClassLoader的理解

類載入器的作用:將class檔案位元組碼內容載入到記憶體中,並將這些靜態數據轉換成方法區的執行時數據結構,然後在堆中生成一個代表這個類的java.lang.Class物件,作爲方法區中類數據的存取入口

JVM 規範定義瞭如下型別的類的載入器:

  • 引導類載入器:該載入器無法獲取
  • 擴充套件類載入器
  • 系統類載入器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-970aRHEH-1596890077601)(F:\JianShu_material\Java\图片\反射\002.png)]

//匯入的包有:import org.junit.Test;

public class ClassLoaderTest {
    @Test
    public void test1(){
        //對於自定義類:使用系統類載入器進行載入
        ClassLoader classLoader1 = ClassLoaderTest.class.getClassLoader();

        //呼叫系統載入器的getParent():獲取擴充套件類載入器
        System.out.println(classLoader1);//sun.misc.Launcher$AppClassLoader@18b4aac2
        ClassLoader classLoader2 = classLoader1.getParent();

        //呼叫擴充套件類載入器的getParent():無法獲取引導類載入器
        //引導類載入器主要負責載入Java的核心類庫,無法載入自定義類
        System.out.println(classLoader2);//sun.misc.Launcher$ExtClassLoader@a09ee92
        ClassLoader classLoader3 = classLoader2.getParent();
        System.out.println(classLoader3);//null
    }
}

4. 建立執行時類的物件

通過反射,建立執行時類的物件

//匯入的包有:import org.junit.Test;

public class ClassLoaderTest {
    @Test
    public void test2() throws IllegalAccessException, InstantiationException {
        Class<String> clazz = String.class;
        /*
        * newInstance():呼叫此方法,建立對應的執行時類的物件,其內部呼叫了執行時類的空參構造器
        *
        * 要想此方法正常的建立執行時類的物件,要求
        * 1.執行時類必須提供空參的構造器
        * 2.空參的構造器的存取許可權足夠,通常設定爲public
        */
        //由於建立Class類的物件時,規定了泛型,所以此處自動轉換型別
        String obj = clazz.newInstance();
        System.out.println(obj);
    }
}

5. 獲取執行時類的完整結構

5.1 獲取屬性結構

當我們建立好Class物件後,可以獲得其所有屬性

//匯入的包有:import org.junit.Test;import java.lang.reflect.Field;import java.lang.reflect.Modifier;

public class Test1 {
    @Test
    public void test1(){
        Class clazz = String.class;

        //獲取屬性結構
        //getFields():獲取當前執行時類及其父類別中宣告爲public存取許可權的屬性
        Field[] fields = clazz.getFields();
        for(Field f : fields){
            System.out.println(f);
        }

        //getDeclaredFields():獲取當前執行時類中宣告的所有屬性,不包含父類別中宣告的屬性
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            System.out.println(f);
        }
    }
    @Test
    public void test2(){
        Class clazz = String.class;
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            //1.獲取許可權修飾符
            int modifier = f.getModifiers();
            System.out.println(Modifier.toString(modifier));

            //2.獲取數據型別
            Class type = f.getType();
            System.out.println(type.getName());

            //3.獲取變數名
            String fName = f.getName();
            System.out.println(fName);
        }
    }
}

5.2 獲取方法結構

當我們建立好Class物件後,可以獲得其所有方法

//匯入的包有:import org.junit.Test;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.lang.reflect.Modifier;

public class Test1 {
    @Test
    public void test3(){
        Class clazz = String.class;
        
        //getMethods():獲取當前執行時類一起所有父類別中宣告爲public許可權的方法
        Method[] methods = clazz.getMethods();
        for(Method m : methods){
            System.out.println(m);
        }
        
        //getDeclaredMethods():獲取當前執行時類中宣告的所有方法,不包含父類別中宣告的方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for(Method m : declaredMethods){
            System.out.println(m);
        }
    }
}

我們也可以獲取方法的許可權修飾符,返回值型別,方法名,參數,註解,異常,由於使用不多,這裏不展開敘述

5.3 獲取構造器結構

當我們建立好Class物件後,可以獲得其構造器

//匯入的包有:import org.junit.Test;import java.lang.reflect.Constructor;

public class Test1 {
    @Test
    public void test4(){
        //getConstructors():獲取當前執行時類中宣告爲public的構造器
        Class clazz = String.class;
        Constructor[] constructors = clazz.getConstructors();
        for(Constructor c : constructors){
            System.out.println(c);
        }

        //getDeclaredConstructors():獲取當前執行時類中宣告的所有構造器
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for(Constructor c : declaredConstructors){
            System.out.println(c);
        }
    }
}

還可以獲得執行時類的父類別及其父類別的泛型、介面、所在包、註解,這裏不在一一演示

6. 呼叫執行時類的指定結構

6.1 呼叫執行時類的指定屬性

//匯入的包有:import org.junit.Test;import java.lang.reflect.Field;

//現在假設有一Person類,具有一般類所具有的屬性,方法,構造器
public class ReflectionTest2 {
    @Test
    public void test1() throws Exception {
        Class clazz = Person.class;

        //建立執行時類的物件
        Person p = (Person) clazz.newInstance();

        //1.getDeclaredField(String name):獲取執行時類中指定變數名的屬性
        Field name = clazz.getDeclaredField("name");
        //還有一種方式獲取執行時類中的指定變數,但一般不使用,因爲只能獲得Public屬性
      //Field name = clazz.getFueld("name")


        //2.保證當前屬性時可存取的
        name.setAccessible(true);
        //3.獲取、設定指定物件的此屬性
        name.set(p,"Tom");
        //4.輸出當前屬性的值
        System.out.println(name.get(p));
    }
}

6.2 呼叫執行時類的指定方法

//匯入的包有:import org.junit.Test;import java.lang.reflect.Method;

//現在假設有一Person類,具有一般類所具有的屬性,方法,構造器
public class ReflectionTest2 {
    @Test
    public void test2() throws Exception {
        Class clazz = Person.class;

        //建立執行時類的物件
        Person p = (Person) clazz.newInstance();
        /*
         1.獲取某個指定方法
         getDeclaredMethod():參數1:指明獲取方法的名稱,參數2:指明獲取的方法的形參列表
         */
        Method show = clazz.getDeclaredMethod("show", String.class);
        //2.保證當前方法是可存取的
        show.setAccessible(true);
        /*
        3.呼叫方法的invoke():參數1:方法的呼叫者,參數2:給方法形參賦值的實參
        invoke()的返回值即爲對應類中呼叫的方法的返回值
        若呼叫執行時類的靜態方法,則傳入的參數1爲Person.class
         */
        Object returnValue = show.invoke(p, "China");
    }
}

還可以呼叫執行時類的指定構造器,但是用非常少,這裏不再演示

7. 反射的應用:動態代理

7.1 代理模式概述

代理設計模式的原理

  • 使用一個代理將物件包裝起來, 然後用該代理物件取代原始物件
  • 任何對原始物件的呼叫都要通過代理
  • 代理物件決定是否以及何時將方法呼叫轉到原始物件上

代理模式分爲靜態和動態

  • 靜態代理,特徵是代理類和目標物件的類都是在編譯期間確定下來,不利於程式的擴充套件。同時,每一個代理類只能爲一個介面服務,這樣一來程式開發中必然產生過多的代理
  • 動態代理,是指客戶通過代理類來呼叫其它物件的方法,並且是在程式執行時根據需要動態建立目標類的代理物件,可以更加靈活和統一的處理衆多的方法

7.2 靜態代理舉例

interface ClothFactory{
    void produceCloth();
}
//代理類
class ProxyClothFactory implements ClothFactory{

    private ClothFactory factory;//用被代理物件進行範例化
    public ProxyClothFactory(ClothFactory factory){
        this.factory = factory;
    }
    @Override
    public void produceCloth() {
        System.out.println("代理工廠做準備工作");
        factory.produceCloth();
        System.out.println("代理工廠做後續工作");
    }
}
//被代理類
class NikeClothFactory implements ClothFactory{

    @Override
    public void produceCloth() {
        System.out.println("Nike工廠生產一批運動服");
    }
}
public class StaticProxyTest {
    public static void main(String[] args) {
        //建立被代理類的物件
        NikeClothFactory nike = new NikeClothFactory();
        //建立代理類的物件
        ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nike);

        proxyClothFactory.produceCloth();
    }
}
/*代理工廠做準備工作
Nike工廠生產一批運動服
代理工廠做後續工作*/