1.約定編程

約定編程

  • 本節(jié)我們完全拋開AOP的概念祖凫,先來看一個(gè)約定編程的實(shí)例。當(dāng)你弄明白了這個(gè)實(shí)例后酬凳,相信SpringAOP 的概念也就很容易理解了惠况,因?yàn)閷?shí)質(zhì)上它們是異曲同工的東西。只是這需要自己去實(shí)踐宁仔,如果僅僅是看看售滤,那么效果肯定就會(huì)相差幾個(gè)檔次。

約定

  • 首先來看一個(gè)簡(jiǎn)單不需要去解釋的接口台诗,如下代碼
public interface HelloService {
    public void sayHello(String name);
}
  • 這個(gè)接口很簡(jiǎn)單完箩,就是定義一個(gè)sayHello的方法,其中的參數(shù)name是名字拉队,這樣就可以對(duì)該名字說hello了弊知。于是很快我們可以得到這樣的一個(gè)實(shí)現(xiàn)類,如下代碼
public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello(String name) {
        if (name == null || name.trim() == "") {
            throw new RuntimeException("parameter is null!!");
        }
        System.out.println("hello " + name);
    }
}
  • 好了粱快,這里的代碼也很簡(jiǎn)單秩彤,方法 sayHello首先判斷name是否為空。如果為空事哭,則拋出異常漫雷,告訴調(diào)用者參數(shù)為空:如果不為空,則對(duì)該名字說 hello鳍咱。
  • 這樣一個(gè)幾乎就是最簡(jiǎn)單的服務(wù)寫好了降盹。下面先定義一個(gè)攔截器接口,它十分簡(jiǎn)單谤辜,只是存在幾個(gè)方法蓄坏,如下代碼
/**
 * 攔截器接口
 */
public interface Interceptor {
    /**
     * 事前方法
     * @return
     */
    public boolean before();

    /**
     * 事后方法
     */
    public void after();

    /**
     * 取代原有事件方法
     * @param invocation 回調(diào)參數(shù)价捧,可以通過它的proceed方法,回調(diào)原有事件
     * @return 原有事件返回對(duì)象
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public Object around(Invocation invocation)throws InvocationTargetException, IllegalAccessException;

    /**
     * 是否返回方法涡戳。事件沒有發(fā)生異常執(zhí)行
     */
    public void afterReturning();

    /**
     * 事后異常方法结蟋,當(dāng)事件發(fā)生異常后執(zhí)行
     */
    public void afterThrowing();

    /**
     * 是否使用around方法取代原有方法
     * @return
     */
    boolean useAround();
}
  • 這個(gè)接口的定義我也是設(shè)計(jì)了一番的,后面會(huì)給出約定渔彰,將這些方法織入流程中嵌屎。這里我們首先給出around方法中的參數(shù)Invocation對(duì)象的源碼,如下代碼
public class Invocation {
    private Object target;
    private Method method;
    private Object[] params;

    public Invocation(Object target, Method method, Object[] params) {
        this.target = target;
        this.method = method;
        this.params = params;
    }

