一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

手把手教你寫個(gè)AOP框架

 redalx 2015-06-16

 

Why AOP?

 

AOP(Aspect-Oriented Programming),意思是面向切面編程。傳統(tǒng)的OOP面向?qū)ο笙喈?dāng)于站在一個(gè)上帝模式從上往下看,里面的一塊塊都是一個(gè)對(duì)象,由我任意組合;而AOP不同之處在于,他是以一個(gè)旁觀者的身法,從“側(cè)面”看整個(gè)系統(tǒng)模塊,看看哪里可以見縫插針,將自己想要處理的一段業(yè)務(wù)邏輯“編織”進(jìn)去。


Code duplication is the ultimate code smell. It’s a sign that something is very wrong with implementation or design.(重復(fù)的代碼會(huì)讓代碼的質(zhì)量很糟糕。如果出現(xiàn)這個(gè)狀況,那么一定是實(shí)現(xiàn)或者設(shè)計(jì)環(huán)境出了問題)


OOP本身是極力反對(duì)“重復(fù)發(fā)明輪子”的,但是有時(shí)卻對(duì)重復(fù)的代碼顯得無可奈何,而AOP本身是一種很好的能解決這個(gè)問題的一種思想。

抽象了半天,還是利用一個(gè)例子還更加形象的解釋吧。如果你要做一個(gè)權(quán)限系統(tǒng),那么肯定需要在很多業(yè)務(wù)邏輯之前都加上一個(gè)權(quán)限判斷——只有符合條件的才能完成后面的操作。如果利用傳統(tǒng)思想,很顯然你會(huì)把做權(quán)限判斷的業(yè)務(wù)邏輯做封裝,然后在每個(gè)業(yè)務(wù)邏輯執(zhí)行之前都執(zhí)行以下那片處理權(quán)限判斷的代碼。如下圖:

看到?jīng)],每次一個(gè)判斷每次一個(gè)判斷,如果讓這些權(quán)限判斷的代碼散落在系統(tǒng)的各個(gè)角落,那會(huì)是一個(gè)噩夢(mèng)!就算采用OOP思想,將權(quán)限檢查的業(yè)務(wù)放在一個(gè)類中,照樣無濟(jì)于事。因?yàn)槊慷螛I(yè)務(wù)代碼開頭總有這么一段抹不掉的身影(doSecurityCheck)。

這時(shí),AOP老兄終于按耐不住,要出場大展身手了!這位老兄馬上說,放著那段業(yè)務(wù)邏輯代碼,我來處理!

他首先將權(quán)限處理的部分視作一個(gè)aspect(切面),然后想辦法在運(yùn)行時(shí)把切面weave(編織)進(jìn)業(yè)務(wù)邏輯中合適的位置。比如就像這樣做:

這樣,AOP就成功的幫我把權(quán)限驗(yàn)證部分插入到調(diào)用代碼的前面執(zhí)行。具體調(diào)用哪個(gè)方法其實(shí)AOP并不知道,只要你把切面織入了用戶登錄,那后調(diào)用用戶登錄,只要你織入了用戶查詢,那就調(diào)用用戶查詢。而且不單單是只掉某一個(gè)方法,它可以挨著排的調(diào)用。

這只是其中一個(gè)強(qiáng)大的用處,還有像日志記錄、性能分析、事務(wù)處理等更多都可以利用到AOP的地方。


Think of AOP as complementing, not competing with, OOP. AOP can supplement OOP where it is weak. (AOP和OOP沒有競爭關(guān)系,相反,AOP能夠很好的補(bǔ)充OOP的不足)

AOP基本術(shù)語

 

l  Aspect(切面):就是你想給程序織入的代碼片段、如權(quán)限處理、日志記錄等。

l  Weaving(編織):就是給指定的程序加上額外的業(yè)務(wù)邏輯的過程,比如將權(quán)限驗(yàn)證插入到用戶登錄的過程。

