《豬弟拱Java》連載番外篇:Java代理(上)

glmapper_2018發表於2018-01-15

大家好,我是磊叔的豬弟,豬在我心中從來不是蠢的代名詞,而是懶的代名詞;大病一場之後發現自己更懶了,磊叔實在看不下去了,拖著我去打了一波籃球,感覺渾身又充滿了力量,鍛鍊還是很重要的,趁著這股傻勁,趕緊拱了拱Java中的動態代理。

代理是什麼?

代理這個詞大家並不陌生,我們通過一個案例來描述一下代理的概念。

案例: 譬如說鄧紫棋有個經紀人叫小明,經紀人就是一個代理物件,而鄧紫棋本身就是被代理的物件,鄧紫棋可以有多個經紀人,當然經紀人也可以代理多個明星,那麼經紀人的功能就是 幫助鄧紫棋打理一些繁瑣的事情,比如鄧紫棋手上同時有做很多的事情要做:

  • ①想買一套房子;
  • ②又要談多個演出;
  • ③想要去旅遊,又沒選好地方。

那麼此時經紀人就發揮功效了,一個經紀人可以幫著選房子、一個經紀人去談演出、一個經紀人去選旅遊地點,然後把結果都拿給鄧紫棋做選擇,這樣鄧紫棋要做的事就是付房款、去表演、去旅行,因為這些事情是經紀人完成不了的。所以這樣子就可以把多件繁瑣的事情給拆了出來,把不必要鄧紫棋親自去做的繁瑣的事情交給經紀人,自己只處理問題的核心。下面是兩張使用代理和不使用代理的兩張直觀圖。

《豬弟拱Java》連載番外篇:Java代理(上)

《豬弟拱Java》連載番外篇:Java代理(上)

結合上面的案例,可以對代理有個直觀的瞭解,關於有代理是好還是壞,大家自行判斷,只能說心疼寶強 。

Java中的代理

代理模式

代理(Proxy)是一種設計模式,提供了對目標物件另一種訪問方式;即通過代理物件訪問目標物件。這樣做的好處是:可以在目標物件實現的基礎上,增強額外的功能操作,即擴充套件目標物件的功能。常用代理來做方法增強,將核心程式碼和關注點程式碼(重複程式碼)分離,最好的例子就是Spring的AOP。

Java代理的種類

Java中的代理有三大類:

  • 靜態代理
  • JDK動態代理
  • glib子類代理

代理能幹嘛

代理能幹嘛,能幹的事情老多了,我們還是用一個場景來講述代理的功能。

場景一:(請忽略場景中的其他與主題無關的漏洞)

部門經理S讓程式設計師 小A、劉B、阿C 去開發某一塊功能,小A負責寫各種場景簡訊的統一呼叫方法,劉B和阿C 負責的功能會呼叫到發簡訊方法,唰唰唰,小A的簡訊功能寫好了,然後唰唰唰,劉B和阿C的功能也都做完了。測試的時候,劉B的各種模組功能都很正常,但就是簡訊發不出去,阿C的簡訊能正常發出去,此時測試就找上了劉B,劉B覺得自己的程式碼很NB不可能出問題,然後叫測試去找小A,小A看了半天沒看出什麼貓膩,然後叫測試找劉B,這下兩人都覺得自己的程式碼沒毛病,怎麼辦?

測試無奈,只好叫部門經理出面,部門經理看了看,給小A提了一個需求:在呼叫簡訊方法之前列印入參日誌,在傳送結束之後列印呼叫結果,這樣出了什麼問題,看一下日誌就一目瞭然了。但是小A就有點崩潰了,小A為每一個場景都寫了一個類,足足幾十個場景對應了幾十個類(此處的類設計是有問題的,不是關注點,請忽略),要一個一個去手動加日誌,沒辦法,小A就去寫了,唰唰唰,複製貼上完成了。下面是小A的程式碼原來的程式碼

/**
 * 功能描述: 簡訊介面
 * 
 * @author 小A 
 * @since v1.0.0 
 */
public interface ISmsService {
   /**
     * 傳送場景簡訊
     *
     * @param content
     * @param phoneNumber
     * @return
     */
    boolean sendMsg(String content, String phoneNumber);
}
複製程式碼
/**
 * 功能描述:場景簡訊實現
 *
 * @author 小A
 * @since v1.0.0
 */
public class SmsServiceForScan1 implements ISmsService {
    /**
     * 簡訊支援元件
     */
    private SmsSendSupport smsSendSupport = new SmsSendSupport();
    /**
     * 傳送場景一簡訊
     *
     * @param content
     * @param phoneNumber
     * @return
     */
    @Override
    public boolean sendMsg(String content, String phoneNumber) {
        return smsSendSupport.send(content, phoneNumber, 1);
    }
}
複製程式碼
/**
 *
 功能描述:
 *
 * @author 小A
 * @since v1.0.0
 */