    /**
     * 反射方法
     *
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, params);
    }
}
  • 它沒有太多可以探討的內(nèi)容恍涂,唯一值得探討的就是proceed方法编整,它會(huì)以反射的形式去調(diào)用原有的方法。
  • 接著乳丰,你可以根據(jù)攔截器(Interceptor)接口的定義開發(fā)一個(gè)屬于你自己的攔截器MyInterceptor,如下代碼
public class MyInterceptor implements Interceptor {
    @Override
    public boolean before() {
        System.out.println("before....");
        return false;
    }

    @Override
    public void after() {
        System.out.println("after....");
    }

    @Override
    public Object around(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
        System.out.println("around before....");
        Object obj = invocation.proceed();
        System.out.println("around after....");
        return obj;
    }

    @Override
    public void afterReturning() {
        System.out.println("afterReturning....");
    }

    @Override
    public void afterThrowing() {
        System.out.println("afterThrowing....");
    }

    @Override
    public boolean useAround() {
        return true;
    }
}
  • 這個(gè)攔截器的功能也不復(fù)雜内贮,接著就要談?wù)勎液湍愕募s定了产园。約定是本節(jié)的核心,也是Spring AOP的本質(zhì)夜郁。我先提供一個(gè)類——ProxyBean給讀者使用什燕,它有一個(gè)靜態(tài)的(static)方法:
    • public static Object getProxyBean(Object target, Interceptor interceptor)
  • 這個(gè)方法的說明如下:
    • 要求參數(shù)target對(duì)象存在接口,而interceptor對(duì)象則是代碼HelloService的接口對(duì)象竞端;
    • 那么它將返回一個(gè)對(duì)象屎即,我們把這個(gè)返回的對(duì)象記為proxy,你可以使用target對(duì)象實(shí)現(xiàn)的接口類型對(duì)它進(jìn)行強(qiáng)制轉(zhuǎn)換事富。
  • 于是你就可以使用它拿到proxy了技俐,例如,下面的代碼:
HelloService helloService = new HelloServiceImpl();
HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor());
  • 然后我再提供下面的約定统台。請(qǐng)注意雕擂,這個(gè)約定是十分重要的。
  • 當(dāng)調(diào)用proxy對(duì)象的方法時(shí)贱勃,其執(zhí)行流程如下井赌。
    • 1)使用proxy調(diào)用方法時(shí)會(huì)先中
    • 2)如果攔截器的useAround方法返回true,則執(zhí)行攔截器的around方法贵扰,而不調(diào)用target對(duì)象對(duì)應(yīng)的方法仇穗,但around方法的參數(shù)invocation對(duì)象存在一個(gè)proceed方法,它可以調(diào)用target對(duì)象對(duì)應(yīng)的方法戚绕;
    • 3)無論怎么樣纹坐,在完成之前的事情后,都會(huì)執(zhí)行攔截器的after方法舞丛;
    • 4)在執(zhí)行around方法或者回調(diào)target的事件方法時(shí)恰画,可能發(fā)生異常宾茂,也可能不發(fā)生異常,也可能不發(fā)生異常拴还。如果發(fā)生異常跨晴,就執(zhí)行攔截器的afterThrowing方法,否則就執(zhí)行afterReturning方法片林。
  • 下面我們?cè)儆昧鞒虉D來展示這個(gè)約定流程


    image
  • 有了這個(gè)流程圖端盆,就可以進(jìn)行約定編程了。如下代碼測(cè)試約定流程费封。
public class testProxy {
    public static void main(String[] args) {
        HelloService helloService = new HelloServiceImpl();
        HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor());
        proxy.sayHello("zhangsan");
        System.out.println("########### name is null! ###########");
        proxy.sayHello(null);
    }
}
  • 按照我們的約定焕妙,這段代碼打印的信息如下:
before....
around before....
hello zhangsan
around after....
after....
afterReturning....
########### name is null! ###########
before....
around before....
after....
afterThrowing....

ProxyBean的實(shí)現(xiàn)

  • 如何將服務(wù)類和攔截方法織入對(duì)應(yīng)的流程,是ProxyBean要實(shí)現(xiàn)的功能弓摘。首先要理解動(dòng)態(tài)代理模式焚鹊。其實(shí)代理很簡(jiǎn)單,例如韧献,當(dāng)你需要采訪一名兒童時(shí)末患,首先需要經(jīng)過它父母的同意,在一些問題上父母也許會(huì)替他回答锤窑,而對(duì)于另一些問題璧针,也許父母覺得不太合適這個(gè)小孩會(huì)拒絕掉,顯然這時(shí)父母就是這名兒童的代理(proxy)了渊啰。通過代理可以增強(qiáng)或者控制對(duì)兒童這個(gè)真實(shí)對(duì)象(target)的訪問探橱,如下圖。


    image
  • 也就是需要一個(gè)代理對(duì)象绘证。在JDK中隧膏,提供了類Proxy的靜態(tài)方法——newProxyInstance,其內(nèi)容具體如下:
    • public static Object newProxyinstance (ClassLoader classLoader, Class<?>[] interfaces, InvocationHandler invocationHandler) throws IllegalArguπ1entException
  • 給予我們來生成一個(gè)代理對(duì)象(proxy)嚷那,它有3個(gè)參數(shù):
    • classLoader——類加載器私植;
    • interface——綁定的接口,也就是把代理對(duì)象綁定到哪些接口下车酣,可以是多個(gè)曲稼;
    • invocationHandler——綁定代理對(duì)象邏輯實(shí)現(xiàn)。
  • 這里的invocationHandler是一個(gè)接口InvocationHandler對(duì)象湖员,它定義了一個(gè)invoke方法贫悄,這個(gè)方法就是實(shí)現(xiàn)代理對(duì)象的邏輯的,其定義代碼如下
/**
* 處理代理對(duì)象方法邏輯
* @param proxy 代理對(duì)象
* @param method 當(dāng)前方法
* @param args 運(yùn)行參數(shù)
* @return 方法調(diào)用結(jié)果
*/
publi Object invoke(Object proxy, Metthod method, Object[] args);
  • 然后通過目標(biāo)對(duì)象(target)娘摔,方法(method)和參數(shù)(args)就能夠反射方法運(yùn)行了窄坦,于是我們可以實(shí)現(xiàn)ProxyBean的源碼如下代碼。
public class ProxyBean implements InvocationHandler {

    private Object target = null;
    private Interceptor interceptor = null;

    /**
     * 綁定代理對(duì)象
     *
     * @param target      被代理對(duì)象
     * @param interceptor 攔截器
     * @return 代理對(duì)象
     */
    public static Object getProxyBean(Object target, Interceptor interceptor) {
        ProxyBean proxyBean = new ProxyBean();
        //保存被代理對(duì)象
        proxyBean.target = target;
        //保存攔截器
        proxyBean.interceptor = interceptor;
        //生成代理對(duì)象
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                proxyBean);
        //返回代理對(duì)象
        return proxy;
    }

