框架與RTTI的關係,RTTI與反射之間的關係

導讀

源碼地址前端

在以後的幾篇文章,我會講解我本身的hibernate、spring、beanutils框架,但講解這些框架以前,我須要講解RTTI和反射。java

工做將近一年了,咱們公司項目所使用的框架是SSH,或者,其餘公司使用的是SSM框架。不論是什麼樣的框架,其都涉及到反射。那麼,什麼是反射?咱們在生成對象時,事先並不知道生成哪一種類型的對象,只有等到項目運行起來,框架根據咱們的傳參,才生成咱們想要的對象。git

好比,咱們從前端調用後端的接口,查詢出這我的的全部項目,咱們只要傳遞這我的的id便可。固然,數據來源於數據庫,那麼,問題來了,數據是怎麼從持久態轉化成咱們想要的順時態的?這裏面,就涉及到了反射。可是,一提到反射,咱們勢必就提到RTTI,即運行時類型信息(runtime Type Infomation)。spring


RTTI


  • po類
/**
 * Created By zby on 16:53 2019/3/16
 */
@AllArgsConstructor
@NoArgsConstructor
public class Pet {

    private String name;

    private String food;

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

    public void setFood(String food) {
        this.food = food;
    }

    public String getName() {
        return name;
    }

    public String getFood() {
        return food;
    }
}

/**
 * Created By zby on 17:03 2019/3/16
 */
public class Cat extends Pet{

    @Override
    public void setFood(String food) {
        super.setFood(food);
    }
}

/**
 * Created By zby on 17:04 2019/3/16
 */
public class Garfield extends Cat{

    @Override
    public void setFood(String food) {
        super.setFood(food);
    }
}


/**
 * Created By zby on 17:01 2019/3/16
 */
public class Dog extends Pet{

    @Override
    public void setFood(String food) {
        super.setFood(food);
    }
}

以上是用來講明的persistent object類,也就是,咱們在進行pojo經常使用的javabean類。其有繼承關係,以下圖:數據庫

繼承關係圖

  • 展現信息

以下代碼所示,方法eatWhatToday有兩個參數,這兩個參數一個是接口類,一個是父類,也就是說,咱們並不知道打印出的是什麼信息。只有根據接口的實現類來和父類的子類,來確認打印出的信息。這就是咱們說的運行時類型信息,正由於有了RTTI,java纔有了動態綁定的概念。bootstrap

/**
 * Created By zby on 17:05 2019/3/16
 */
public class FeedingPet {

    /**
     * Created By zby on 17:05 2019/3/16
     * 某種動物今天吃的是什麼
     *
     * @param baseEnum 枚舉類型 這裏表示的時間
     * @param pet      寵物
     */
    public static void eatWhatToday(BaseEnum baseEnum, Pet pet) {
        System.out.println( pet.getName() + "今天" + baseEnum.getTitle() + "吃的" + pet.getFood());
    }
    
}

  • 測試類
@Test
public void testPet(){
    Dog dog=new Dog();
    dog.setName("寵物狗京巴");
    dog.setFood(FoodTypeEnum.FOOD_TYPE_BONE.getTitle());

    FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MORNING,dog);

    Garfield garfield=new Garfield();
    garfield.setName("寵物貓加菲貓");
    garfield.setFood(FoodTypeEnum.FOOD_TYPE_CURRY.getTitle());
    FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MIDNIGHT_SNACK,garfield);
}

打印出的信息爲:後端

打印信息

那麼,這和反射有什麼關係呢?安全


反射獲取當前類信息

正如上文提到的運行時類型信息,那麼,類型信息在運行時是如何表示的?此時,咱們就想到了Class這個特殊對象。見名知其意,即類對象,其包含了類的全部信息,包括屬性、方法、構造器。mybatis

咱們都知道,類是程序的一部分,每一個類都有一個Class對象。每當編寫而且執行了一個新類,就會產生一個Class對象(更恰當地說,是被保存在一個同名的.class文件中)。爲了生成這個類的對象,運行當前程序的jvm將使用到類加載器。jvm首先調用bootstrap類加載器,加載核心文件,jdk的核心文件,好比Object,System等類文件。而後調用plateform加載器,加載一些與文件相關的類,好比壓縮文件的類,圖片的類等等。最後,才用applicationClassLoader,加載用戶自定義的類。app

加載當前類信息

反射正是利用了Class來建立、修改對象,獲取和修改屬性的值等等。那麼,反射是怎麼建立當前類的呢?


  • 第一種,可使用當前上下文的類路徑來建立對象,如咱們記載jdbc類驅動的時候,如如下代碼:
/**
 * Created By zby on 18:07 2019/3/16
 * 經過上下文的類路徑來加載信息
 */