l  Advice(通知):表示是在程序的哪里織入切面,比如前面織入,還是后面織入,或者是拋出異常的時(shí)候織入。

l  Joinpoint(連接點(diǎn)):表示給那個(gè)程序織入切面,也就是被代理的目標(biāo)對(duì)象的目標(biāo)方法。

l  Pointcut(切入點(diǎn)):表示給哪些程序織入切面,是連接點(diǎn)的集合,比如是用戶登錄和用戶查詢等都需要被織入。

 

為了方便用戶使用AOP,需要定義幾種通知類型。

l  Before:前置通知,在業(yè)務(wù)邏輯之前通知

l  After:后置通知,在業(yè)務(wù)邏輯正常完結(jié)之后通知

l  End:結(jié)束通知,不管業(yè)務(wù)邏輯是否正常完結(jié),都會(huì)在后面執(zhí)行的通知

l  Error:錯(cuò)誤通知,在業(yè)務(wù)邏輯拋出異常的時(shí)候通知

 

AOP執(zhí)行流程

 

下圖展示了AOP核心調(diào)用過程,通過調(diào)用AOP代理類,開始一個(gè)一個(gè)調(diào)用后面的(前置)通知/攔截器鏈條,完成之后在調(diào)用目標(biāo)方法,最后回來的時(shí)候接著調(diào)用(后置、結(jié)束)通知/攔截器鏈條。


如此一來就成功的完成了在AOP中給某個(gè)程序(目標(biāo)方法)之前加上一段業(yè)務(wù)邏輯,之后加上一段業(yè)務(wù)邏輯的流程,并且殺傷力極大,可以將目標(biāo)方法的范圍進(jìn)行任意控制。

 

動(dòng)手實(shí)現(xiàn)一個(gè)AOP

 

前戲那么長,高潮不會(huì)短!這次寫的AOP參考了很多Spring的代碼,吸收了大師補(bǔ)充的養(yǎng)分。

利用測試驅(qū)動(dòng)開發(fā)的原則,我們先來考慮考慮我們會(huì)怎么用(寫好測試代碼),然后想想API怎么設(shè)計(jì)(將接口寫好),最后考慮實(shí)現(xiàn)的問題。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
        @Test
    public void testTranscation() {
        // 創(chuàng)建動(dòng)態(tài)代理工廠,這是調(diào)用動(dòng)態(tài)代理實(shí)現(xiàn)aop的初始點(diǎn)
        AopProxyFactory proxy = new AopProxyFactory();
        // 創(chuàng)建目標(biāo)對(duì)象
        proxy.setTarget(new AopDemo());
        // 設(shè)置各個(gè)advice,以便在調(diào)用目標(biāo)對(duì)象的指定方法時(shí)可以出現(xiàn)這些advice
        proxy.addAdvice(new TransactionAspect());
        // 獲取代理對(duì)象
        IAopDemo p = (IAopDemo) proxy.getProxy();
        // 通過代理對(duì)象調(diào)用目標(biāo)對(duì)象的指定方法
        p.doSomething();
    }


參考springAPI設(shè)計(jì),我們給出了上面這段測試代碼。這樣就能給AOP代理工廠配置目標(biāo)對(duì)象,和各種各樣的通知,目前只有事務(wù)處理的通知。然后通過代理工廠獲取目標(biāo)對(duì)象的代理對(duì)象,并完成類型轉(zhuǎn)換的過程。最后調(diào)用指定方法,完成在這個(gè)方法的周圍實(shí)現(xiàn)事務(wù)處理過程。

設(shè)置目標(biāo)對(duì)象的方法無需贅言,就是看中了那個(gè)對(duì)象,設(shè)置進(jìn)去,這樣就能搞個(gè)代理幫他做了。。。

