<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java反射

          共 4091字,需瀏覽 9分鐘

           ·

          2021-01-04 16:54


          本文公眾號(hào)來(lái)源:安琪拉的博客

          作者:安琪拉的博客

          本文已收錄至我的GitHub


          剛開(kāi)始學(xué)Java 一般不太會(huì)關(guān)注到反射,但是如果看很多框架的源碼,發(fā)現(xiàn)反射無(wú)處不在。最近一個(gè)業(yè)務(wù)需求中用了反射,感覺(jué)非常絲滑。

          前文回顧(推薦點(diǎn)擊下方藍(lán)色鏈接閱讀):

          Java 程序員都需要懂的 反射!

          前言

          魯班: ?什么是反射?

          安琪拉: ?反射是Java 中提供的運(yùn)行期獲取對(duì)象信息的能力。先記住二個(gè)關(guān)鍵詞:運(yùn)行期、對(duì)象信息。

          魯班: ?那為什么Java 需要反射呢?需要在運(yùn)行期獲取對(duì)象信息呢?

          安琪拉: ?比如你希望調(diào)用某個(gè)對(duì)象的方法,例如下面這段代碼:

          angela 對(duì)象你如果運(yùn)行期不知道它是否有dance 方法, 可以調(diào)用 getClass().getMethod("dance") 判斷一下。

          魯班: ?那豈不是很弱雞,我可以直接調(diào) angela.dance() 方法啊!

          安琪拉: ?你要用 angela.dance() 方法,包里是不是需要 import Angela 類,一定要有確定的Angela 對(duì)象,很多框架場(chǎng)景,是不知道目標(biāo)對(duì)象的Class類型的,要?jiǎng)討B(tài)獲取對(duì)象的類類型。

          魯班: ?我知道了,反射就是運(yùn)行的時(shí)候知道這個(gè)對(duì)象能不能調(diào)某個(gè)方法。

          安琪拉: ?不止如此,反射就是對(duì)于任意一個(gè)對(duì)象,我們能夠運(yùn)行時(shí)訪問(wèn)它的方法和屬性。

          魯班: ?為什么強(qiáng)調(diào)運(yùn)行時(shí)?

          安琪拉: ? 因?yàn)槭蔷幾g期,類型是確定的,很多時(shí)候在拿不到確定的對(duì)象的屬性和值的時(shí)候,需要運(yùn)行時(shí)動(dòng)態(tài)調(diào)用方法或獲取屬性。后面會(huì)介紹一個(gè)通用框架能力通過(guò)反射實(shí)現(xiàn)的sample。

          先說(shuō) Java 反射API相關(guān)的類有下面幾個(gè):

          這里可以引出一個(gè)很有意思的話題,Java 中一切皆對(duì)象,那Class 也是對(duì)象,另外所有對(duì)象都有對(duì)應(yīng)的Class(類),Class(類)就像餅干模板,Object(對(duì)象)是根據(jù)Class(類) 做出的餅干,那JDK 加載時(shí)先有Class 還是先有Object 呢?如何加載 ?這個(gè)可以留個(gè)思考題。

          真實(shí)業(yè)務(wù)場(chǎng)景

          魯班: ?那知道反射有什么用?對(duì)我平常寫(xiě) curd 有幫助嗎?

          安琪拉: ?有幾點(diǎn)原因要知道反射,一個(gè)是一些框架代碼里面會(huì)有很多反射,例如, 我們經(jīng)常接觸的動(dòng)態(tài)代理, Spring的自定義注解。另外我們?nèi)绻M褟臉I(yè)務(wù)層代碼抽象出一些平臺(tái)能力,就可以用反射。

          魯班: ?你這么說(shuō)沒(méi)有體感,能不能舉個(gè)例子?

          安琪拉: ?那你繼續(xù)說(shuō)說(shuō)上次你的需求。

          魯班:你說(shuō)我最近接到了一個(gè)需求啊,要在下路把對(duì)方每一波過(guò)來(lái)的小兵做標(biāo)注,只有遇到特定的小兵,我才開(kāi)火。

          安琪拉: 那這些小兵有什么特點(diǎn)呢?你打算怎么精準(zhǔn)定位要開(kāi)火的小兵?

          魯班:如果小兵身上的符文是紅色符文(除此以外,還有藍(lán)色符文和紫色符文),法術(shù)防御是魔法防御(除此以外,還有物理防御),我只對(duì)這些小兵開(kāi)火,當(dāng)然咯,可能以后還需要對(duì)帶各種屬性組合的小兵進(jìn)行開(kāi)火打擊。

          安琪拉: 需求我大概清楚了,你有思路了嗎?我們先把模型建立起來(lái),如下圖所示,是小兵的模型

          魯班:你上次說(shuō)了,寫(xiě)業(yè)務(wù)代碼的時(shí)候要考慮通用性和可擴(kuò)展性,但是這個(gè)功能也能用反射嗎?

          安琪拉: ?我們拆解一下需求,希望對(duì)于指定對(duì)象,這個(gè)對(duì)象上具有指定屬性值或某些屬性值時(shí),我們做一些后置業(yè)務(wù)處理。這個(gè)是我們做的業(yè)務(wù)邏輯抽象,這個(gè)就是設(shè)計(jì)能力。

          安琪拉: ?我們列一下有幾個(gè)變量: ?對(duì)象不確定、提取的屬性不確定、 提取屬性的個(gè)數(shù)不確定、屬性值不確定,最后是要做的后續(xù)業(yè)務(wù)處理邏輯不確定。怎么把模型做的足夠通用呢?我們來(lái)設(shè)計(jì)一下。

          魯班:但是產(chǎn)品給我這個(gè)需求就是判斷小兵對(duì)象的符文和防御屬性值啊?

          安琪拉: ?如果只是按照產(chǎn)品的需求搞,以后有的改,所以索性一次把模型設(shè)計(jì)的通用。我們可以這么搞:

          安琪拉: ?我們抽象后可以把這個(gè)服務(wù)叫做定位服務(wù),如上所示,我們希望無(wú)論是什么對(duì)象,可以判斷對(duì)象的指定屬性值和預(yù)期值是否一致。這里用反射獲取到屬性的get 方法,然后調(diào)用get 方法獲取屬性值,和預(yù)期值做比較,這里 getReadMethod 方法為了方便說(shuō)明做了簡(jiǎn)化,很多情況沒(méi)寫(xiě)進(jìn)入,比如屬性是boolean 類型,get方法前綴是is,比如是父類或接口的方法等等。

          魯班:這樣寫(xiě)有什么好處呢?

          安琪拉: ?這樣就把原來(lái)的只對(duì)Batman 對(duì)象的屬性做判斷做了一層抽象,這樣以后類似的需求都可以滿足了。我們來(lái)做一下對(duì)比:

          魯班:這二個(gè)方案都是判斷 batman(小兵)身上帶的 rune(符文)是不是紅色,如果是紅色,就開(kāi)火。但是新方案用了反射,有什么優(yōu)勢(shì)嗎?

          安琪拉: ?實(shí)際業(yè)務(wù)場(chǎng)景里面,規(guī)則往往比這個(gè)復(fù)雜很多,而且還會(huì)一直變化,怎么把方案做的通用性和可擴(kuò)展性更新,同時(shí)性能損耗減少到最少使我們要考慮的問(wèn)題。例如:產(chǎn)品經(jīng)理跟你說(shuō),這次除了對(duì)batman(小兵)身上帶的 rune(符文)一定是紅色開(kāi)火,條件還要加一條必須盔甲是防法術(shù)傷害的才開(kāi)火,或者是二者滿足其中一條就開(kāi)火,除了batman(小兵)做判斷,也要對(duì)野怪、對(duì)方英雄做屬性值判斷。

          魯班:你的意思是業(yè)務(wù)需求這么變,我用反射做了通用性功能,可以不需要重復(fù)寫(xiě)代碼嗎?

          安琪拉: ?對(duì)呀。到時(shí)候你可以抽更多時(shí)間來(lái)研究?的技能。

          魯班:用反射可以實(shí)現(xiàn)對(duì)不同對(duì)象做業(yè)務(wù)邏輯處理,我可以理解,但是你剛才說(shuō)的那些條件之前的業(yè)務(wù)規(guī)則,比如同時(shí)滿足,二者滿足其一就可以怎么能做到復(fù)用呢?

          安琪拉: ? 你可以建一個(gè)規(guī)則表,一個(gè)條件表,規(guī)則表中有規(guī)則的場(chǎng)景、規(guī)則關(guān)聯(lián)的條件(可以多個(gè)),條件之前的關(guān)系。條件的關(guān)系你可以設(shè)計(jì)的靈活一些,支持四類:

          • simple ?簡(jiǎn)單條件,滿足一個(gè)屬性值就符合

          • and ? 多個(gè)條件都要滿足

          • or ? 多個(gè)條件滿足其中一個(gè)

          • expression ?表達(dá)式,如果上面都滿足不了,你還可以支持自定義表達(dá)式,例如: (A & B) | (C &D)

          以上面的需求舉例,條件表存二條記錄,分別是

          1. propertyKey="rune" , ?propertyValue= "red", conditionName=\"**\", conditionId=\"\"

          2. propertyKey="armor" , ?propertyValue= "magic-defend", conditionName=\"**\", conditionId=\"\"

          規(guī)則定為:

          1. 條件組: 條件1,條件2 ?

          2. 條件關(guān)系: and (代表?xiàng)l件都要滿足)

          然后你的產(chǎn)品經(jīng)理需求變更了,你只需要新增規(guī)則和條件,或者修改規(guī)則表的記錄就可以了。

          魯班:既然都說(shuō)到這個(gè)份上了,能給寫(xiě)段代碼嗎?干說(shuō)不練假把式

          安琪拉: ?好的,如下圖代碼所示,這套帶有規(guī)則的反射可以應(yīng)付來(lái)自產(chǎn)品各種花樣需求了。

          魯班:那規(guī)則表和條件表我都建在數(shù)據(jù)庫(kù)嗎?

          安琪拉: ?有配置中心,可以把表建在配置中心,本地做份緩存, 沒(méi)有放在數(shù)據(jù)庫(kù)也可以,做好一致性。

          魯班:并發(fā)量非常高的時(shí)候,反射不會(huì)影響程序的性能嗎?聽(tīng)說(shuō)反射很耗性能。

          安琪拉: ?反射的確對(duì)性能有損耗,但你知道反射為什么影響性能嗎?性能主要損耗在哪里?

          魯班:不知道。

          反射性能問(wèn)題

          安琪拉: ?反射影響性能是因?yàn)檫\(yùn)行時(shí),程序需要?jiǎng)討B(tài)解析的類型,例如Class.getDeclaredMethod 的時(shí)候方法方法的類型都是運(yùn)行時(shí)檢查,Java虛擬機(jī)也沒(méi)辦法優(yōu)化,每次Method 執(zhí)行都要從Class 類信息中加載,我們知道類的方法信息是放在單獨(dú)的方法區(qū)的,對(duì)象在堆區(qū),但是相比于反射帶來(lái)的便利,如果不是高并發(fā)需要十分頻繁的調(diào)用,反射的性能損耗可以忽略,并且反射性能損耗也有方法優(yōu)化降低。

          魯班:怎么優(yōu)化反射的性能損耗?

          安琪拉: ?例如,我們前面每次調(diào) locateObject 時(shí)都需要查找Method,我們可以把第一次查找的 Method 緩存起來(lái),下次就不需要再調(diào)Class.getDeclaredMethod了。我們可以看下Class.getDeclaredMethod 內(nèi)部處理邏輯,是比較耗性能的。

          下圖是截取的一段源碼:

          魯班:那如果我要用反射,這個(gè)性能問(wèn)題需要我自己做緩存嗎?

          安琪拉: ?其實(shí)已經(jīng)有現(xiàn)成的框架想到了反射的性能問(wèn)題,因此可以直接用就好了。

          魯班:啊。。。在哪里?

          安琪拉: ?就是大名鼎鼎的 springframework 的 BeanUtils,我們上面的那段自己實(shí)現(xiàn)的獲取Method的代碼可以改寫(xiě)成如下這樣:

          在BeanUtils中實(shí)現(xiàn)了Method 的緩存。

          我們對(duì)反射做一個(gè)簡(jiǎn)單的性能測(cè)試, 對(duì)反射代碼執(zhí)行100萬(wàn)次,打印耗時(shí), 同時(shí)看Cpu、堆內(nèi)存和非堆內(nèi)存占用情況:

          性能指標(biāo)截圖如下:

          CPU、內(nèi)存反射和常規(guī)內(nèi)存占用基本差別不到,100萬(wàn)次耗時(shí)多290ms左右,

          100萬(wàn)次反射調(diào)用:?

          299ms,多次執(zhí)行,在上下浮動(dòng)。因此使用Spring framework提供的BeanUtils 包,反射性能影響很少。

          在阿里巴巴開(kāi)發(fā)規(guī)約有一條

          【強(qiáng)制】避免用Apache Beanutils進(jìn)行屬性的copy。
          說(shuō)明:Apache BeanUtils性能較差,可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier,注意均是淺拷貝。
          反例:[性能提升300%:Apache的BeanUtils的坑]

          Apache BeanUtils 在類似copyProperties 方法實(shí)現(xiàn)機(jī)制上和Spring BeanUtils 略有不同,Apache BeanUtils 拷貝機(jī)制做了各種轉(zhuǎn)換和解析邏輯, 導(dǎo)致性能變差,大家使用的時(shí)候注意區(qū)分。

          歡迎關(guān)注我的微信公眾號(hào)【面試造火箭】來(lái)聊聊Java面試

          添加我的微信進(jìn)一步交流和學(xué)習(xí)

          如果顯示頻繁,微信手動(dòng)搜索sanwaiyihao添加即可

          點(diǎn)亮在看轉(zhuǎn)發(fā)是我持續(xù)更新的動(dòng)力,對(duì)我真的很重要!

          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  麻豆电影久久 | 四虎影院毛片 | 抽插视频网站 | 久久国产劲爆∧v内射 | 日本aa免费看 |