Java反射機制

w39發表於2021-09-09

反射機制概述

反射視為動態語言的關鍵,反射機制允許程式在執行期藉助於Reflection API取得任何類的內部資訊,並能直接操作認一物件的內部屬性及方法。 載入完類之後在,在堆記憶體的方法區就產生了一個Class型別的物件(一個類只有一個Class物件),這個物件就包含了完整的類的結構資訊。我們可以透過這個物件看到類的結構。這個物件就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象的稱之為:反射。圖片描述反射相關的主要API圖片描述獲取Class類的四種結構:

  1. 呼叫執行時類的屬性

Class clazz1 = Person.class; System.out.prinln(clazz1);
2. 透過執行時類的物件,呼叫getClass() Person p1 = new Person(); Class clazz2 = p1.getClass(); System.out.println(clazz2); 3. 呼叫Class的靜態方法:forName(String classPath) Class clazz3 = Class.forName("java.lang.String"); System.out.println(clazz3); 4. 使用類的載入器:ClassLoader ClassLoader classLoader = ReflectionTest.class.getClassLoader(); Class clazz4 = classLoader.loadClass("com.atguigu.java.Person"); System.out.println(clazz4);圖片描述

理解Class類並獲取Class例項

在反射之前,在一個類的外部,不可以透過Person類的物件呼叫其內部私有結構。(如:name、showNation()以及私有的構造器)。圖片描述反射之後對於Person的常規操作:圖片描述透過反射,可以呼叫Person類的私有構造器,私有屬性和私有方法。

  1. 呼叫私有構造器

Constructor cons1 = clazzz.getDeclaredConstructor(String.class); cons1.setAccessible(true);          // 私有的都需要執行這一步 Person p1 = (Person) cons1.newInstance("jerry"); System.out.println(p1); 2. 呼叫私有屬性 Field name = clazz.getDeclaredField("name"); name.setAccessible(true); name.set(p1,"HanMeimei"); System.out.prinln(p1); 3. 呼叫私有方法 Method showNation = clazz.getDeclaredMethod("showNation",String.class) ; showNamtion.setAccessible(true); showNation.invoke(p1,"中國"); // 執行私有方法 String nation = (String) showNation.invoke(p1,”中國"); //
System.out.prinln(nation); 相關疑惑?

  1. 透過直接new的方式或反射的方式都可以呼叫公共的結構,開發中到底用哪個?

建議:使用new的方式 什麼時候使用反射: 當我們編譯階段不確定要造哪個類的物件,那我們這時需要用到反射 2. 反射與封裝是否相矛盾

類的載入與ClassLoader的理解

  1. 類的載入過程:

程式經過java.exec命令以後,會生成一個或多個位元組碼檔案(.class結尾)。 接著我們使用java.exe命令對某個位元組碼檔案進行解釋執行。相當於將某個位元組碼檔案載入到記憶體中。此過程成為類的載入。載入到記憶體中的類,我們稱為執行時類,此執行時的類,就作為Class的一個例項。 2. Class的例項對應著一個執行時類 3. 載入到記憶體中的執行時類,會快取一定的時間。在此時間之內,我們可以透過不同的方式來獲取此執行時類圖片描述圖片描述

什麼是類的載入器

圖片描述三種類載入器:圖片描述普通讀取配置檔案的方式:圖片描述使用系統類載入器來讀取配置檔案圖片描述

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

Class clazz = Person.class; Person obj = (Person)clazz.newInstance();圖片描述

獲取執行時類的完整結構

獲取類的屬性:

Class clazz = Person.class;

Field[] fields =  clazz.getFields();    // 可以獲取當前類以及父類中定義的public屬性

Field[] declaredFields = clazz.getDeclaredFields();  // 可以獲取當前類中所有定義的屬性

獲取類屬性的內部結構:

for(Field f: declaredFields){

 獲取類屬性的許可權修飾符

 int modifier = f.getModifiers();

 System.out.print(Modifier.toString(modifier));

獲取類屬性的資料型別

Class type = f.getType();

System.out.println(type.getName());

獲取類屬性的變數名   

String fName = f.getName();

System.out.print(fName);

}

