Java學習筆記十九(Java的反射機制)

反射的概述:

重點掌握:
1、理解Class類並獲取Class實例;
2、創建運行時類的對象;
3、調用運行時類的指定結構;

關於反射的理解:
Reflection(反射)被視爲動態語言的關鍵,反射機制允許程序在執行期藉助Reflection API獲得任何類的內部屬性,並能夠直接操作任意對象的內部屬性和方法。

框架 = 反射 + 註解 + 設計模式

體會反射的動態性:

//體會反射的動態性
@Test
public void test2(){

    for(int i = 0;i < 100;i++){
        int num = new Random().nextInt(3);//0,1,2
        String classPath = "";
        switch(num){
            case 0:
                classPath = "java.util.Date";
                break;
            case 1:
                classPath = "java.lang.Object";
                break;
            case 2:
                classPath = "com.atguigu.java.Person";
                break;
        }

        try {
            Object obj = getInstance(classPath);
            System.out.println(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

創建一個指定類的對象:

/*
創建一個指定類的對象。
classPath:指定類的全類名
 */
public Object getInstance(String classPath) throws Exception {
   Class clazz =  Class.forName(classPath);
   return clazz.newInstance();
}

反射機制能提供的功能:
在這裏插入圖片描述
相關API:
java.lang.Class:反射的源頭
java.lang.reflect.Method
java.lang.reflect.Field
java.lang.reflect.Constructor

關於反射的幾個問題:
1、new+構造器和反射都能夠創建對象,但是一般情況下都是使用new的方式來解決。
2、什麼時候會使用反射的方式?
反射的特性:動態性;當編譯時無法確定對象的類型,必須要等到運行時才能夠進行確定時,就要使用反射的方式創建對象。l例如,服務器在運行時,並不知道你是要進行登錄還是進行註冊,必須等你發送請求時才能進行確定。

3、反射機制和封裝性是不是矛盾的?
封裝性的含義是通過權限修飾符來告訴使用人員,類中哪些方法可以被調出來使用,哪一些方法只是創造出來自己內部使用的,不建議調到外部使用,可能調用類內部其他的結構能更好的的完成任務。而反射可以調用私有的方法和屬性,只是表明Java中可以做到調用私有的結構,並不矛盾。

Class類的理解與獲取Class的實例:

類的加載過程----瞭解
在這裏插入圖片描述
在這裏插入圖片描述
類的加載器的作用:
在這裏插入圖片描述
類的加載器的分類:
在這裏插入圖片描述
Java類編譯、運行的執行的流程:
在這裏插入圖片描述
使用Classloader加載src目錄下的配置文件:(掌握)
主要知道兩種方式要求的文件存放的位置不同,Properties中key和value都要求是String。

@Test
    public void test2() throws Exception {

        Properties pros =  new Properties();
        //此時的文件默認在當前的module下。
        //讀取配置文件的方式一:
//        FileInputStream fis = new FileInputStream("jdbc.properties");
//        FileInputStream fis = new FileInputStream("src\\jdbc1.properties");
//        pros.load(fis);

        //讀取配置文件的方式二:使用ClassLoader
        //配置文件默認識別爲:當前module的src下
        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
        pros.load(is);

        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        System.out.println("user = " + user + ",password = " + password);
    }

1.Class類的理解:
1、類的加載過程:
程序經過javac.exe命令後,會生成一個或多個字節碼文件(.class結尾)。接着我們使用java.exe命令對某個字節碼文件進行解釋運行。相當於將某個字節碼文件加載到內存中。此過程稱爲類的加載。加載到內存中的類,我們稱之爲運行時類,此運行時類就作爲Class的一個實例。(換句話說Class類的實例對應着一個運行時類)

2、加載到內存中的運行時類,會緩存一段時間。在此時間內我們可以通過不同的方式獲取此運行時類。(且不同方式獲得的運行時類相同,都對應着同一個運行時類)

2.獲取Class實例的幾種方式:(前三種方式需要掌握)

//方式一:調用運行時類的屬性:.class
        Class clazz1 = Person.class;
        System.out.println(clazz1);
        //方式二:通過運行時類的對象,調用getClass()
        Person p1 = new Person();
        Class clazz2 = p1.getClass();
        System.out.println(clazz2);

        //方式三:調用Class的靜態方法:forName(String classPath)
        Class clazz3 = Class.forName("com.atguigu.java.Person");
//        clazz3 = Class.forName("java.lang.String");
        System.out.println(clazz3);

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

        //方式四:使用類的加載器:ClassLoader  (瞭解)
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
        System.out.println(clazz4);

        System.out.println(clazz1 == clazz4);

總結:創建類的對象的方式
方式一:new + 構造器
方式二:要創建Xxx類的對象,可以考慮:Xxx、Xxxs、XxxFactory、XxxBuilder類中查看是否有
靜態方法的存在。可以調用其靜態方法,創建Xxx對象。
方式三:通過反射

3、Class實例可以是哪些結構的說明:
在這裏插入圖片描述
補充:
對於數組而言。只要元素類型和維度一樣就是同一個Class:

int[] a = new int[10];
        int[] b = new int[100];
        Class c10 = a.getClass();
        Class c11 = b.getClass();
        // 只要數組的元素類型與維度一樣,就是同一個Class
        System.out.println(c10 == c11);//true

創建運行時類的對象:

1、代碼舉例:

Class<Person.> clazz = Person.class;
Person obj = clazz.newInstance;

2、說明:
newInstance():調用此方法創建對應的運行時類的對象。內部調用了運行時類的空參構造器。

要想此方法正常的運行,要求:
1、運行時類要提供空參的構造器;
2、此空參構造器的權限要夠調用,通常設置爲public的。

補充: 在javabean中要求提供一個public的空參構造器的原因:
1、便於通過反射創建運行時類的對象;
2、便於子類默認調用此構造器時存在。

反射應用二:獲取運行時類的完整結構“

我們可以通過反射,獲取對應的運行時類中所有的屬性、方法、構造器、父類、接口、父類的泛型、包、註解、異常等。。。。”
幾個需要關注的:(其他了解就行)
獲取父類的泛型(JDBC可以用到);
獲取實現的接口(動態代理);
獲取註解(後面框架)

典型代碼:

@Test
public void test1(){

    Class clazz = Person.class;

    //獲取屬性結構
    //getFields():獲取當前運行時類及其父類中聲明爲public訪問權限的屬性
    Field[] fields = clazz.getFields();
    for(Field f : fields){
        System.out.println(f);
    }
    System.out.println();

    //getDeclaredFields():獲取當前運行時類中聲明的所屬性。(不包含父類中聲明的屬性
    Field[] declaredFields = clazz.getDeclaredFields();
    for(Field f : declaredFields){
        System.out.println(f);
    }
}

@Test
public void test1(){

    Class clazz = Person.class;

    //getMethods():獲取當前運行時類及其所父類中聲明爲public權限的方法
    Method[] methods = clazz.getMethods();
    for(Method m : methods){
        System.out.println(m);
    }
    System.out.println();
    //getDeclaredMethods():獲取當前運行時類中聲明的所方法。(不包含父類中聲明的方法
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for(Method m : declaredMethods){
        System.out.println(m);
    }
}

/*
    獲取構造器結構

     */
    @Test
    public void test1(){

        Class clazz = Person.class;
        //getConstructors():獲取當前運行時類中聲明爲public的構造器
        Constructor[] constructors = clazz.getConstructors();
        for(Constructor c : constructors){
            System.out.println(c);
        }

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

    }

    /*
    獲取運行時類的父類

     */
    @Test
    public void test2(){
        Class clazz = Person.class;

        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);
    }

    /*
    獲取運行時類的帶泛型的父類

     */
    @Test
    public void test3(){
        Class clazz = Person.class;

        Type genericSuperclass = clazz.getGenericSuperclass();
        System.out.println(genericSuperclass);
    }

    /*
    獲取運行時類的帶泛型的父類的泛型

    代碼:邏輯性代碼  vs 功能性代碼
     */
    @Test
    public void test4(){
        Class clazz = Person.class;

        Type genericSuperclass = clazz.getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) genericSuperclass;
        //獲取泛型類型
        Type[] actualTypeArguments = paramType.getActualTypeArguments();
//        System.out.println(actualTypeArguments[0].getTypeName());
        System.out.println(((Class)actualTypeArguments[0]).getName());
    }

    /*
    獲取運行時類實現的接口
     */
    @Test
    public void test5(){
        Class clazz = Person.class;

        Class[] interfaces = clazz.getInterfaces();
        for(Class c : interfaces){
            System.out.println(c);
        }

        System.out.println();
        //獲取運行時類的父類實現的接口
        Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
        for(Class c : interfaces1){
            System.out.println(c);
        }

    }
    /*
        獲取運行時類所在的包

     */
    @Test
    public void test6(){
        Class clazz = Person.class;

        Package pack = clazz.getPackage();
        System.out.println(pack);
    }

    /*
        獲取運行時類聲明的註解

     */
    @Test
    public void test7(){
        Class clazz = Person.class;

        Annotation[] annotations = clazz.getAnnotations();
        for(Annotation annos : annotations){
            System.out.println(annos);
        }
    }

反射應用三:調用運行時類的指定結構:(需要掌握)

@Test
public void testField1() throws Exception {
    Class clazz = Person.class;

    //創建運行時類的對象
    Person p = (Person) clazz.newInstance();

    //1. getDeclaredField(String fieldName):獲取運行時類中指定變量名的屬性
    Field name = clazz.getDeclaredField("name");

    //2.保證當前屬性是可訪問的
    name.setAccessible(true);
    //3.獲取、設置指定對象的此屬性值
    name.set(p,"Tom");

    System.out.println(name.get(p));
}
調用指定的方法:
 @Test
    public void testMethod() 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()的返回值即爲對應類中調用的方法的返回值。
         */
        Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
        System.out.println(returnValue);

        System.out.println("*************如何調用靜態方法*****************");

        // private static void showDesc()

        Method showDesc = clazz.getDeclaredMethod("showDesc");
        showDesc.setAccessible(true);
        //如果調用的運行時類中的方法沒返回值,則此invoke()返回null
//        Object returnVal = showDesc.invoke(null);
        Object returnVal = showDesc.invoke(Person.class);
        System.out.println(returnVal);//null

    }

調用指定的構造器:
@Test
public void testConstructor() throws Exception {
    Class clazz = Person.class;

    //private Person(String name)
    /*
    1.獲取指定的構造器
    getDeclaredConstructor():參數:指明構造器的參數列表
     */

    Constructor constructor = clazz.getDeclaredConstructor(String.class);

    //2.保證此構造器是可訪問的
    constructor.setAccessible(true);

    //3.調用此構造器創建運行時類的對象
    Person per = (Person) constructor.newInstance("Tom");
    System.out.println(per);

}

反射的應用四:動態代理(以及面向AOP)

1、代理模式的原理:
使用一個代理將原始對象包裝起來,用代理對象取代原始對象。任何對原始對象的調用都要通過代理對象來進行,代理對象決定是否以及何時將方法調用轉移到原始對象上。

靜態代理:
1、實現Runnable接口的方法創建多線程。

Class MyThread implements Runnable{} //相當於被代理類
Class Thread implements Runnable{} //相當於代理類
main(){
MyThread t = new MyThread();
Thread thread = new Thread(t);
thread.start();//啓動線程;調用線程的run()
}

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) {
        //創建被代理類的對象
        ClothFactory nike = new NikeClothFactory();
        //創建代理類的對象
        ClothFactory proxyClothFactory = new ProxyClothFactory(nike);

        proxyClothFactory.produceCloth();
    }
}

