java高級進階-------反射

java反射是java中功能很強大的一個功能,很多的框架都使用到了反射的機制,所以學習反射是java進階必不可少的步驟。
下面的內容就是反射中一些常見的問題和使用,能力屬於初級,所以很多很深的東西根本寫不了,只能帶大家入門學習。
1、在學習java反射之前有必要了解一些概念:
類的初始化:類的初始化過程分爲三步 系統會通過加載、連接、初始化三步來實現類的初始化
加載:將指定的class文件讀取到內存中,並且爲它創建一個Class對象 任何類被用時系統都會創建一個Class對象
連接: 驗證 是否有正確的內部結構,並和其它類協調一致
準備 負責爲類的靜態成員變量分配內存,並且設置初始值
初始化:初始化成員變量等

類的加載的時機:
類的實例化
調用類的靜態變量 或者爲靜態變量賦值
調用類的靜態方法
初始化某個類的子類
使用反射原理強制創建某一類或接口對應的Class對象

java反射概念:自己理解的就是在程序運行的時候,可以動態的獲取一個類中的屬性和方法,而且可以調用以及修改類中方法和屬性的一種機制我們成爲java反射機制。
要想使用java反射機制,就必須有字節碼文件。字節碼文件獲取的方法有三種:
1、Class.forName(“類路徑”);
2、類名.class
3、對象.getClass();
2、使用反射
注意如果同時用三種方法創建的字節碼對象,三個對象都是相同的
有了字節碼文件就可以通過它來創建對象,通過對象就可以調用類中的方法和屬性。

package com.yxc.domain;

/**
 * 實體類 後面通過反射拿到這個類中的所有方法和所有屬性
 * 不管是私有的還是公有的,都可以獲取,這就是反射強大之處。
 */
public class User  {

    //定義兩個方法
    public void print(){
        System.out.println("我是公有打印方法");
    }
    private void print1(){
        System.out.println("我是私有的打印方法");
    }
}

package com.yxc.reflect;

import com.yxc.domain.User;

/**
 * 第一個簡單的反射類
 */
public class ReflectDemo {
    public static void main(String[] args) {
        //三種獲取字節碼文件的方法
        /**
         * 第一種方法,通過forName()方法獲取字節碼文件
         */
        Class clazz1=null;
        try {
            clazz1=Class.forName("com.yxc.domain.User");
            //通過字節碼文件的newInstance可以創建對象,返回的Object類型,我們需要強轉
            User user = (User)clazz1.newInstance();
            user.print();
            //在這裏私有方法是不可以直接調用的,但是等會我們可以通過反射獲取所有的方法,包括私有的字段
            //user.print1();
        } catch (Exception e) {
            e.printStackTrace();
        }

        /**
         * 第二種,直接通過類名.class獲取字節碼文件;
         */
        Class clazz2 = User.class;
        try {
            User user =(User)clazz2.newInstance();
            user.print();
        }catch (Exception e) {
            e.printStackTrace();
        }

        /**
         * 通過對象的getClass()方法獲取字節碼文件
         */
        User user=new User();
        Class clazz3 = user.getClass();
        try {
            User user1 = (User)clazz3.newInstance();
            user1.print();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        //這裏的對象都是相同的,對於同一個類而言,字節碼類是相同的
        System.out.println(clazz2==clazz3);
        System.out.println(clazz1==clazz2);
    }
}

上面的User類就是通過反射來執行它裏面的方法和屬性的,我在學的時候有一個疑問,既然都知道是User類,要調用它裏面的方法和屬性,幹嘛不直接創建對象,通過對象調用呢?那是因爲剛開始學,沒有體會反射真正強大的地方,如果類中的私有屬性和私有方法呢,我們在其它類中是無法調用的,這時候通過反射就可以解決問題。這些我們馬上接着說。
3、下面我們通過一個案列來實現公有屬性,私有屬性,公有方法,公有有參數的方法,私有方法,私有有參數的方法的獲取。

package com.yxc.domain;

/**
 * 實體類 後面通過反射拿到這個類中的所有方法和所有屬性
 * 不管是私有的還是公有的,都可以獲取,這就是反射強大之處。
 */
public class User  {
    /**
     * 定義一個私有屬性和公共屬性
     */
    private String name;
    public int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void print(){
        System.out.println("我是公有打印方法");
    }
    public void print(String name){
        System.out.println("我是有參公有方法"+name);
    }
    private void print1(){
        System.out.println("我是私有的打印方法");
    }

