源碼地址前端
在以後的幾篇文章,我會講解我本身的hibernate、spring、beanutils框架,但講解這些框架以前,我須要講解RTTI和反射。java
工做將近一年了,咱們公司項目所使用的框架是SSH,或者,其餘公司使用的是SSM框架。不論是什麼樣的框架,其都涉及到反射。那麼,什麼是反射?咱們在生成對象時,事先並不知道生成哪一種類型的對象,只有等到項目運行起來,框架根據咱們的傳參,才生成咱們想要的對象。git
好比,咱們從前端調用後端的接口,查詢出這我的的全部項目,咱們只要傳遞這我的的id便可。固然,數據來源於數據庫,那麼,問題來了,數據是怎麼從持久態轉化成咱們想要的順時態的?這裏面,就涉及到了反射。可是,一提到反射,咱們勢必就提到RTTI,即運行時類型信息(runtime Type Infomation)。spring
/** * 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來建立、修改對象,獲取和修改屬性的值等等。那麼,反射是怎麼建立當前類的呢?
/** * 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; }
框架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();這種通常是無參構造器,而且建立對對象後,能夠獲取其屬性,經過屬性賦值和方法賦值,如如代碼所示:
/** * 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); }
測試結果:
這是經過構造器建立的對象。可是注意的是,形參類型和和參數值的位數必定要相等,不然,就會報出錯誤的。
爲何寫這篇文章,前面也說了,不少框架都用到了反射和RTTI。可是,咱們的日常的工做,通常以業務爲主。每每都是使用別人封裝好的框架,好比spring、hibernate、mybatis、beanutils等框架。因此,咱們不大會關注反射,可是,你若是想要往更高的方向去攀登,仍是要把基礎給打撈。不然,基礎不穩,爬得越高,摔得越重。
我會之後的篇章中,經過介紹我寫的spring、hibernate框架,來說解更好地講解反射。