添加通知的實(shí)現(xiàn),我想設(shè)計(jì)的好用一點(diǎn)。什么叫好用呢?也就是給你一些接口,Before、After、Error、End等,只要你定義的aspect類(切面)實(shí)現(xiàn)了任意一個(gè)接口,就能保證按照這個(gè)接口名字所顯示的那樣執(zhí)行。比如我實(shí)現(xiàn)了Before接口和他的抽象方法,并在里面加了個(gè)“記錄日志”的功能,這樣,我以后就能在我的目標(biāo)方法執(zhí)行之前完成一次記錄日志的過程了。這里我們使用的是事務(wù)處理的aspect切面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
@Match(methodMatch = "org.*.doSomething")
public class TransactionAspect implements Before, End, Error {
    @Override
    public void error(Method method, Object[] args, Exception e) {
        System.out.println("回滾事務(wù)");
    }
    @Override
    public void before(Method method, Object[] args) {
        System.out.println("開啟事務(wù)");
    }
    @Override
    public void end(Method method, Object[] args, Object retVal) {
        System.out.println("關(guān)閉事務(wù)");
    }
}


在代理工廠中,我們使用Object  target存儲(chǔ)這個(gè)目標(biāo)對(duì)象,并使用集合來記錄所有該類涉及到的通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
        private Object target;
    private List<Advice> adviceList = new ArrayList<Advice>();
    /**
     *
     * @Title: setTarget
     * @Description: 設(shè)置目標(biāo)
     * @param @param target 設(shè)定文件
     * @return void 返回類型
     * @throws
     */
    public void setTarget(Object target) {
        this.target = target;
    }
    public void addAdvice(Advice advice) {
        if (advice == null)
            throw new NullPointerException("添加的通知不能為空");
        adviceList.add(advice);
    }

而在完成配置代理工廠后,需要通過這個(gè)代理工廠來獲取代理對(duì)象。在獲取代理對(duì)象之前,我們把之前完成的配置(目標(biāo)方法、通知集合)都初始化到AdvisedSupport對(duì)象中,將這個(gè)對(duì)象整體傳給后面的代理實(shí)現(xiàn)(jdk、cglib)完成代理類的初始化,以及通知和目標(biāo)方法的調(diào)用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
        public Object getProxy() {
        if (target == null)
            throw new NullPointerException("目標(biāo)對(duì)象不能為空");
         
        AdvisedSupport config=new AdvisedSupport(target, adviceList);
        AopProxy proxy = null;
        // 若該目標(biāo)對(duì)象實(shí)現(xiàn)了接口,就優(yōu)先選擇jdk動(dòng)態(tài)代理;如果沒有實(shí)現(xiàn)任何接口,就只能采用cglib動(dòng)態(tài)代理;
        if (config.hasInterfaces()) {
            logger.info("采用jdk動(dòng)態(tài)代理");
            proxy = new JDKAopProxy(config);
        } else {
            logger.info("采用cglib動(dòng)態(tài)代理");
            proxy = new CglibAopProxy(config);
        }
        return proxy.getProxy();
    }


這里我們會(huì)根據(jù)不同的情況來判斷他是選擇jdk動(dòng)態(tài)代理還是選擇cglib動(dòng)態(tài)代理。