    private void print1(Integer i){
        System.out.println("我是私有的有參數的打印方法,編號"+i);
    }
}

package com.yxc.reflect;

import com.yxc.domain.User;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * 第一個簡單的反射類
 */
public class ReflectDemo {
    public static void main(String[] args) {
        /**
         * 獲取屬性
         * 獲取公有屬性和私有屬性
         */

        Class clazz=null;
        try {
            //1、首先獲取字節碼文件
            clazz=Class.forName("com.yxc.domain.User");
            //2、通過字節碼文件創建對象
            User user= (User)clazz.newInstance();

            //3、獲取公有屬性的代碼
            Field f1 = clazz.getField("age");//獲取字段名
            f1.set(user,23);                      //設置字段新的值
            int age = (int)f1.get(user);          //獲取字段
            System.out.println(age);


            //獲取私有屬性的代碼
            Field f2 = clazz.getDeclaredField("name"); //和公有屬性的獲取一樣,只是方法不一樣
            f2.setAccessible(true);                          //這一步是核心,需要設置可訪問爲true
            f2.set(user,"yxc");                              //後面的操作和公有屬性一致
            String name = (String)f2.get(user);
            System.out.println(name);

            //獲取公有無參數的方法
            Method m1 = clazz.getMethod("print");
            m1.invoke(user);
            //獲取有參數的公有方法
            Method m2 = clazz.getMethod("print", String.class);//參數的類型
            m2.invoke(user,"hhh");//那個對象執行這個方法,方法的參數

            //獲取私有方法
            Method m3 = clazz.getDeclaredMethod("print1");
            m3.setAccessible(true); //對於方法來說,同樣需要設置可訪問
            m3.invoke(user);

            //獲取有參數的私有方法
            Method m4 = clazz.getDeclaredMethod("print1", Integer.class);
            m4.setAccessible(true);
            m4.invoke(user,23);


        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

通過上面的簡單案列,我們可以瞭解到反射的基本使用方法。反射的強大支持就在於此,不管你是私有還是公有,只要我想要的,沒有得不到的,很霸道,很強勢。
4、反射的應用
下面通過簡單的幾個反射的應用來鞏固一下反射的知識,當然反射的應用是很廣泛的,幾乎所有的框架的開發都使用到了反射。

反射使用案例1:越過泛型檢測

package com.yxc.reflect;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 反射的案例:越過泛型檢測
 * 我們都知道集合可以定義泛型,只要定義了,那麼其它的類型是不可以添加進入的
 * 但是學完反射以後我們就可以通過反射來實現,越過泛型檢驗
 */
public class ArraysCheck {
    public static void main(String[] args) {
        //定義一個list集合,指定類型爲整型數據
        ArrayList<Integer> list=new ArrayList<>();
        list.add(23);
        list.add(24);
        list.add(6);
        //此時如果試圖添加字符串類型,那麼肯定不能通過編譯
        //list.add("報錯");

        //下面通過反射來
        //獲取字節碼文件
        Class clazz=ArrayList.class;
        try {
            //獲取集合中的add方法,而且將參數設置爲Object類型
            Method m1 = clazz.getMethod("add", Object.class);
            m1.setAccessible(true);      //其實這一步可以不用,因爲add是公有的方法
            //開始執行方法 使用list對象
            m1.invoke(list,"越過泛型檢驗");

        } catch (Exception e) {
            e.printStackTrace();
        }

        //通過迭代器將集合中的數據輸出
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

    }
}

反射應用案例2:靜態代理
這裏又出現了一個新的專業名詞,代理。如果我們自己先理解一下,什麼是代理就好比是讓一個人專門幫你做事,你不需要管,只要將代理權給他,那麼他可以幫你完成一些任務,而你不需要關心這些,只要做好自己的事就好。 代理有很多種,常見的靜態代理,jdk代理(動態代理)後面學習的第三方的cglib代理。
爲什麼要使用代理呢?我們都知道在一些操作的前面或者後面需要動態的加入一些功能,比如在查詢數據前加一個權限校驗,後面加一個日誌生成的方法,當然我們可以使用繼承的方式去完成這樣的功能,繼承勢必會增加類的數量,這樣會導致類的膨脹,當然也可以直接在原方法中直接添加,那麼這樣的代碼是不健全的,不易維護的。我們都知道軟件的開發需求是不斷變更的,如果沒改動一次就去動源碼的話,那基本是不理想的。所以我們需要使用代理,讓那些增強的方法專門放在一個代理類中,這樣就可以在不修改源碼的情況下添加新的功能。下面我們通過代碼來實現一個簡單的靜態代理。

package com.yxc.proxy;

/**
 * 對於靜態代理來說,必須要有接口
 */
public interface UserDao {
    /**
     * 定義一個抽象方法根據id查詢一個用戶
     * @param id
     */
    public void findUser(int id);
}

package com.yxc.proxy;



/**
 * 這是真正工作的類
 */
public class ReadWorkUserDaoImpl implements UserDao {

    //在實際開發中這裏就可以直接從數據庫查詢到需要的數據
    @Override
    public void findUser(int id) {
        System.out.println("從數據庫查詢id爲"+id+"的用戶");
    }
}

package com.yxc.proxy;

/**
 * 這是代理類
 * 對於代理類來說也需要繼承接口,而且要持有接口對象,就好比證明一樣,這樣纔可以拿到代理權
 */
public class ProxyUser implements UserDao{
    //代理類必須持有接口對象,就好比代理要有代理權
    private UserDao userDao;
    //通過構造方法將參數初始化
    public ProxyUser(UserDao userDao){
        this.userDao=userDao;
    }


    @Override
    public void findUser(int id) {
        System.out.println("用戶權限檢驗,可以進行數據庫查詢");
        userDao.findUser(id);
        System.out.println("完成操作,記錄日誌");
    }
}
package com.yxc.proxy;

/**
 * 測試靜態代理
 */
public class ProxyTest {
    public static void main(String[] args) {
        UserDao userDao=new ReadWorkUserDaoImpl();
        ProxyUser proxyUser = new ProxyUser(userDao);
        proxyUser.findUser(23);
    }
}

說明一下:對於靜態代理必須由接口,然後真正執行的類和代理類都要實現接口,對重要的就是在代理類中必須持有接口的對象,就好比代理的人肯定要有代理權,而接口就是權力。我看網上的很多舉例說代理類好比就是經紀人,幫你處理一些事情,而你只要授權給他即可,你不用關心那些事情,只要做好自己的事情,其它的包裝都有經濟人幫你完成,這就好比代理的任務。
靜態代理的優點:顯然在不改變源碼的情況下可以添加新的功能,這樣增強代碼的可維護性
靜態代理的缺點:如果有很多類需要代理,就必須持有多個接口的變量或者寫很多的代理類,那樣的話代理類又會膨脹,而且毫無意義,所以有沒有什麼方法可以根據接口對象,動態的生成這一種的代理類,那麼就引入的動態代理。
有了上面的說明以後,我們可以理解動態代理只需要在靜態代理上加以修飾。代碼如下:
這裏只貼出不同於靜態代理的類,相同的類參考上面所說的靜態代理代碼

package com.yxc.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 這是代理類
 * 對於代理類來說也需要繼承接口,而且要持有接口對象,就好比證明一樣,這樣纔可以拿到代理權
 * 動態代理其實就是實現的接口不一樣,我們使用內部實現好的
 */
public class ProxyUser implements InvocationHandler {
    //對於動態代理也需要持有接口的對象
    private UserDao userDao;
    //通過構造方法將參數初始化
    public ProxyUser(UserDao userDao){
        this.userDao=userDao;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
         System.out.println("用戶權限檢驗,可以進行數據庫查詢");
        //invoke方法的前面就是在實際執行方法中的前面添加的功能
        Object invoke = method.invoke(userDao,args);
        //後面就是後面新增的方法
       System.out.println("完成操作,記錄日誌");
        //最後返回的是一個代理對象
        return invoke;
    }
}

package com.yxc.proxy;

import java.lang.reflect.Proxy;

/**
 * 測試靜態代理
 */
public class ProxyTest {
    public static void main(String[] args) {
        //創建一個真正的實現類
        ReadWorkUserDaoImpl userDaoImpl=new ReadWorkUserDaoImpl();
        /**
         * 這裏面的參數有點多我們一個個講解
         * Proxy.newProxyInstance()方法直接創建一個代理對象,這是內部提供好的類和方法,我們直接使用
         * 參數說明:
         * 1、需要一個接口的類加載器ClassLoader
         * 2、需要一個接口
         * 3、需要一個代理對象
         */
        UserDao userDao=(UserDao) Proxy.newProxyInstance(userDaoImpl.getClass().getClassLoader(),
                userDaoImpl.getClass().getInterfaces(),
                new ProxyUser(userDaoImpl)
        );
        userDao.findUser(23);
    }
}

靜態代理和動態代理執行的效果圖如下:
在這裏插入圖片描述
最後還有一種代理是使用第三方開發的cglib,這個我們在spring框架中會有所涉及,到時使用註解開發會更加高效。我們可以在一個類中寫很多的代理方法,直接通過註解表名這個方法添加哪個類中的哪個方法的哪個位置。
總結:對於反射來說,其實應用還有很多,但是對於初學者來說,後面的東西可能有點深,我自己也還沒有深層次的接觸過,而且在一般的java開發中,很少接觸到反射這一章,都是接觸一些框架的源碼學習時纔會慢慢的看到反射,那個時候才需要更深一步的研究學習。今天的總結就到此結束。有很多不足的地方,歡迎大家指出,一起學習。菜鳥一枚,慢慢的成長。一步一個腳印,走穩咯。

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