長話短說Spring(2)之AOP面向切面編程

簡書 Wwwwei
轉(zhuǎn)載請注明原創(chuàng)出處宿刮,謝謝毅哗!

前言


上節(jié)回顧 長話短說Spring(1)之IoC控制反轉(zhuǎn)

??上篇文章中貌嫡,我們介紹了有關(guān)Spring IoC控制反轉(zhuǎn)的概念以及實(shí)現(xiàn),接下來我們將說一說Spring的另一重大特點(diǎn)AOP面向切面編程哮肚。同樣停局,還是老套路很钓,我們將從是什么怎么做董栽、為什么三個主要方向入手码倦,說清楚AOP到底是個什么東西Spring 怎么做實(shí)現(xiàn)了AOP以及為什么要使用AOP這幾個問題锭碳。

什么是AOP袁稽?


官方解釋

??我們先來看一下比較官方的解釋。
??AOP擒抛,Aspect Oriented Programming的縮寫推汽,意為面向切面編程,通過預(yù)編譯方式和運(yùn)行期動態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)歧沪。
??依然沒看懂ㄟ( ▔, ▔ )ㄏ民泵。

生活中的AOP

??要理解AOP(面向切面編程),首先我們要明白什么叫做切面槽畔。
??舉個生活中的例子,我們都知道一個成功的老板背后一定有一個優(yōu)秀的秘書胁编,而秘書的職責(zé)就是負(fù)責(zé)調(diào)度和安排老板的日程厢钧。想象一下這樣一個場景鳞尔,今天秘書小M將事先安排好的一天日程向老板大B匯報(bào)后,老板大B表示同意早直。那么如無意外寥假,老板大B今天將和往常一樣,按照日程表一樣一樣完成霞扬,一天就過去了糕韧。但是,秘書小M突然接到通知喻圃,一個十分重要的會議臨時(shí)決定在午后召開萤彩,無奈秘書小M只能將該會議強(qiáng)行插入老板大B已經(jīng)確認(rèn)過的日程安排中,然后默默等待老板大B的白眼斧拍。

切面

????
??我們注意到秘書小M將臨時(shí)會議插入日程的動作雀扶,是不是很符合切面這個感覺呢?更形象一點(diǎn)肆汹,我們將老板大B確認(rèn)過的日程想象成一條長面包愚墓,而秘書小M突然被告知還有一片火腿(即那個討厭的臨時(shí)會議),為了老板能夠吃好昂勉,不得已只能用小刀將面包切開后硬生生將火腿塞入浪册。
??可以這樣理解,面向切面就是為了達(dá)到目的岗照,動態(tài)地將某件事切入到某件事中村象。

程序中的AOP