## 獲取類的方法

Class clazz = Person.class;

Method[] methods = clazz.getMethods(); // 可以獲取當前類以及父類中定義的public方法

Method[] methods = clazz.getDeclaredMethods(); // 可以獲取當前類中所有定義的方法

獲取類方法的內部結構:

獲取方法的註解:

 Annotation[] annos = m.getAnnotations();

 for(){sout(a));

 獲取許可權修飾符:

 System.out.print(Modifier.toString(m.getModifiers()));

 獲取返回值型別:

 System.out.print(m.getReturnType().getName());

獲取形參列表:

 Class[] parameterTypes = m.getParameterTypes();

 if(!(parameterTypes == null || parameterTyupes.length ==0)){

     for(Class p:parameterTypes){

         System.out.print(p.getName)

         }

     }

獲取丟擲的異常:

Class[] exceptionTypes = m.getExceptionTypes();

  if(!(exceptionTypes == null || exceptionTypes.length ==0)){

     for(Class p:exception  Types){

         System.out.print(p.getName)

         }

     }


獲取執行時類構造器結構

Class clazz = Person.class;

Constructor[] construcotrs = clazz.getConstructors();      // 獲取當前執行時類中宣告為public的構造器

for(Constructor c : constructors){

 System.out.println(c);

 }

Constructor[] construcotrs = clazz.getDeclaredConstructors();      // 獲取當前執行時類中所有構造器

for(Constructor c : constructors){

 System.out.println(c);

 }


獲取執行時類父類和父類的型別(功能性程式碼vs邏輯性程式碼)

// 獲取執行時類的父類

Class clazz = Person.class;

Class superclass = clazz.getSuperclass();

System.out.println(superclass);

// 獲取執行時類帶泛型的父類

Class clazz = Person.class;

Type genericSuperclass = clazz.getGenericSuperclass();

System.out.println(superclass);

// 獲取執行時類帶泛型父類的泛型

 Class clazz = Person.class;

 Type genericSuperclass = clazz.getGenericSuperclass();

 ParameterizedType paramType = (ParameterizedType) genericSuperclass;

 Type[] actualTypeArguments = paramType.getActualTypeArguments(); // 為什麼是陣列呢,因為像Map型別的泛型會有兩個引數:Map

 System.out.println(genericSuperclassj[0]o.getTypeName());


獲取執行時類實現的介面

Class clazz = Person.class;

Class[] interfaces = clazz.getInterfaces();

Class[] interfaces1 = clazz.getSuperclass.getInterfaces(); // 獲取父類的介面