靜態代理的缺點:
1、代理類和目標對象都是在編譯器就確定下來了,不利於程序的擴展。
2、每個代理類只能爲一個接口服務,這樣在程序開發中會產生過多的代理類。

動態代理的特點:
動態代理是指通過代理類來調用目標對象的方法,並且是在程序運行時根據需要動態的創建目標類的代理對象。

動態代理的實現:
動態代理的實現需要解決兩個問題:
1、問題一:如何根據加載到內存中的被代理類,動態的創建一個代理類及其對象。 (通過Proxy.newProxyInstance()實現,三個參數分別是被代理類的同加載器;被代理類實現的接口;最後一個是實現InvocationHandler接口的實現類,用於定位方法)

2、問題二:當通過代理類的對象調用方法a時,如何動態的去調用被代理類中的同名方法a。(通過InvocationHandler接口的實現類及其方法invoke(),當代理類調用方法a是會轉換爲調用該實現類中的invoke()方法)。

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);
    }
}

class HumanUtil{

    public void method1(){
        System.out.println("====================通用方法一====================");

    }

    public void method2(){
        System.out.println("====================通用方法二====================");
    }

}


class ProxyFactory{
    //調用此方法,返回一個代理類的對象。解決問題一
    public static Object getProxyInstance(Object obj){//obj:被代理類的對象
        MyInvocationHandler handler = new MyInvocationHandler();

        handler.bind(obj);

        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
//這裏接口的傳入是爲了和obj實現同一個接口,可以調用被代理類的方法。
    }

}

class MyInvocationHandler implements InvocationHandler{

    private Object obj;//需要使用被代理類的對象進行賦值

    public void bind(Object obj){
        this.obj = obj;
    }

    //當我們通過代理類的對象,調用方法a時,就會自動的調用如下的方法:invoke()
    //將被代理類要執行的方法a的功能就聲明在invoke()中
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        HumanUtil util = new HumanUtil();
        util.method1();

        //method:即爲代理類對象調用的方法,此方法也就作爲了被代理類對象要調用的方法
        //obj:被代理類的對象
        Object returnValue = method.invoke(obj,args);

        util.method2();

        //上述方法的返回值就作爲當前類中的invoke()的返回值。
        return returnValue;

    }
}

public class ProxyTest {

    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        //proxyInstance:代理類的對象
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //當通過代理類對象調用方法時,會自動的調用被代理類中同名的方法
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        proxyInstance.eat("四川麻辣燙");

        System.out.println("*****************************");

        NikeClothFactory nikeClothFactory = new NikeClothFactory();

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

        proxyClothFactory.produceCloth();

    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章