這里以jdk動(dòng)態(tài)代理為例,cglib留給讀者自行分析:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class JDKAopProxy implements AopProxy, InvocationHandler {
    private AdvisedSupport config;
    public JDKAopProxy(AdvisedSupport config) {
        this.config = config;
    }
    @Override
    public Object getProxy() {
        return Proxy.newProxyInstance(config.getClassLoader(),
                config.getInterfaces(), this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        return new ReflectiveMethodInvocation(config.getInterceptors(),config.getMatchers(), args,
                method, config.getTarget()).proceed();
    }
}



這里我們首先在getProxy初始化了代理類,然后當(dāng)代理類的方法被調(diào)用時(shí),會(huì)完成目標(biāo)方法調(diào)用,這個(gè)步驟都是ReflectiveMethodInvocation對(duì)象完成的。這個(gè)類實(shí)現(xiàn)了MethodInvocation,目的是為了完成之后的回調(diào)過程,這個(gè)后面可以看到。在這個(gè)ReflectiveMethodInvocation類里面,我們存儲(chǔ)了足夠多的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
        /**
     * 通知advice
     */
    private List<MethodInterceptor> chain;
    /**
     * 每個(gè)advice所配置的匹配信息
     */
    private List<Matcher> matches;
    /**
     * 執(zhí)行目標(biāo)方法需要的參數(shù)
     */
    private Object[] arguments;
    /**
     * 目標(biāo)方法
     */
    private Method method;
    /**
     * 目標(biāo)對(duì)象
     */
    private Object target;
     
    /**
     * 記錄當(dāng)前advice鏈條(chain)所需要執(zhí)行的方法的索引
     */
    private int index;



目的很簡單:就是將多個(gè)通知鏈條和目標(biāo)對(duì)象的方法本身的調(diào)用整合起來,形成邏輯完善的鏈條——前置通知在目標(biāo)方法前面排著隊(duì)完成,如果目標(biāo)方法拋出了異常就執(zhí)行錯(cuò)誤通知,一旦正常執(zhí)行完成目標(biāo)方法就執(zhí)行后置通知,而結(jié)束通知時(shí)不管是是正常執(zhí)行完目標(biāo)方法還是拋出了異常,最后都會(huì)執(zhí)行的一個(gè)通知。

下面來看看這個(gè)類中處理一連串方法調(diào)用的核心方法proceed()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Override
    public Object proceed() throws Throwable {
        //當(dāng)鏈條走完的時(shí)候調(diào)用目標(biāo)方法
        if (index == chain.size())
            return invokeJoinpoint();
        Matcher matcher = matches.get(index);
        // 查看是否匹配,
        if (matcher.matches(this.method, this.target.getClass())) {
            return chain.get(index++).invoke(this);
        } else {
            index++;
            return proceed();
        }
    }
    /**
     *
     * @Title: invokeJoinpoint
     * @Description: 調(diào)用連接點(diǎn)方法
     * @param @return
     * @param @throws Throwable 設(shè)定文件
     * @return Object 返回類型
     * @throws
     */
    protected Object invokeJoinpoint() throws Throwable {
        return method.invoke(target, arguments);
    }



很簡單,就是當(dāng)鏈條走完的時(shí)候,調(diào)用目標(biāo)方法。否則就繼續(xù)指向鏈條上的方法。這里有一個(gè)檢測是否匹配的過程,也就是我給我的切面類,也就是處理事務(wù)的切面TransactionAspect配置了一個(gè)注解Match,這個(gè)注解表示當(dāng)目標(biāo)類是org包下的某個(gè)類時(shí),我就會(huì)對(duì)他的doSomething方法完成攔截,在這個(gè)方法周圍加上事務(wù)處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
@Match(methodMatch = "org.*.doSomething")
public class TransactionAspect implements Before, End, Error {
    @Override
    public void error(Method method, Object[] args, Exception e) {
        System.out.println("回滾事務(wù)");
    }
    @Override
    public void before(Method method, Object[] args) {
        System.out.println("開啟事務(wù)");
    }
    @Override
    public void end(Method method, Object[] args, Object retVal) {
        System.out.println("關(guān)閉事務(wù)");
    }
}



具體判斷是否匹配,我采用的是正則表達(dá)式,也就是當(dāng)目標(biāo)類的某個(gè)方法被調(diào)用時(shí),一旦檢測到他符合methodMatch配置的正則表達(dá)式,就給該方法前后加上指定的邏輯。如果發(fā)現(xiàn)不匹配,這繼續(xù)尋找鏈條上下一個(gè)通知,直到走完整個(gè)通知鏈條。

這里大家肯定會(huì)有一個(gè)問題:在鏈條上獲取到一個(gè)通知,執(zhí)行該通知的時(shí)候,如何確保前置通知是再前面執(zhí)行,后置通知是再后面執(zhí)行呢?并且在完成調(diào)用之后確保執(zhí)行后面的通知調(diào)用流程?

其實(shí),spring在這里用到了一個(gè)很巧妙的編程技巧——通過多態(tài)原理和回調(diào)函數(shù)來處理。

1
chain.get(index++).invoke(this);



這里獲取到了第index個(gè)通知,拿到的是個(gè)接口類型,但是實(shí)現(xiàn)類出賣了他的本質(zhì),表示它到底是前置還是后置或者是其他等。當(dāng)執(zhí)行invoke時(shí),將該MethodInvocation的實(shí)現(xiàn)類ReflectiveMethodInvocation的對(duì)象的引用傳遞進(jìn)去。如此調(diào)用的方法,其實(shí)是這樣的:

可以發(fā)現(xiàn),當(dāng)實(shí)現(xiàn)類是后置通知的時(shí)候,我會(huì)選擇AfterInterceptor來執(zhí)行,當(dāng)時(shí)前置通知的時(shí)候,會(huì)選擇BeforeInterceptor來執(zhí)行。也就是,碰到合適的通知,就采用合適的攔截器處理。

以前置通知的方法攔截器為例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BeforeInterceptor implements MethodInterceptor {
    private Before advice;
    public BeforeInterceptor(Before advice) {
        this.advice =advice;
    }
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
        advice.before(mi.getMethod(), mi.getArguments());
        return mi.proceed();
    }
}



