摘要:不管是什么樣的框架,其都涉及到反射。見(jiàn)名知其意,即類(lèi)對(duì)象,其包含了類(lèi)的所有信息,包括屬性方法構(gòu)造器。為了生成這個(gè)類(lèi)的對(duì)象,運(yùn)行當(dāng)前程序的將使用到類(lèi)加載器。這種是等主流框架使用的。
導(dǎo)讀
源碼地址
在之后的幾篇文章,我會(huì)講解我自己的hibernate、spring、beanutils框架,但講解這些框架之前,我需要講解RTTI和反射。
工作將近一年了,我們公司項(xiàng)目所使用的框架是SSH,或者,其他公司使用的是SSM框架。不管是什么樣的框架,其都涉及到反射。那么,什么是反射?我們?cè)谏蓪?duì)象時(shí),事先并不知道生成哪種類(lèi)型的對(duì)象,只有等到項(xiàng)目運(yùn)行起來(lái),框架根據(jù)我們的傳參,才生成我們想要的對(duì)象。
比如,我們從前端調(diào)用后端的接口,查詢(xún)出這個(gè)人的所有項(xiàng)目,我們只要傳遞這個(gè)人的id即可。當(dāng)然,數(shù)據(jù)來(lái)源于數(shù)據(jù)庫(kù),那么,問(wèn)題來(lái)了,數(shù)據(jù)是怎么從持久態(tài)轉(zhuǎn)化成我們想要的順時(shí)態(tài)的?這里面,就涉及到了反射。但是,一提到反射,我們勢(shì)必就提到RTTI,即運(yùn)行時(shí)類(lèi)型信息(runtime Type Infomation)。
RTTIpo類(lèi)
/** * 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); } }
以上是用來(lái)說(shuō)明的persistent object類(lèi),也就是,我們?cè)谶M(jìn)行pojo常用的javabean類(lèi)。其有繼承關(guān)系,如下圖:
展示信息
如下代碼所示,方法eatWhatToday有兩個(gè)參數(shù),這兩個(gè)參數(shù)一個(gè)是接口類(lèi),一個(gè)是父類(lèi),也就是說(shuō),我們并不知道打印出的是什么信息。只有根據(jù)接口的實(shí)現(xiàn)類(lèi)來(lái)和父類(lèi)的子類(lèi),來(lái)確認(rèn)打印出的信息。這就是我們說(shuō)的運(yùn)行時(shí)類(lèi)型信息,正因?yàn)橛辛薘TTI,java才有了動(dòng)態(tài)綁定的概念。
/** * Created By zby on 17:05 2019/3/16 */ public class FeedingPet { /** * Created By zby on 17:05 2019/3/16 * 某種動(dòng)物今天吃的是什么 * * @param baseEnum 枚舉類(lèi)型 這里表示的時(shí)間 * @param pet 寵物 */ public static void eatWhatToday(BaseEnum baseEnum, Pet pet) { System.out.println( pet.getName() + "今天" + baseEnum.getTitle() + "吃的" + pet.getFood()); } }
測(cè)試類(lèi)
@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); }
打印出的信息為:
那么,這和反射有什么關(guān)系呢?
反射獲取當(dāng)前類(lèi)信息正如上文提到的運(yùn)行時(shí)類(lèi)型信息,那么,類(lèi)型信息在運(yùn)行時(shí)是如何表示的?此時(shí),我們就想到了Class這個(gè)特殊對(duì)象。見(jiàn)名知其意,即類(lèi)對(duì)象,其包含了類(lèi)的所有信息,包括屬性、方法、構(gòu)造器。
我們都知道,類(lèi)是程序的一部分,每個(gè)類(lèi)都有一個(gè)Class對(duì)象。每當(dāng)編寫(xiě)并且執(zhí)行了一個(gè)新類(lèi),就會(huì)產(chǎn)生一個(gè)Class對(duì)象(更恰當(dāng)?shù)卣f(shuō),是被保存在一個(gè)同名的.class文件中)。為了生成這個(gè)類(lèi)的對(duì)象,運(yùn)行當(dāng)前程序的jvm將使用到類(lèi)加載器。jvm首先調(diào)用bootstrap類(lèi)加載器,加載核心文件,jdk的核心文件,比如Object,System等類(lèi)文件。然后調(diào)用plateform加載器,加載一些與文件相關(guān)的類(lèi),比如壓縮文件的類(lèi),圖片的類(lèi)等等。最后,才用applicationClassLoader,加載用戶(hù)自定義的類(lèi)。
加載當(dāng)前類(lèi)信息反射正是利用了Class來(lái)創(chuàng)建、修改對(duì)象,獲取和修改屬性的值等等。那么,反射是怎么創(chuàng)建當(dāng)前類(lèi)的呢?
第一種,可以使用當(dāng)前上下文的類(lèi)路徑來(lái)創(chuàng)建對(duì)象,如我們記載jdbc類(lèi)驅(qū)動(dòng)的時(shí)候,如以下代碼:
/** * Created By zby on 18:07 2019/3/16 * 通過(guò)上下文的類(lèi)路徑來(lái)加載信息 */ public static Class byClassPath(String classPath) { if (StringUtils.isBlank(classPath)) { throw new RuntimeException("類(lèi)路徑不能為空"); } classPath = classPath.replace(" ", ""); try { return Class.forName(classPath); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; }
第二種,通過(guò)類(lèi)字面常量,這種做法非常簡(jiǎn)單,而且更安全。因?yàn)?,他在編譯時(shí)就會(huì)受到檢查,我們不需要將其置于try catch的代碼快中,而且,它根除了對(duì)forName的方法調(diào)用,所以,更高效。這種是spring、hibernate等主流框架使用的。
框架hibernate的內(nèi)部使用類(lèi)字面常量去創(chuàng)建對(duì)象后,底層通過(guò)jdbc獲取數(shù)據(jù)表的字段值,根據(jù)數(shù)據(jù)表的字段與當(dāng)前類(lèi)的屬性進(jìn)行一一匹配,將字段值填充到當(dāng)前對(duì)象中。匹配不成功,就會(huì)報(bào)出相應(yīng)的錯(cuò)誤。
類(lèi)字面常量獲取對(duì)象信息,如代碼所示。下文,也是通過(guò)類(lèi)字面常量創(chuàng)建對(duì)象。
/** * Created By zby on 18:16 2019/3/16 * 通過(guò)類(lèi)字面常量加載當(dāng)前類(lèi)的信息 */ public static void byClassConstant() { System.out.println(Dog.class); }
第三種,是通過(guò)對(duì)象來(lái)創(chuàng)建當(dāng)前類(lèi),這種會(huì)在框架內(nèi)部使用。
/** * Created By zby on 18:17 2019/3/16 * 通過(guò)類(lèi)對(duì)象加載當(dāng)前類(lèi)的信息 */ public static Class byCurrentObject(Object object) { return object.getClass(); }反射創(chuàng)建當(dāng)前類(lèi)對(duì)象
我們創(chuàng)建當(dāng)前對(duì)象,一般有兩種方式,一種是通過(guò)clazz.newInstance();這種一般是無(wú)參構(gòu)造器,并且創(chuàng)建對(duì)對(duì)象后,可以獲取其屬性,通過(guò)屬性賦值和方法賦值,如如代碼所示:
第一種,通過(guò)clazz.newInstance()創(chuàng)建對(duì)象
/** * Created By zby on 18:26 2019/3/16 * 普通的方式創(chuàng)建對(duì)象 */ public staticT byCommonGeneric(Class clazz, String name, BaseEnum baseEnum) { if (null == clazz) { return null; } try { T t = (T) clazz.newInstance(); //通過(guò)屬性賦值,getField獲取公有屬性,獲取私有屬性 Field field = clazz.getDeclaredField("name"); //跳過(guò)檢查,否則,我們沒(méi)辦法操作私有屬性 field.setAccessible(true); field.set(t, name); //通過(guò)方法賦值 Method method1 = clazz.getDeclaredMethod("setFood", String.class); method1.setAccessible(true); method1.invoke(t, baseEnum.getTitle()); return t; } catch (Exception e) { e.printStackTrace(); } return null; } 測(cè)試: @Test public void testCommonGeneric() { Dog dog= GenericCurrentObject.byCommonGeneric(Dog.class, "寵物狗哈士奇", FoodTypeEnum.FOOD_TYPE_BONE); FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_NOON,dog); }
叔叔出結(jié)果為:
你會(huì)發(fā)現(xiàn)一個(gè)神奇的地方,就是名字沒(méi)有輸出來(lái),但我們寫(xiě)了名字呀,為什么沒(méi)有輸出來(lái)?因?yàn)?,dog是繼承了父類(lèi)Pet,當(dāng)我們?cè)趧?chuàng)建子類(lèi)對(duì)象時(shí),首先,會(huì)加載父類(lèi)未加載的構(gòu)造器、靜態(tài)代碼塊、靜態(tài)屬性、靜態(tài)方法等等。但是,Dog在這里是以無(wú)參構(gòu)造器加載的,當(dāng)然,同時(shí)也通過(guò)無(wú)參構(gòu)造器的實(shí)例化了父類(lèi)。我們?cè)诮odog對(duì)象的name賦值時(shí),并沒(méi)有給父類(lèi)對(duì)象的name賦值,所以,dog的name是沒(méi)有值的。父類(lèi)引用指向子類(lèi)對(duì)象,就是這個(gè)意思。
如果我們把Dog類(lèi)中的 @Override public void setFood(String food) {super.setFood(food); }的super.setFood(food); 方法去掉,屬性food也是沒(méi)有值的。如圖所示:
通過(guò)構(gòu)造器創(chuàng)建對(duì)象
/** * Created By zby on 18:26 2019/3/16 * 普通的方式創(chuàng)建對(duì)象 */ public staticT byConstruct(Class clazz, String name, BaseEnum baseEnum) { if (null == clazz) { return null; } // 參數(shù)類(lèi)型, Class paramType[] = {String.class, String.class}; try { // 一般情況下,構(gòu)造器不止一個(gè),我們根據(jù)構(gòu)器的參數(shù)類(lèi)型,來(lái)使用構(gòu)造器創(chuàng)建對(duì)象 Constructor constructor = clazz.getConstructor(paramType); // 給構(gòu)造器賦值,賦值個(gè)數(shù)和構(gòu)造器的形參個(gè)數(shù)一樣,否則,會(huì)報(bào)錯(cuò) return (T) constructor.newInstance(name, baseEnum.getTitle()); } catch (Exception e) { e.printStackTrace(); } return null; } 測(cè)試: @Test public void testConstruct() { Dog dog= GenericCurrentObject.byConstruct(Dog.class, "寵物狗哈士奇", FoodTypeEnum.FOOD_TYPE_BONE); System.out.println("輸出寵物的名字:"+dog.getName()+" "); System.out.println("寵物吃的什么:"+dog.getFood()+" "); FeedingPet.eatWhatToday(DateTypeEnum.DATE_TYPE_MIDNIGHT_SNACK,dog); }
測(cè)試結(jié)果:
這是通過(guò)構(gòu)造器創(chuàng)建的對(duì)象。但是注意的是,形參類(lèi)型和和參數(shù)值的位數(shù)一定要相等,否則,就會(huì)報(bào)出錯(cuò)誤的。
總結(jié)為什么寫(xiě)這篇文章,前面也說(shuō)了,很多框架都用到了反射和RTTI。但是,我們的平常的工作,一般以業(yè)務(wù)為主。往往都是使用別人封裝好的框架,比如spring、hibernate、mybatis、beanutils等框架。所以,我們不大會(huì)關(guān)注反射,但是,你如果想要往更高的方向去攀登,還是要把基礎(chǔ)給打撈。否則,基礎(chǔ)不穩(wěn),爬得越高,摔得越重。
我會(huì)以后的篇章中,通過(guò)介紹我寫(xiě)的spring、hibernate框架,來(lái)講解更好地講解反射。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/73736.html
摘要:接口與類(lèi)型信息關(guān)鍵字的一種重要目標(biāo)就是允許程序員隔離構(gòu)件,進(jìn)而降低耦合性。如果你編寫(xiě)接口,那么就可以實(shí)現(xiàn)這一目標(biāo),但是通過(guò)類(lèi)型信息,這種耦合性還是會(huì)傳播出去接口并非是對(duì)解耦的一種無(wú)懈可擊的保障。 點(diǎn)擊進(jìn)入我的博客 運(yùn)行時(shí)類(lèi)型信息使得你可以在運(yùn)行時(shí)發(fā)現(xiàn)和使用類(lèi)型信息,主要有兩種方式: 傳統(tǒng)的RTTI,它假定我們?cè)诰幾g時(shí)已經(jīng)知道了所有的類(lèi)型; 反射機(jī)制,它允許我們?cè)谶\(yùn)行時(shí)發(fā)現(xiàn)和使用類(lèi)的...
摘要:因而,我從中也知道了,很多公司沒(méi)有實(shí)現(xiàn)數(shù)據(jù)過(guò)濾。因?yàn)?,那樣將?huì)造成數(shù)據(jù)的冗余。因而,我們這時(shí)需要過(guò)濾數(shù)據(jù)對(duì)象,如代碼所示常用的把圖片轉(zhuǎn)成結(jié)構(gòu)如上訴代碼的轉(zhuǎn)換,公司使用的是這個(gè)框架。而棧是存放數(shù)據(jù)的一種結(jié)構(gòu),其采用,即先進(jìn)后出。 導(dǎo)讀 上一篇文章已經(jīng)詳細(xì)介紹了框架與RTTI的關(guān)系,RTTI與反射之間的關(guān)系。尤其是對(duì)反射做了詳細(xì)說(shuō)明,很多培訓(xùn)機(jī)構(gòu)也將其作為高級(jí)教程來(lái)講解。 其實(shí),我工作年限...
摘要:同時(shí)也有一些兒高級(jí)的處理,比如批處理更新事務(wù)隔離和可滾動(dòng)結(jié)果集等。連接對(duì)象表示通信上下文,即,與數(shù)據(jù)庫(kù)中的所有的通信是通過(guò)此唯一的連接對(duì)象。因?yàn)槭轻槍?duì)類(lèi)的關(guān)系而言,所以一個(gè)對(duì)象對(duì)應(yīng)多個(gè)類(lèi)的實(shí)例化。返回表示查詢(xún)返回表示其它操作。 JDBC是什么? JDBC是一個(gè)Java API,用中文可以通俗的解釋為,使用Java語(yǔ)言訪(fǎng)問(wèn)訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)的一套接口集合。這是調(diào)用者(程序員)和實(shí)行者(數(shù)據(jù)庫(kù)廠(chǎng)商...
摘要:找到字節(jié)碼并創(chuàng)建一個(gè)對(duì)象。鏈接,檢驗(yàn)字節(jié)碼,為字段分配存儲(chǔ)空間,解決其對(duì)他類(lèi)的引用。初始化,如果有父類(lèi)則初始化父類(lèi),執(zhí)行靜態(tài)初始化器和靜態(tài)初始化區(qū)塊直到第一次訪(fǎng)問(wèn)靜態(tài)成員時(shí)初始化才執(zhí)行。如果成員不是編譯時(shí)常量由初始化器賦值,也會(huì)引起初始化。 有兩種形式在運(yùn)行時(shí)獲取類(lèi)型信息: 傳統(tǒng)的RTTI 反射 Class對(duì)象 運(yùn)行時(shí)的類(lèi)型信息是通過(guò)Class對(duì)象表現(xiàn)的,它包含了類(lèi)的信息。所有...
反射機(jī)制與原理筆記 聲明 文章均為本人技術(shù)筆記,轉(zhuǎn)載請(qǐng)注明出處https://segmentfault.com/u/yzwall 反射機(jī)制 反射:當(dāng)程序無(wú)法獲知對(duì)象類(lèi)型時(shí),在運(yùn)行期間動(dòng)態(tài)獲取類(lèi)的所有屬性和方法,這種動(dòng)態(tài)獲取類(lèi)信息和動(dòng)態(tài)調(diào)用對(duì)象方法的功能稱(chēng)為反射機(jī)制;反射機(jī)制實(shí)現(xiàn):Class類(lèi)與java.lang.reflect類(lèi)庫(kù)一起實(shí)現(xiàn)機(jī)制,java.lang.reflect類(lèi)庫(kù)包含F(xiàn)ield...
閱讀 1123·2021-09-22 15:19
閱讀 1757·2021-08-23 09:46
閱讀 2262·2021-08-09 13:47
閱讀 1432·2019-08-30 15:55
閱讀 1441·2019-08-30 15:55
閱讀 1997·2019-08-30 15:54
閱讀 2829·2019-08-30 15:53
閱讀 735·2019-08-30 11:03