public class SmsServiceForScan2 implements ISmsService {
    /**
     * 簡訊支援元件
     */
    private SmsSendSupport smsSendSupport = new SmsSendSupport();
    /**
     * 傳送場景一簡訊
     *
     * @param content
     * @param phoneNumber
     * @return
     */
    @Override
    public boolean sendMsg(String content, String phoneNumber) {
        return smsSendSupport.send(content, phoneNumber, 1);
    }
}
複製程式碼

下面小A對每一個類都手動打上了日誌:

/**
 * 功能描述:
 *
 * @author 小A
 * @since v1.0.0
 */
public class SmsServiceForScan1 implements ISmsService {
    /**
     * 日誌
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(SmsServiceForScan1.class);
    /**
     * 簡訊支援元件
     */
    private SmsSendSupport smsSendSupport = new SmsSendSupport();
    /**
     * 傳送場景一簡訊
     *
     * @param content
     * @param phoneNumber
     * @return
     */
    @Override
    public boolean sendMsg(String content, String phoneNumber) {
        LOGGER.info("sendMsgForScan1入參為:{},{}",content,phoneNumber);
        boolean result = smsSendSupport.send(content, phoneNumber, 1);
        LOGGER.info("返回結果為:{}",result);
        return result;
    }
}
複製程式碼

其他幾十個類與上面類似,此處省略......

唰唰唰,經過一番測試,在日誌中發現劉B的程式碼中傳送過來的電話號碼都是null,這下鍋就甩給了劉B,至於是什麼原因導致的電話號碼為null不是我們關注的重點。

好的,小A成功甩鍋,回頭CodeReview的時候,部門經理S發現了小A的這段加日誌的程式碼,部門經理很和藹,叫了小A過去看了一下那段程式碼。

S:小夥子,工作完成的不錯,但是你看這段程式碼有什麼毛病嗎?

小A:(看了一下)沒毛病啊。

S:功能確實是實現了,但是你可以發現這些程式碼中打日誌的程式碼都是可以提取出來的不是嗎?

小A:(想了一下)好像是的哎!可是該怎麼做呢。

S:你知道Java中的代理嗎?

小A:(恍然大悟)謝謝經理,我知道該怎麼做了。

小A回去就找了一下代理的資料,唰唰唰,就完成了,比之前可快多了呢,程式碼如下:

靜態代理實現日誌列印:

/**
 * 功能描述:簡訊代理類
 *
 * @author 小A
 * @since v1.0.0
 */
public class SmsServiceProxy implements ISmsService {

    private static final Logger LOGGER = LoggerFactory.getLogger(SmsServiceProxy.class);

    private ISmsService smsService;

    /**
     * 構造器
     */
    public SmsServiceProxy(ISmsService smsService) {
        this.smsService = smsService;
    }

    /**
     * 代理髮送簡訊方法
     */
    @Override
    public boolean sendMsg(String content, String phoneNumber) {
        //統一列印入參
        LOGGER.info("sendMsgForScan1入參為:{},{}", content, phoneNumber);
        //執行核心程式碼
        boolean result = smsService.sendMsg(content, phoneNumber);
        //統一列印返回結果
        LOGGER.info("返回結果為:{}", result);
        //返回結果
        return result;
    }
}
複製程式碼

經過代理類的改造,之前每個類中重複列印日誌的程式碼都可以刪除了,程式碼量一下子減少了很多,結構感更加強了。

部門經理看了也很欣慰,叫了小A過來,問道。

S:你為什麼不選擇動態代理和cglib子類代理呢?

小A:我也有想過,不過靜態代理相對於其他兩種代理是有一個優點的——輕量,而且靜態代理在這裡已經能夠夠用了

S:嗯,不錯不錯。

下面先說下三類代理的使用條件和代理範圍

① 靜態代理可以代理某一類物件,這一類物件必須實現同一介面,所以它的使用條件就是被代理類要實現介面。上面的各種場景簡訊都實現了ISmsService介面,所以代理類可以代理所有場景簡訊實現類,並呼叫真正的簡訊傳送方法去傳送正確的場景簡訊。

① 動態代理的使用條件也是被代理類要實現介面,但是動態代理能夠代理所有實現了介面的類,強大必然也會有缺點:動態代理依賴Java反射機制,反射是一個比較影響效能的功能,所以動態代理效能上會比靜態代理低。

③cglib子類代理,首先需要依賴第三方庫,然後它是基於位元組碼來生成子類代理的,沒有特定的使用條件,所以也不需要實現介面,它可以代理所有的類,所以論效能是比不上靜態代理的。

本來想把所有的一次性寫完的,不料被朋友拖出去吃了頓火鍋,回來就晚了,無奈,把動態代理和cglib子類代理放在下一篇吧

為了為大家提供更加方便的閱讀,小夥伴們可以關注我們的公眾微訊號哦...

《豬弟拱Java》連載番外篇:Java代理(上)

相關文章