for(Class c: interfaces){

System.out.println(c);


獲取執行時類所在的包

Class clazz = Person.class;

Package pack = clazz.getPackage();

System.out.prinln(pack);


獲取執行時類宣告的註解

Class clazz = Person.class;

Annotation[] annotaions = clazz.getAnnotations();

for(Annotation annos: annotations){

 System.out.println(annos);

 }


呼叫執行時類的指定結構(重要)

獲取和操作執行時類指定的屬性

Class clazz = Person.class; // 首先建立執行時類的物件 Person p = clazz.newInstance(); Field id = clazz.getField("id"); // 要求執行時類屬性宣告為public,通常不用此方法 Field id = clazz.getDeclaredField("id"); id.setAccessible(true); // public以下的許可權需要設定為true // 設定當前屬性的值 id.set(p,1001); // 獲取當前屬性的值 int pid = (int)id.get(p);

獲取和操作執行時類中指定的方法(掌握)

Class clazz = Person.class; Person p = (Person)  clazz.newInstance(); Method show = clazz.getDecalaredMethod("show",String.class); //引數1: 獲取指定方法的名稱,引數2:指明獲取方法的形參列表 show.setAccessible(true); show.invoke(p,"CHN") // 引數1:方法的呼叫者 引數2:給方法形參賦值的實參 String nation = (String)  show.invoke(p,"CHN")  // 有返回值的方法的呼叫 // 呼叫靜態方法 呼叫靜態方法不需要例項化 Method showStatic = clazz.getDeclaredMethod("showStatic"); showStatic.setAccessible(true); Object returnVal = showStatic.invoke(Person.class); // 括號裡面為null也可以,因為每個物件的static方法都是一樣的 System.out.prinln(returnVal); //null 如果是void方法的話返回值為null

獲取和操作執行時類中指定的構造器(不常用,常用newInstance)

Class clazz = Peson.class; Constructor cons = clazz.getDeclaredConstructor(String.class); cons.setAccessible(true); Person per = (Person) cons.newInstance("Tom"); System.out.println(per);

反射的應用:動態代理AOP(aspect orient proxy)(Sprint裡面會再講)

靜態代理

// 工廠介面:代理類需要代理做的事情

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{

    public void produceCloth(){

        System.out.println("Nike生產一批運動服");

    }

}

public class StaticProxyTest {

    public static void main(String[] argv){

        // 建立被代理類的物件

        ClothFactory nike = new NikeClothFactory();

        // 建立代理類物件

        ClothFactory proxyFactory = new ProxyClothFactory(nike);

        proxyFactory.produceCloth();

    }

}


動態代理:

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.lang.reflect.InvocationHandler;

// 被代理類需要讓代理類需要做的事情

interface Human{

    String getBelief();

    void eat(String food);

}

// 被代理類

class SuperMan implements Human{

    @Override

    public String getBelief(){

        return "I believe I can fly";

    }

    @Override

    public void eat(String food){

        System.out.println("我喜歡吃" + food);

    }

}

// 問題:

// 1. 如何根據發載入到記憶體中的被代理類,動態的建立一個代理類及其物件?

// 2. 當透過代理類的物件呼叫方法時,如何動態的去呼叫被代理類中的同名方法?

//建立代理類物件

class ProxyFactory{

    // obj為被代理類

    public static Object getProxyInstance(Object obj){

        MyInvocationHandler handler = new MyInvocationHandler();

        handler.bind(obj);

        // 建立一個代理類物件:引數1:獲取obj類的類載入器,引數2:獲取obj實現的介面,引數3:獲取obj中呼叫的方法

        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);

    }

}

class MyInvocationHandler implements InvocationHandler{

    private Object obj;

    // 繫結被代理類

    public void bind(Object obj){

        this.obj = obj;

    }

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 呼叫被代理類中的方法:method是反射的

        HumanUti1 util1 = new HumanUti1();

        HumanUtil2 util2 = new HumanUtil2();

        util1.method1();

        Object returnValue = method.invoke(obj,args);

        util2.method2();

        return returnValue;

    }

}

// 呼叫通用方法:

class HumanUti1{

    public void method1(){

        System.out.println("-------通用方法1-------");

    }

}

class HumanUtil2{

    public void method2(){

        System.out.println("-----通用方法2---------");

    }

}

public class ProxyTest {

    public static void main(String[] args) {

        SuperMan superMan = new SuperMan();

        // 例項化動態代理類

        Human proxyInstance = (Human)ProxyFactory.getProxyInstance(superMan);

        // 透過代理類物件呼叫方法時呼叫的都是被代理類中的方法

        String belief = proxyInstance.getBelief();

        System.out.println(belief);

        proxyInstance.eat("雞腿");

        // 代理類動態性的體現:

        NikeClothFactory nikeClothFactory = new NikeClothFactory();

        ClothFactory proxyClothFactory = (ClothFactory)ProxyFactory.getProxyInstance(nikeClothFactory);

        proxyClothFactory.produceCloth();

    }

}


動態代理複習:圖片描述靜態代理的舉例說明:圖片描述靜態代理的缺點:圖片描述動態代理的特點:圖片描述動態代理的實現:

  1. 如何根據載入到記憶體中的被代理類,動態建立一個代理類及其物件:透過Proxy.newProxyInstance()實現

  2. 當透過代理類的物件呼叫方法a時,如何動態的去呼叫被代理類中的同名方法a:透過InvocationHandler介面的實現類及其方法invoke()


作者:dandeseed
連結:
來源:掘金
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/854/viewspace-2797067/,如需轉載,請註明出處,否則將追究法律責任。

相關文章