??回到代碼層面,關(guān)于AOP我在網(wǎng)上博文中發(fā)現(xiàn)了這樣一句話:運(yùn)行時(shí)谴返,動態(tài)地將代碼切入到類的指定方法或者指定位置煞肾。是不是豁然開朗了呢?
??這里還想強(qiáng)調(diào)一下運(yùn)行時(shí)動態(tài)兩個點(diǎn)嗓袱,運(yùn)行時(shí)不難理解籍救,如果在程序非運(yùn)行時(shí)我們只要將代碼寫入指定位置再運(yùn)行就好了,而程序運(yùn)行時(shí)渠抹,代碼就無法進(jìn)行更改了蝙昙,這也體現(xiàn)了不修改源代碼的意義;動態(tài)這個概念我們可以這樣理解梧却,切入的過程并非是事先完成好的奇颠,而是在程序運(yùn)行過程中觸發(fā)了某個時(shí)機(jī)而進(jìn)行的。
??此處介紹幾個概念放航,便于讀者理解:
??通知(advice):切入到類指定方法或者指定位置的代碼片段烈拒,即需要增加的功能代碼,也就是上述的那片火腿(臨時(shí)會議)。
??連接點(diǎn)(join point):程序運(yùn)行過程中能夠進(jìn)行插入切面操作的時(shí)間點(diǎn)荆几。例如方法調(diào)用吓妆、異常拋出或字段修改等,可以理解為上述的長面包(老板的整個日程安排)吨铸。
??切入點(diǎn)(pointcut):描述一個通知將被切入的一系列連接點(diǎn)的集合行拢,即代碼片段具體切入到哪些類、哪些方法诞吱,也就是上述面包切口處(特指午后的日程)舟奠。所以說,切入點(diǎn)規(guī)定了哪些連接點(diǎn)可以執(zhí)行哪些通知房维。
??切面(aspect):AOP中的切面等同于OOP中的類(class)沼瘫,由通知(advice)和切入點(diǎn)(pointcut)組成,其中通知(advice)和切入點(diǎn)(pointcut)既可以是1對1的關(guān)系握巢,也可以是1對多的關(guān)系晕鹊。概括的說就是描述了何時(shí)何地干何事的基本單元,其中通知(advice)說明了切面干何事暴浦,而切入點(diǎn)則說明了切面何時(shí)何地切入溅话。
??關(guān)于幾者的關(guān)系,我們可以這樣理解歌焦,通知是在連接點(diǎn)上執(zhí)行的飞几,但是我們不希望通知應(yīng)用到所有的連接點(diǎn),所以引入了切入點(diǎn)來匹配特定的連接點(diǎn)独撇,指名我們所希望通知應(yīng)用的連接點(diǎn)屑墨。
??因此,所謂的AOP(面向切面編程)就是在程序運(yùn)行過程中的某個時(shí)機(jī)將代碼片段插入到某些類的指定方法和指定位置纷铣;換句話說卵史,秘書接到臨時(shí)會議通知時(shí),將臨時(shí)會議插入到老板的日程安排中去搜立。

為什么要使用AOP以躯?


??程序的最終目的就是實(shí)現(xiàn)業(yè)務(wù),但是我們在進(jìn)行編程的過程中經(jīng)常會發(fā)現(xiàn)除了所謂的業(yè)務(wù)代碼啄踊,還存在數(shù)量相當(dāng)?shù)墓泊a忧设,類似日志、安全驗(yàn)證颠通、事物址晕、異常處理等問題。這部分代碼重要但是與我們編寫程序要實(shí)現(xiàn)的功能沒有關(guān)系顿锰,具有功能相似谨垃、重用性高启搂、使用場景分散等特點(diǎn)。我們姑且稱它們?yōu)?strong>共性問題刘陶。
??對大多數(shù)程序而言狐血,代碼都是以縱向結(jié)構(gòu)將各個業(yè)務(wù)模塊串聯(lián)從而完成功能的。我們提到的共性問題本身不屬于業(yè)務(wù)范圍易核,但是又散落在各個業(yè)務(wù)模塊間,同實(shí)現(xiàn)主功能的代碼相互雜糅在一起浪默,即如下圖所示:

程序邏輯圖

??試想一下牡直,如果將共性問題部分的代碼融入業(yè)務(wù)代碼中,一旦涉及到對某個共性問題部分的代碼進(jìn)行更改的時(shí)候纳决,例如日志部分發(fā)生需求變更碰逸,我們可能需要牽涉許許多多其他模塊代碼。這在小規(guī)模程序中也許是可以接受的阔加,可能只修改1饵史、2處;但是如果牽涉的地方數(shù)量過多胜榔,特別是應(yīng)用在中大型規(guī)模程序中胳喷,我們甚至?xí)榱诵⌒〉囊粋€功能,修改上千夭织、上萬處吭露。這樣的方式是十分糟糕的,不僅費(fèi)時(shí)費(fèi)力尊惰,可能還會引起一些不必要的麻煩(回歸錯誤讲竿、結(jié)構(gòu)混亂等等)。
??AOP的就是為了解決這類共性問題弄屡,將散落在程序中的公共部分提取出來题禀,以切面的形式切入業(yè)務(wù)邏輯中,使程序員只專注于業(yè)務(wù)的開發(fā)膀捷,從事務(wù)提交等與業(yè)務(wù)無關(guān)的問題中解脫出來迈嘹。
AOP程序邏輯圖

AOP的好處

