您好,登錄后才能下訂單哦!
這篇文章主要介紹“服務(wù)器中反射是什么”,在日常操作中,相信很多人在服務(wù)器中反射是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”服務(wù)器中反射是什么”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
假設(shè)我們希望開發(fā)一套通用型的軟件框架,這個框架允許用戶自定義大量不同的情況下的回調(diào)函數(shù)(方法),用來實現(xiàn)豐富多彩的業(yè)務(wù)邏輯功能,例如一個游戲腳本引擎,那么,其中一個實現(xiàn)方式,就是使用觀察者模式,以事件的方式來驅(qū)動整個框架。用戶通過定義各個事件的響應(yīng)函數(shù),來組織和實現(xiàn)業(yè)務(wù)邏輯。而框架也提供了自定義事件及其響應(yīng)函數(shù)的入口。在一些實現(xiàn)代碼中,我們可能會發(fā)現(xiàn)有大量的“注冊事件”的代碼,或者是使用一個巨大的switch…case…對事件函數(shù)進(jìn)行分發(fā)調(diào)用。譬如我們想做一個服務(wù)器端的基本進(jìn)程框架,這個框架讓用戶只需要填寫一些回調(diào)函數(shù),就能成為一個穩(wěn)定持續(xù)運(yùn)行的后臺服務(wù)進(jìn)程。其中一個部分,就是需要定義程序啟動事件,以便用戶自定義程序啟動要做的事情。那么我們可以定義一個”Init”的字符串來代表這個事件,在一個事件響應(yīng)函數(shù)的回調(diào)哈希表里面,記錄上”Init”?pfunInit()。又或者是用一個常量宏INIT=12來表示此事件,在程序的主循環(huán)處,利用switch…case…來檢查代表每個事件的類型編碼,如果發(fā)現(xiàn)是和INIT宏相等的,就調(diào)用case INIT下面的代碼(往往是一個單獨(dú)的函數(shù),如pfunINit())
維護(hù)長長的“注冊事件”代碼和長長的switch…case…都一樣的讓人昏昏欲睡,同時容易讓人錯漏百出。這些代碼往往還帶有大量的“常量”,因為用來作為回調(diào)函數(shù)的key的數(shù)據(jù),往往都是一些自定義的常量。這些常量的同步維護(hù),也往往讓人筋疲力盡。這些長長的代碼清單,經(jīng)常還都需要由多個開發(fā)者一起來使用,自然就很容易發(fā)生你錯改了我的,我覆蓋了你的這一類問題。這些問題非常的“低級”,但是要找起來卻一點(diǎn)都不容易。
[游戲的按鍵控制代碼/JS]
難道我們的框架代碼中,就一定會充斥著長長的字符串常量,或者整數(shù)常量嗎?答案是否定的,因為很多編程語言,都提供能反射的功能。在編譯型語言如C/C++里面,也可以利用代碼生成技術(shù),模擬出類似反射的能力。
要想知道什么是反射,我們可以先來看一個觀察者模式的例子。假設(shè)我們在編寫一個GUI的程序:在一個窗體上安放了一個按鈕,此按鈕的名字叫“ButtonA”,當(dāng)這個按鈕按下的時候,我們希望有一個我們自己寫的函數(shù)被調(diào)用。根據(jù)觀察者模式的設(shè)計,這個按鈕被用戶按下后,程序底層應(yīng)該能監(jiān)測到這個事情,然后在進(jìn)程內(nèi)部產(chǎn)生一個“事件”,這個“事件”對象往往會帶有這個信息:被按下的按鈕名字。如果我們用以前的注冊事件的方法來編碼,我們必須要在按鈕被按下之前,比如程序初始化的時候,就向觀察者對象注冊這樣一個回調(diào)函數(shù):RegisterEvent(“ButtonA”, ONCLICK, myOnClick) —— ButtonA
被按下的事件—myOnClick()
。這里的函數(shù)myOnClick()
就是我們想處理ButtonA被按下的事件的響應(yīng)函數(shù)。但是,我們可以用另外一個更省事的方法來解決:我們把myOnClick()函數(shù)的名字改成ButtonA_OnClick()
,然后觀察者在發(fā)生“ButtonA”被按下的事件后,自動去找有沒有叫“ButtonA_OnClick
”這個名字的函數(shù),如果找到的話,就調(diào)用這個函數(shù)?!@然這種做法無需預(yù)先手工去注冊回調(diào)函數(shù),而是僅僅根據(jù)函數(shù)名字的約定,簡單的來決定要調(diào)用什么函數(shù)。一般來說,我們認(rèn)為程序運(yùn)行的過程中,這些函數(shù)名字、類名字、屬性名字都不起什么重要的作用,以至于我們還會用一些“混淆器”軟件來處理源代碼,把這些自定義的名字都弄的亂七八糟,也不影響程序的運(yùn)行。然而,如果我們使用反射的技術(shù),程序就可以在運(yùn)行時,實時的用一些常量,來檢索并且獲得源代碼中,函數(shù)、類、屬性名字所對應(yīng)的實體,并且還能調(diào)用這些東西。
[Spring通過XML來配置對象的關(guān)系]
從代碼維護(hù)的角度來看,類、成員、方法的名字,被程序以外的一些“配置文件”所管理和知道,是有一定風(fēng)險的。因為我們常常不把配置文件看成是源代碼那么重要的東西,錯漏也沒有編譯器或者IDE協(xié)助,所以一些難以調(diào)試的BUG往往是從這些位置產(chǎn)生的。不過作為一種大大節(jié)省框架代碼的技術(shù),還是受到廣泛歡迎。而上文所說的問題,現(xiàn)在漸漸由另外一種技術(shù)“元數(shù)據(jù)”(或者叫注解、特性),把配置文件和源代碼合并起來,這樣就能大大改善上述的問題。
我們在編寫通信功能的程序時,傳統(tǒng)的思路是要定義協(xié)議,也就是定義協(xié)議頭部,協(xié)議包長度,協(xié)議包字段等等。在一個比較復(fù)雜的網(wǎng)絡(luò)服務(wù)程序中,這樣的協(xié)議很容易就有幾十上百個。維護(hù)代碼的程序員想要搞明白別人定義的如此眾多的協(xié)議,實際上是不太容易的。我們很容易想到,能不能使用對象模型來代替通信協(xié)議的定義呢?答案是可以的。但是,使用對象模型又有一個新的問題:對象是一個在運(yùn)行時的內(nèi)存結(jié)構(gòu),如何把對象中的數(shù)據(jù),通過網(wǎng)絡(luò)接收和發(fā)送呢?最簡單的做法,就是使用memcpy(),Linux提供了這個功能強(qiáng)大的API,可以讓任何內(nèi)存中的數(shù)據(jù)變成一段字節(jié)數(shù)組,然后我們就能直接通過網(wǎng)絡(luò)發(fā)送了。但是,如果我們的對象不是一個簡單的結(jié)構(gòu)體(事實上簡單的結(jié)構(gòu)體也有問題),而是一個對象,這個對象里面可能存在指針類型的成員,這樣的拷貝就不可能顧及到這些指針指向的數(shù)據(jù)了。而且,如果收發(fā)兩端的程序,并不是同一種語言(操作系統(tǒng)、平臺),這樣的內(nèi)存結(jié)構(gòu)數(shù)據(jù)可能毫無意義,比如把一個C++的對象內(nèi)存直接拷貝給JAVA程序,肯定無法直接使用。所以,我們想要用對象結(jié)構(gòu)來定義通信協(xié)議,我們需要一個把對象轉(zhuǎn)換成通用的字節(jié)數(shù)組的方法,這就是“序列化/反序列化”的能力。在這里我不打算說太多關(guān)于序列化的內(nèi)容,我只想說,當(dāng)這些對象具備序列化能力后,就能成為通信數(shù)據(jù)的載體。問題是,如果我們收到了一段對象序列化的數(shù)據(jù),如何構(gòu)建出對應(yīng)數(shù)據(jù)的對象呢?答案就是使用反射,反射機(jī)能能從數(shù)據(jù)中獲得對象類的名字,然后通過這個名字構(gòu)造出對象來,然后從數(shù)據(jù)中繼續(xù)獲得余下成員的數(shù)據(jù),一一復(fù)制到這個對象身上。由此看,只要我們有反射功能,我們可以讓使用者,簡單的構(gòu)造一個對象,然后整個把這個對象發(fā)送給網(wǎng)絡(luò)的另外一端,對方也能直接收到一個對象,這樣在編寫通信程序的時候,只要按照業(yè)務(wù)需求定義對象即可。對于閱讀代碼的程序員來說,不用在腦子裝一根叫“編碼、解碼”的弦,只要“無腦”的定義、處理對象即可。
在通信程序中,有種叫命令模式的設(shè)計模式非常常見,它脫胎于傳統(tǒng)的基于命令字的網(wǎng)絡(luò)處理方式:解析出命令字?通過switch…case調(diào)用對應(yīng)的處理函數(shù)。命令模式下的通信程序往往很簡單,就是定義一個類型,這個類型的成員屬性(通信協(xié)議)是可以隨便定義的,只要再定一個Process()方法即可——這個方法的內(nèi)容,就是收到此類型對象,應(yīng)該如何處理的容器。由于我們利用反射可以在網(wǎng)絡(luò)另外一段重建這個對象,所以我們也可以調(diào)用這個預(yù)定義的Process()方法,這個方法由于和協(xié)議對象類定義在一起,所以它是知道所有的成員定義的,這樣這個處理方法,就無需好像以前的程序那樣,費(fèi)勁的通過強(qiáng)制類型轉(zhuǎn)換,來得到具體的數(shù)據(jù)內(nèi)容。在命令模式的通信程序?qū)崿F(xiàn)過程里,反射是至關(guān)重要的一環(huán),因為當(dāng)我們收到一個數(shù)據(jù)包時,必須要從數(shù)據(jù)包中得到其對應(yīng)的對象的類名,然后建立這個類所對應(yīng)的對象。一旦這個對象建立后,我們可以調(diào)用其反序列化函數(shù),讓對象的內(nèi)容和數(shù)據(jù)包中一致,最后調(diào)用其Process()方法,就大功告成了。這種設(shè)計,可以用不同的語言,定義同結(jié)構(gòu)的類對象,用來在不同的語言平臺程序之間通訊,而無需定義很復(fù)雜的協(xié)議定義規(guī)范。一些強(qiáng)大的對象數(shù)據(jù)工具,比如Google Protocol Buffer和Apache Thrift,直接可以用一個通用的IDL語言,生成各種語言的類定義源代碼,就更方便了。
[delphi上用界面設(shè)置ADO數(shù)據(jù)庫控件的屬性]
在JAVA中,JavaBean就是一個著名的利用反射來使用的“對象約定”:只要你編寫的JAVA類型,其成員是類似setXXX()
或者getXXX()
的,很多框架都會自動識別和處理這些成員函數(shù),從而實現(xiàn)諸如自動更新成員數(shù)據(jù),自動關(guān)聯(lián)界面內(nèi)容等功能。另外一個類似的例子是JMX,這個JAVA的通用監(jiān)控標(biāo)準(zhǔn)接口,可以把你定義的類對象解析出來,成員屬性的值可以變成統(tǒng)計圖線、可修改的表格項,方法變成按鈕。在游戲開發(fā)領(lǐng)域,反射還廣泛的用于,把圖形美術(shù)資源和程序代碼結(jié)合的目的:比如Flash Builder就可以通過反射,把一個Flash動畫對象,綁定到一個MovieClip類型上,從而獲得一個既具備美術(shù)效果,又能讓用戶自定義行為的對象。Unity3D在綁定了3D的游戲?qū)ο蠛湍_本組件后,對于腳本中的Start()/Update()函數(shù)調(diào)用,也是通過反射進(jìn)行的,這樣開發(fā)者就不必要把腳本的類型,死死的和某個基類綁定到一塊,而且這些反射調(diào)用的函數(shù),還是可以有不同的返回值(不同的函數(shù)原型),從而實現(xiàn)協(xié)程或者非協(xié)程的調(diào)用。
[在flash編輯器里,對一個動畫指定關(guān)聯(lián)的自定義類]
反射由于可以把源代碼中的信息提取出來,和其他的數(shù)據(jù)結(jié)合,讓源代碼的能力大大的提升,所以在開發(fā)工具方面,具有非常重要的地位。我們不再需要通過寫代碼,一遍遍的把源代碼的數(shù)據(jù)和外部結(jié)構(gòu)做對接,而是簡單的開發(fā)一個反射能力框架,就能讓我們實現(xiàn)某種源代碼的“約定”,從而實現(xiàn)各種豐富的快捷開發(fā)能力。
在反射的使用過程中,我們往往會發(fā)現(xiàn),源代碼直接作為數(shù)據(jù),還是會有一些問題。譬如我們的源代碼可能會根據(jù)一些非業(yè)務(wù)因數(shù)做修改,改名、改參數(shù)類型是在重構(gòu)的時候非常常見的。所以我們往往還是離不開配置文件,把源代碼里的名字寫到配置里面,然后框架再根據(jù)配置來運(yùn)行。一個比較典型的例子就是Hibernate,這一款著名的ORM框架,能讓你的源代碼類型和數(shù)據(jù)庫、表結(jié)構(gòu)關(guān)聯(lián)起來。按理說利用反射,我們可以直接建立一些和數(shù)據(jù)庫表、字段名字同名的對象,就能直接關(guān)聯(lián)了,但是我們的源代碼如果需要修改這些名字,再去改數(shù)據(jù)庫的內(nèi)容,就顯得太麻煩了。所以我們要編寫很多配置文件,來關(guān)聯(lián)什么表對應(yīng)什么類,什么字段對應(yīng)哪個屬性……這些配置文件往往和使用數(shù)據(jù)庫的表數(shù)量一樣多,任何的修改都還要記得對應(yīng)這些配置的修改,我們被迫同時維護(hù):數(shù)據(jù)庫結(jié)構(gòu)、配置文件、源代碼這三個東西。然而,如果我們的平臺是支持“元數(shù)據(jù)”的話,問題就很好解決了。因為我們可以在源代碼里面直接寫配置文件項目。我們在源代碼的類名前面,用類似注釋的方式,標(biāo)注這個類對應(yīng)數(shù)據(jù)庫的哪個表;在屬性名前面,用注釋標(biāo)注對應(yīng)的字段、默認(rèn)值等等。這樣我們只需要維護(hù)兩個東西:數(shù)據(jù)庫結(jié)構(gòu)、源代碼。這大大的減輕的項目的復(fù)雜程度。
我接觸的最早最著名的元數(shù)據(jù),是用來同步修改API文檔的JavaDoc技術(shù),這個技術(shù)讓更新文檔不再成為一個苦力活。由于可以在源代碼的注釋里面編寫文檔,所以在修改代碼的同時也可以同時更新文檔。更重要的是,javadoc標(biāo)記自然的把源代碼中的“名字表”和相關(guān)注釋自動對應(yīng)起來了,要知道,這種對應(yīng)如果人工來做,可是要費(fèi)相當(dāng)大的功夫。在javadoc的教育下,我對于java的注解、C#的attribute(特性)都覺得非常親切。以前那些需要登記大量類名、方法名的配置,統(tǒng)統(tǒng)都可以直接記錄在源代碼里面了。而一些和美術(shù)資源關(guān)聯(lián)的客戶端代碼,也可以通過源代碼的特殊標(biāo)記,連接上正確的圖形資源。
能讓這些源代碼里面的“元數(shù)據(jù)”生效的重要技術(shù),其實就是反射。由于我們的元數(shù)據(jù)處理程序,一般都需要和源代碼里面的類、方法名字對應(yīng)起來,所以都要使用反射的方法。而這種反射,又為我們?nèi)我庠黾印霸獢?shù)據(jù)”提供了強(qiáng)大的機(jī)制。
我們曾經(jīng)相信:數(shù)據(jù)結(jié)構(gòu)+算法=程序。但是從今天的軟件產(chǎn)業(yè)來看,固然還是有很多專事計算的軟件在被開發(fā)著,然而我們接觸到更多的軟件,都是所謂“信息管理系統(tǒng)”類的軟件。這類軟件要處理的并非是復(fù)雜的計算任務(wù),而是對各種各樣現(xiàn)實世界中的信息,增刪查改是這些信息處理最通俗的描述。我們在處理這些信息的時候,如果還是把程序的載體源代碼,僅僅看成是編譯過程中不可缺少的一環(huán)而已,那么我們就必須額外處理大量的數(shù)據(jù)形式:數(shù)據(jù)庫、配置文件、IDE配置……然而,在面向?qū)ο蟮娘L(fēng)潮之下,源代碼完全可以作為一種“樹狀”的數(shù)據(jù)承載方式。面向?qū)ο蠖x的類、成員、方法,就是一個個現(xiàn)實世界中的實體映像,他們所包含的結(jié)構(gòu)和常量,往往直接可以成為系統(tǒng)中的數(shù)據(jù)源頭。在MUD文字游戲中,幾乎整個游戲世界,都是以源代碼常量的形式編寫的,這不但沒有成為維護(hù)的難題,反而讓真?zhèn)€游戲的開發(fā)變得更輕松,因為程序員還是最習(xí)慣于面對源代碼去工作。
反射這種特性,能把源代碼中的所有數(shù)據(jù),包括“名字符號表”,都提供給開發(fā)者去使用,讓軟件開發(fā)過程,從單純的算法實現(xiàn)過程,變成一個綜合的信息管理的過程。這個做法看起來似乎不夠?qū)I(yè),但是在編程已經(jīng)不算“高科技”的年代,這種技術(shù)能幫助大量的開發(fā)者,以某種“約定”的方式去編寫源代碼,從而自動獲得框架的強(qiáng)大支持?!圃爝@種允許“約定”方式運(yùn)行源代碼的框架,正式新的框架應(yīng)該擁有的特點(diǎn),因為人類的創(chuàng)造時間,不應(yīng)該被浪費(fèi)在大量的重復(fù)而類似的工作之上??!
到此,關(guān)于“服務(wù)器中反射是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。