轉(zhuǎn)載自
作者:Wwwwei
鏈接:http://www.reibang.com/p/da55782965d9
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處官帘。
什么是AOP?
我們先來看一下比較官方的解釋昧谊。
??AOP刽虹,Aspect Oriented Programming的縮寫,意為面向切面編程呢诬,通過預(yù)編譯方式和運(yùn)行期動態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)涌哲。
??關(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ī)將代碼片段插入到某些類的指定方法和指定位置。
為什么要使用AOP岩睁?
程序的最終目的就是實(shí)現(xiàn)業(yè)務(wù)钞脂,但是我們在進(jìn)行編程的過程中經(jīng)常會發(fā)現(xiàn)除了所謂的業(yè)務(wù)代碼,還存在數(shù)量相當(dāng)?shù)?strong>公共代碼捕儒,類似日志冰啃、安全驗(yàn)證、事物刘莹、異常處理等問題阎毅。這部分代碼重要但是與我們編寫程序要實(shí)現(xiàn)的功能沒有關(guān)系,具有功能相似点弯、重用性高扇调、使用場景分散等特點(diǎn)。我們姑且稱它們?yōu)楣残詥栴}抢肛。
??對大多數(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將程序中的共性問題進(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???
??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:異常
??
??(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("測試用戶");
}
}
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)行代理着绊,但是這樣可能會造成性能上的下降谐算。