??解耦:AOP將程序中的共性問題進(jìn)行了剝離,毫無疑問地降低了各個業(yè)務(wù)模塊和共性問題之間的耦合担孔。
??重用性:共性問題散落于業(yè)務(wù)邏輯的各處江锨,十分難維護(hù),使用AOP進(jìn)行提取后糕篇,能夠?qū)⑾嗨乒δ艿墓残詥栴}收斂啄育,減少重復(fù)代碼,提高了代碼的重用性拌消。
??拓展性:對于一個程序而言挑豌,迭代的重心一定在于業(yè)務(wù)和功能上安券。AOP使得每當(dāng)發(fā)生變更時(shí),可以只關(guān)注業(yè)務(wù)邏輯相關(guān)的代碼氓英,而減少共性問題上帶來的變化侯勉,大大降低了程序未來拓展的成本。

Spring如何實(shí)現(xiàn)AOP铝阐?


什么是Spring AOP址貌?

??通過上述介紹,我們了解到AOP的重心在于定義基本結(jié)構(gòu)完成切面切入兩部分徘键,解決了它們练对,我們就能方便快捷的使用AOP思想進(jìn)行編程了。
??Spring AOP就是負(fù)責(zé)完成AOP相關(guān)工作的框架吹害,它將切面所定義的橫切邏輯切入到切面所指定的連接點(diǎn)中螟凭。框架的目的就是將復(fù)雜的事情變得簡單易用它呀,所以其主要工作分成了如下兩點(diǎn):
??1.提供相應(yīng)的數(shù)據(jù)結(jié)構(gòu)來定義AOP所需基本結(jié)構(gòu)螺男,例如通知、連接點(diǎn)纵穿、切入點(diǎn)下隧、切面等。
??2.封裝切面切入的相關(guān)工作政恍,提供相應(yīng)接口給用戶汪拥。封裝的內(nèi)容主要包括如何通過切面(切入點(diǎn)和通知)定位到特定的連接點(diǎn);如何將切面中的功能代碼植入到特定的連接點(diǎn)中等等篙耗;提供相應(yīng)接口主要包括配置文件迫筑、注解等用戶能夠使用的工具。
??這里想提一下宗弯,在 Spring AOP 中脯燃,連接點(diǎn)(join point)總是方法的執(zhí)行點(diǎn), 即只有方法連接點(diǎn)蒙保。所以我們可以認(rèn)為辕棚,在 Spring 中所有的方法都可以是連接點(diǎn)。

怎么使用Spring AOP邓厕?

??由于Spring對AOP的封裝逝嚎,使得我們可以十分方便的使用,我們只需要定義切面详恼,即定義Advice通知和Pointcut切入點(diǎn)补君。

(1)Spring AOP中Pointcut切入點(diǎn)

??使用@Pointcut("execution()")注解定義切入點(diǎn),其中execution的格式如下所示:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) 

??其中昧互,除ret-type-pattern和name-pattern之外挽铁,其他都是可選的伟桅,各部分具體含義如下:
??1.modifiers-pattern:方法的操作權(quán)限
??2.ret-type-pattern:返回值
??3.declaring-type-pattern:方法所在的包
??4.name-pattern:方法名
??5.parm-pattern:參數(shù)名
??6.throws-pattern:異常
??為了更加理解,舉一個簡單的execution示例叽掘,如下所示:

execution示例
(2)Spring AOP中Advice通知
  • before advice:前置通知楣铁,由@Before注解定義,表示在 join point 前被執(zhí)行的advice更扁。(雖然before advice是在 join point 前被執(zhí)行盖腕,但是它并不能夠阻止 join point 的執(zhí)行, 除非發(fā)生了異常浓镜,即在before advice代碼中赊堪, 不能人為地決定是否繼續(xù)執(zhí)行join point中的代碼)
  • after return advice:后置通知,由@AfterReturning注解定義竖哩,表示在一個 join point 正常返回后執(zhí)行的advice。
  • after throwing advice:異常通知脊僚,由@AfterThrowing注解定義相叁,表示當(dāng)一個 join point 拋出異常后執(zhí)行的advice。
  • after(final) advice:最終通知辽幌,由@After注解定義增淹,表示無論一個join point是正常退出還是發(fā)生了異常,都會被執(zhí)行的advice乌企。
  • around advice:環(huán)繞通知虑润,由@Around注解定義,表示在join point 前和joint point退出后都執(zhí)行的 advice加酵,是最常用的advice拳喻。