    /**
     * 處理代理對(duì)象方法邏輯
     *
     * @param proxy  代理對(duì)象
     * @param method 當(dāng)前方法
     * @param args   運(yùn)行參數(shù)
     * @return 方法調(diào)用結(jié)果
     * @throws Throwable 異常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //異常標(biāo)識(shí)
        boolean exceptionFlag = false;
        Invocation invocation = new Invocation(target, method, args);
        Object retObj = null;
        try {
            if (this.interceptor.before()) {
                retObj = this.interceptor.around(invocation);
            } else {
                retObj = method.invoke(target, args);
            }
        } catch (Exception ex) {
            //產(chǎn)生異常
            exceptionFlag = true;
        }
        this.interceptor.after();
        if (exceptionFlag) {
            this.interceptor.afterThrowing();
        } else {
            this.interceptor.afterReturning();
        }
        return null;
    }
}
  • 首先,這個(gè)ProxyBean實(shí)現(xiàn)了接口InvocationHandler鸭津,因此就可以定義invoke方法了彤侍。其中在getBean方法中,我讓其生成了一個(gè)代理對(duì)象逆趋,并且創(chuàng)建了ProxyBean實(shí)例保存目標(biāo)對(duì)象(target)和攔截器(interceptor)盏阶,為了后面的調(diào)用做好了準(zhǔn)備,其次闻书,生成了一個(gè)代理對(duì)象名斟,而這個(gè)代理對(duì)象掛在target實(shí)現(xiàn)的接口之下,所以你可以用target對(duì)象實(shí)現(xiàn)的接口對(duì)這個(gè)代理對(duì)象實(shí)現(xiàn)強(qiáng)制轉(zhuǎn)換魄眉,并且將這個(gè)代理對(duì)的邏輯掛在ProxyBean實(shí)例之下矾削,這樣就完成了目標(biāo)對(duì)象(target)和代理對(duì)象(proxy)的綁定队腐。最后腥泥,將代理對(duì)象返回給調(diào)用者伯襟。于是在如下代碼中我們只需要通過以下兩句代碼:
HelloService helloService = new HelloServiceImpl();
//按約定獲取
HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor());
  • 就可以獲取這個(gè)代理對(duì)象了,當(dāng)我們使用它調(diào)用方法時(shí)晃择,就會(huì)進(jìn)入ProxyBean的invoke方法里冀值,而這個(gè)invoke方法就是按照上面的約定流程圖來實(shí)現(xiàn)的,這就是我們可以通過一定的規(guī)則完成約定編程的原因藕各。動(dòng)態(tài)代理的概念比較抽象,掌握不易焦除,這里建議對(duì)invok方法進(jìn)行調(diào)式激况,一步步印證它運(yùn)行的過程。編程是門實(shí)踐學(xué)科膘魄,通過自己動(dòng)手會(huì)有更加深入的理解乌逐。

總結(jié)

  • 到現(xiàn)在為止,我們并沒有講述關(guān)于AOP的概念创葡,本節(jié)只是通過約定告訴讀者浙踢,只要提供一定的約定規(guī)則,按照約定編程后洛波,就可以把自己開發(fā)的代碼織入約定的流程中蹬挤。而實(shí)際上在開發(fā)中,你只需要知道框架和你的約定便可以了扫茅。在現(xiàn)實(shí)中很多框架也是這么做的爆雹,換句話說钙态,Spring AOP也是這么做的蚓挤,它可以通過與我們的約定崇呵,把對(duì)應(yīng)的方法通過動(dòng)態(tài)代理技術(shù)織入約定的流程中荒辕,這就是Spring AOP編程的本質(zhì)叠骑。所以掌握Spring AOP的根本在于掌握其對(duì)我們的約定規(guī)則,下面的部分讓我們來學(xué)習(xí)它們。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锣夹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子银萍,更是在濱河造成了極大的恐慌变勇,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贴唇,死亡現(xiàn)場(chǎng)離奇詭異搀绣,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)戳气,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門链患,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瓶您,你說我怎么就攤上這事麻捻。” “怎么了呀袱?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵贸毕,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我夜赵,道長(zhǎng)明棍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任寇僧,我火速辦了婚禮摊腋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婉宰。我一直安慰自己歌豺,他們只是感情好推穷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布心包。 她就那樣靜靜地躺著,像睡著了一般馒铃。 火紅的嫁衣襯著肌膚如雪蟹腾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天区宇,我揣著相機(jī)與錄音娃殖,去河邊找鬼。 笑死议谷,一個(gè)胖子當(dāng)著我的面吹牛炉爆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼芬首,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼赴捞!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起郁稍,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤赦政,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后耀怜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體恢着,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年财破,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了掰派。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡狈究,死狀恐怖碗淌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抖锥,我是刑警寧澤亿眠,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站磅废,受9級(jí)特大地震影響纳像,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拯勉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一竟趾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宫峦,春花似錦岔帽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至妥曲,卻和暖如春贾费,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背檐盟。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工褂萧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人葵萎。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓导犹,卻偏偏與公主長(zhǎng)得像唱凯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谎痢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353