看到這里是不是有種似曾相識(shí)的感覺,這不就是開始那個(gè)權(quán)限驗(yàn)證的翻版么?對(duì)啊,it is。這里實(shí)現(xiàn)的很明確,就是在目標(biāo)方法調(diào)用之前執(zhí)行before中的業(yè)務(wù)邏輯,接著進(jìn)行mi.proceed()又回調(diào)到了我們MethodInvocation的實(shí)現(xiàn)類ReflectiveMethodInvocation中的proceed方法中了。

這樣,也就保證了鏈條的次序執(zhí)行。

來看看我們測試用例的輸出結(jié)果吧:

1
2
3
4
5
開啟事務(wù)
和哈哈哈哈哈...
關(guān)閉事務(wù)



目前還沒能把ioc和aop整合起來使用,還有像ioc在web mvc框架中如何使用都還沒提到,不過這些會(huì)在我們以后的博客中不斷出現(xiàn)。

源代碼

 最后,放出源代碼:https://github.com/mjaow/my_spring

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    成年女人午夜在线视频| 日韩夫妻午夜性生活视频| 亚洲精品一区二区三区免| 午夜精品在线视频一区| 韩日黄片在线免费观看| 国产又黄又猛又粗又爽的片| 视频一区二区 国产精品| 国产日韩欧美在线播放| 日韩精品成区中文字幕| 九九热在线免费在线观看| 亚洲av又爽又色又色| 99一级特黄色性生活片| 日韩一区二区三区免费av| 1024你懂的在线视频| 色综合伊人天天综合网中文| 欧美激情区一区二区三区| 日韩三级黄色大片免费观看 | 亚洲欧洲一区二区中文字幕| 国产一区二区精品高清免费| 日本一区二区三区久久娇喘| 婷婷开心五月亚洲综合| 亚洲天堂男人在线观看| 亚洲精品成人福利在线| 国产精品成人一区二区在线| 色婷婷视频国产一区视频| 日韩人妻精品免费一区二区三区| 青青操日老女人的穴穴| 国产麻豆一线二线三线| 91欧美视频在线观看免费| 六月丁香六月综合缴情| 精品al亚洲麻豆一区| 中文字幕av诱惑一区二区| 中国美女偷拍福利视频| 中文字幕久久精品亚洲乱码| 成人午夜视频在线播放| 久久青青草原中文字幕| 在线免费观看黄色美女| 99福利一区二区视频| 亚洲国产中文字幕在线观看| 欧美大胆美女a级视频| 小黄片大全欧美一区二区|