(3)舉個例子

??首先,我們創(chuàng)建一個簡單的UserService,作為示例的業(yè)務(wù)模塊猪腕,代碼如下:

package com.demo.aop;

import org.springframework.stereotype.Service;

/**
 * Created by wwwwei on 17/8/14.
 */
@Service
public class UserService {
    //創(chuàng)建用戶
    public void createUser(String userName) {
        System.out.println("創(chuàng)建用戶 " + userName);
    }

    //刪除用戶
    public void deleteUser(String userName) {
        System.out.println("刪除用戶 " + userName);
    }

    //更新用戶
    public void updateUser(String userName) {
        System.out.println("更新用戶 " + userName);
        throw new RuntimeException("更新用戶拋出異常");
    }
}

??其次冗澈,定義切面數(shù)據(jù)結(jié)構(gòu),即通知(需要增加的功能代碼片段)和切入點(diǎn)(切入的時(shí)間點(diǎn))陋葡,代碼如下:

package com.demo.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * Created by wwwwei on 17/8/11.
 */
@Component
@Aspect
public class UserAspect {
    //定義切入點(diǎn)
    @Pointcut("execution(* com.demo.aop.*Service*.*(..))")
    public void pointCut() {
    }

    //前置通知 @Before(value="execution(public * *(..))")
    @Before("pointCut()")
    public void mybefore() {
        System.out.println("前置通知");
    }

    //后置通知 @AfterReturning(value="execution(public * *(..))")
    @AfterReturning(pointcut = "pointCut()")
    public void myafterReturning() {
        System.out.println("后置通知");
    }

    //異常通知 @AfterThrowing(value="execution(public * *(..))")
    @AfterThrowing(pointcut = "pointCut()", throwing = "error")
    public void myafterThrowing() {
        System.out.println("異常通知");
    }

    //環(huán)繞通知 @Around(value="execution(public * *(..))")
    @Around("pointCut()")
    public void myAround(ProceedingJoinPoint jp) {
        System.out.println("環(huán)繞前通知");
        try {
            jp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("環(huán)繞后通知");

    }

    //最終通知 @After(value="execution(public * *(..))")
    @After("pointCut()")
    public void myafterLogger() {
        System.out.println("最終通知");
    }
}

??最后亚亲,編寫測試類測試,代碼如下:

package com.demo.aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by wwwwei on 17/8/14.
 */
public class UserTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        UserService userService = context.getBean(UserService.class);
        userService.createUser("測試用戶");
    }
}

??運(yùn)行結(jié)果如下腐缤,可以發(fā)現(xiàn)在業(yè)務(wù)模塊中成功切入了我們定義的代碼片段捌归。


運(yùn)行結(jié)果
AOP的實(shí)現(xiàn)