public static Class byClassPath(String classPath) {
    if (StringUtils.isBlank(classPath)) {
        throw new RuntimeException("類路徑不能爲空");
    }
    classPath = classPath.replace(" ", "");
    try {
        return Class.forName(classPath);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    return null;
}

  • 第二種,經過類字面常量,這種作法很是簡單,並且更安全。由於,他在編譯時就會受到檢查,咱們不須要將其置於try catch的代碼快中,並且,它根除了對forName的方法調用,因此,更高效。這種是spring、hibernate等主流框架使用的。

框架hibernate的內部使用類字面常量去建立對象後,底層經過jdbc獲取數據表的字段值,根據數據表的字段與當前類的屬性進行一一匹配,將字段值填充到當前對象中。匹配不成功,就會報出相應的錯誤。

類字面常量獲取對象信息,如代碼所示。下文,也是經過類字面常量建立對象。

/**
 * Created By zby on 18:16 2019/3/16
 * 經過類字面常量加載當前類的信息
 */
public static void byClassConstant() {
    System.out.println(Dog.class);
}

  • 第三種,是經過對象來建立當前類,這種會在框架內部使用。
/**
* Created By zby on 18:17 2019/3/16
* 經過類對象加載當前類的信息
*/
public static Class byCurrentObject(Object object) {
    return object.getClass();
}

反射建立當前類對象

咱們建立當前對象,通常有兩種方式,一種是經過clazz.newInstance();這種通常是無參構造器,而且建立對對象後,能夠獲取其屬性,經過屬性賦值和方法賦值,如如代碼所示:


  • 第一種,經過clazz.newInstance()建立對象
/**
 * Created By zby on 18:26 2019/3/16
 * 普通的方式建立對象
 */
public static <T> T byCommonGeneric(Class clazz, String name, BaseEnum baseEnum) {
    if (null == clazz) {
        return null;
    }
    try {
        T t = (T) clazz.newInstance();
        
        //經過屬性賦值,getField獲取公有屬性,獲取私有屬性
        Field field = clazz.getDeclaredField("name");
        //跳過檢查,不然,咱們沒辦法操做私有屬性
        field.setAccessible(true);
        field.set(t, name);
        
        //經過方法賦值
        Method method1 = clazz.getDeclaredMethod("setFood", String.class);
        method1.setAccessible(true);
        method1.invoke(t, baseEnum.getTitle());

        return t;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

測試:
 @Test
public void testCommonGeneric() {
    Dog dog= GenericCurrentObject.byCommonGeneric(Dog.class,
            "寵物狗哈士奇", 
            FoodTypeEnum.FOOD_TYPE_BONE);
    FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_NOON,dog);
}

叔叔出結果爲:

輸出結果爲

你會發現一個神奇的地方,就是名字沒有輸出來,但咱們寫了名字呀,爲何沒有輸出來?由於,dog是繼承了父類Pet,當咱們在建立子類對象時,首先,會加載父類未加載的構造器、靜態代碼塊、靜態屬性、靜態方法等等。可是,Dog在這裏是以無參構造器加載的,固然,同時也經過無參構造器的實例化了父類。咱們在給dog對象的name賦值時,並無給父類對象的name賦值,因此,dog的name是沒有值的。父類引用指向子類對象,就是這個意思。

若是咱們把Dog類中的 @Override public void setFood(String food) {super.setFood(food); }super.setFood(food); 方法去掉,屬性food也是沒有值的。如圖所示:

寵物沒有值


  • 經過構造器建立對象
/**
     * Created By zby on 18:26 2019/3/16
     * 普通的方式建立對象
     */
    public static <T> T byConstruct(Class clazz, String name, BaseEnum baseEnum) {
        if (null == clazz) {
            return null;
        }
//        參數類型,
        Class paramType[] = {String.class, String.class};
        try {
//          通常狀況下,構造器不止一個,咱們根據構器的參數類型,來使用構造器建立對象
            Constructor constructor = clazz.getConstructor(paramType);
//            給構造器賦值,賦值個數和構造器的形參個數同樣,不然,會報錯
            return (T) constructor.newInstance(name, baseEnum.getTitle());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    測試:
    
   @Test
    public void testConstruct() {
        Dog dog= GenericCurrentObject.byConstruct(Dog.class,
                "寵物狗哈士奇",
                FoodTypeEnum.FOOD_TYPE_BONE);
        System.out.println("輸出寵物的名字:"+dog.getName()+"\n");
        System.out.println("寵物吃的什麼:"+dog.getFood()+"\n");
        FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MIDNIGHT_SNACK,dog);
    }

測試結果:

clipboard.png

這是經過構造器建立的對象。可是注意的是,形參類型和和參數值的位數必定要相等,不然,就會報出錯誤的。

總結

爲何寫這篇文章,前面也說了,不少框架都用到了反射和RTTI。可是,咱們的日常的工做,通常以業務爲主。每每都是使用別人封裝好的框架,好比spring、hibernate、mybatis、beanutils等框架。因此,咱們不大會關注反射,可是,你若是想要往更高的方向去攀登,仍是要把基礎給打撈。不然,基礎不穩,爬得越高,摔得越重。

我會之後的篇章中,經過介紹我寫的spring、hibernate框架,來說解更好地講解反射。

相關文章
相關標籤/搜索