??AOP的實(shí)現(xiàn)是基于代理機(jī)制的,根據(jù)不同實(shí)現(xiàn)方式主要分為兩類:
??1.靜態(tài)代理岭粤,AOP框架會在編譯階段生成AOP代理類惜索,即在編譯器和類裝載期實(shí)現(xiàn)切入的工作,但是這種方式需要特殊的Java編譯器和類裝載器绍在。AspectJ框架就是采用這種方式實(shí)現(xiàn)AOP门扇。
??2.動態(tài)代理雹有,AOP框架不會去修改字節(jié)碼,而是在內(nèi)存中臨時(shí)為方法生成一個AOP對象臼寄,這個AOP對象包含了目標(biāo)對象的全部方法霸奕,并且在特定的連接點(diǎn)(切入點(diǎn))做了添加通知(advice)處理,并回調(diào)原對象的方法吉拳。
??與AspectJ的靜態(tài)代理不同质帅,Spring AOP使用動態(tài)代理,通過JDK Proxy和CGLIB Proxy兩種方法實(shí)現(xiàn)代理留攒。兩種方式的選擇與目標(biāo)對象有關(guān):

  • 如果目標(biāo)對象沒有實(shí)現(xiàn)任何接口煤惩,那么Spring將使用CGLIB來實(shí)現(xiàn)代理。CGLIB是一個開源項(xiàng)目炼邀,它是一個強(qiáng)大的魄揉,高性能,高質(zhì)量的Code生成類庫拭宁,它可以在運(yùn)行期擴(kuò)展Java類與實(shí)現(xiàn)Java接口洛退。
  • 如果目標(biāo)對象實(shí)現(xiàn)了一個以上的接口,那么Spring將使用JDK Proxy來實(shí)現(xiàn)代理杰标,因?yàn)镾pring默認(rèn)使用的就是JDK Proxy兵怯,并且JDK Proxy是基于接口的。這也是Spring提倡的面向接口編程腔剂。當(dāng)然媒区,你也可以強(qiáng)制使用CGLIB來進(jìn)行代理,但是這樣可能會造成性能上的下降掸犬。
    ??感謝以下博文袜漩,寫作時(shí)作為參考借鑒。
    ??Spring AOP的實(shí)現(xiàn)原理
    ??徹底征服 Spring AOP 之 理論篇
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末湾碎,一起剝皮案震驚了整個濱河市噪服,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胜茧,老刑警劉巖粘优,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異呻顽,居然都是意外死亡雹顺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門廊遍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嬉愧,“玉大人,你說我怎么就攤上這事喉前∶缓ǎ” “怎么了王财?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長裕便。 經(jīng)常有香客問我绒净,道長,這世上最難降的妖魔是什么偿衰? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任挂疆,我火速辦了婚禮,結(jié)果婚禮上下翎,老公的妹妹穿的比我還像新娘缤言。我一直安慰自己,他們只是感情好视事,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布胆萧。 她就那樣靜靜地躺著,像睡著了一般俐东。 火紅的嫁衣襯著肌膚如雪鸳碧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天犬性,我揣著相機(jī)與錄音,去河邊找鬼腾仅。 笑死乒裆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的推励。 我是一名探鬼主播鹤耍,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼验辞!你這毒婦竟也來了稿黄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤跌造,失蹤者是張志新(化名)和其女友劉穎杆怕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壳贪,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡陵珍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了违施。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片互纯。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖磕蒲,靈堂內(nèi)的尸體忽然破棺而出留潦,到底是詐尸還是另有隱情只盹,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布兔院,位于F島的核電站殖卑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秆乳。R本人自食惡果不足惜懦鼠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望屹堰。 院中可真熱鬧肛冶,春花似錦、人聲如沸扯键。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荣刑。三九已至馅笙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間厉亏,已是汗流浹背魔吐。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涡戳,地道東北人凰兑。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像恬试,于是被迫代替她去往敵國和親窝趣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理训柴,服務(wù)發(fā)現(xiàn)哑舒,斷路器,智...
    卡卡羅2017閱讀 134,601評論 18 139
  • 本章內(nèi)容: 面向切面編程的基本原理 通過POJO創(chuàng)建切面 使用@AspectJ注解 為AspectJ切面注入依賴 ...
    謝隨安閱讀 3,127評論 0 9
  • AOP幻馁,也就是面向方面編程或者說面向面編程洗鸵,是一種很重要的思想。在企業(yè)級系統(tǒng)中經(jīng)常需要打印日志仗嗦、事務(wù)管理這樣針對某...
    樂百川閱讀 896評論 0 8
  • TFBOYS 中國內(nèi)地少年偶像組合预麸,出道時(shí)平均年齡僅有13歲,是中國年齡最小的偶像團(tuán)體儒将,兩年內(nèi)又以極快的速度在傳統(tǒng)...
    十年易7閱讀 330評論 1 0
  • 記得2005吏祸、2006年的時(shí)候,博客正盛行。恰好那時(shí)候的工作是在一家音像發(fā)行公司贡翘,除去購買一些紀(jì)錄片版權(quán)后需要發(fā)片...
    愛熙世界閱讀 1,349評論 3 4