一盯另、動態(tài)代理(Dynamic agent):
?1癞揉、特點:
字節(jié)碼隨用隨創(chuàng)建久妆,隨用隨加載榆苞。
?2成箫、作用:
在不修改源碼的基礎(chǔ)上對方法增強铆铆。
?3种蝶、分類:
??<1>员萍、基于接口的動態(tài)代理:
??(1)促王、涉及到的類:
Proxy
??(2)掩完、提供者:
JDK官方
??(3)、創(chuàng)建方法:
使用Proxy類中的newProxyInstance方法硼砰。
??(4)且蓬、對代理對象的要求:
被代理對象至少實現(xiàn)一個接口,否則不可用题翰。
??(5)恶阴、newProxyInstance方法的參數(shù):
???①诈胜、ClassLoader:
類加載器
用于加載代理對象的字節(jié)碼,和被代理對象使用相同的類加載器冯事。
固定寫法
???②焦匈、Class[]:
字節(jié)碼數(shù)組
用于讓代理對象和被代理對象擁有相同方法。
固定寫法
???③昵仅、InvocationHandler:
用于提供增強的代碼
用于讓我們代理缓熟,一般是一個該接口的實現(xiàn)類。通常是匿名內(nèi)部類摔笤,但不是必須的够滑。
作用:
執(zhí)行被代理對象的任何方法都會經(jīng)過該方法。
函數(shù):
proxy:代理對象的引用吕世。
method:當前執(zhí)行的方法彰触。
args:當前執(zhí)行方法所需參數(shù)。
返回值:和被代理對象擁有相同的返回值命辖。
??<2>况毅、基于子類的動態(tài)代理:
??(1)、涉及到的類:
Enhancer
??(2)尔艇、提供者:
第三方cglib庫
??(3)尔许、創(chuàng)建方法:
使用Enhancer中的create方法。
??(4)终娃、對代理對象的要求:
被代理類不能是最終類味廊。
??(5)、create方法的參數(shù):
???①尝抖、class:
字節(jié)碼
指定被代理類的字節(jié)碼毡们,要代理誰,就用誰.class
???②昧辽、callback:
用于提供增強的代碼
一般使用該接口的一個子接口實現(xiàn)類:MethodInterceptor
函數(shù):
proxy:代理對象的引用衙熔。
method:當前執(zhí)行的方法。
args:當前執(zhí)行方法所需參數(shù)搅荞。
methodProxy:當前執(zhí)行方法的代理對象红氯。
返回值:和被代理對象擁有相同的返回值。
二咕痛、面向切面編程(AOP):
1痢甘、作用:
在程序運行期間,不修改源碼茉贡,對已有方法進行增強塞栅。
2、優(yōu)勢:
?<1>腔丧、減少重復(fù)代碼放椰。
?<2>作烟、提高開發(fā)效率。
?<3>砾医、維護方便拿撩。
3、相關(guān)術(shù)語:
?<1>如蚜、Joinpoint(連接點):
指被攔截到的點压恒。在spring中,這些點指的是方法错邦。
因為spring只支持方法類型的連接點探赫。也就是業(yè)務(wù)層的每一個方法。
?<2>兴猩、Pointcut(切入點):
指的是要對哪些Joinpoint進行攔截的定義期吓。也就是業(yè)務(wù)層中被增強的方法早歇。
?<3>倾芝、Advice(通知):
定義:
指的是攔截到Joinpoint之后要做的事情。
分類:
前置通知箭跳、后置通知晨另、異常通知、最終通知谱姓、環(huán)繞通知
分類原理:
以調(diào)用業(yè)務(wù)層方法(被try……catch包裹起來)為界限借尿,在其上面就是前置通知;下面就是后置通知屉来;
在catch內(nèi)就是異常通知路翻;在finally內(nèi)就是最終通知;整個invoke方法在執(zhí)行時就是環(huán)繞通知茄靠。
?<4>茂契、Introduction(引介):
在不修改類代碼的前提下,Introduction可以在運行期為類動態(tài)的添加一些方法或Field慨绳。
?<5>掉冶、Target(目標對象):
代理的目標對象。
?<6>脐雪、Weaving(織入):
指把增強應(yīng)用到目標對象來創(chuàng)建新的代理對象的過程厌小。
spring采用的是動態(tài)代理織入,而AspectJ采用的是編譯器織入和類加載期織入战秋。
?<7>璧亚、Proxy(代理):
一個類被AOP織入增強后,就產(chǎn)生一個新的結(jié)果代理類脂信。
?<8>癣蟋、Aspect(切面):
切入點和通知(引介)的結(jié)合拐袜。
4、spring中AOP的配置:
<1>梢薪、基于XML的配置:
?(1)蹬铺、把通知Bean交給spring管理
?(2)、使用aop:config標簽表明開始AOP的配置
?(3)秉撇、使用aop:aspect標簽表明配置切面
id屬性:給切面提供一個唯一標識
ref屬性:指定通知類bean的標簽
?(4)甜攀、在aop:aspect標簽內(nèi)部使用對應(yīng)標簽來配置通知
前置通知:aop:before
method屬性:用于指定Logger類中哪個方法是前置通知。
pointcut屬性:用于指定切入點表達式琐馆,該表達式的含義指的是對業(yè)務(wù)層哪些方法增強规阀。
后置通知:aop:after-returning
method屬性:用于指定Logger類中哪個方法是前置通知。
pointcut屬性:用于指定切入點表達式瘦麸,該表達式的含義指的是對業(yè)務(wù)層哪些方法增強谁撼。
異常通知:aop:after-throwing
method屬性:用于指定Logger類中哪個方法是前置通知。
pointcut屬性:用于指定切入點表達式滋饲,該表達式的含義指的是對業(yè)務(wù)層哪些方法增強厉碟。
最終通知:aop:after
method屬性:用于指定Logger類中哪個方法是前置通知。
pointcut屬性:用于指定切入點表達式屠缭,該表達式的含義指的是對業(yè)務(wù)層哪些方法增強箍鼓。
切入點表達式:
關(guān)鍵字:execution(表達式)
表達式:
訪問修飾符 返回值 包名.包名...包名.類名.方法名(參數(shù)列表)
eg:
標準寫法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
訪問修飾符可以省略:
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以采用通配符*表示返回任意值
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以采用通配符*表示任意包;注意:有幾集包就需要寫幾個*呵曹,中間以.隔開款咖。
* *.*.*.*.AccountServiceImpl.saveAccount()
包名也可以使用..表示當前包及其子包
* *..AccountServiceImpl.saveAccount()
類名可以采用通配符*實現(xiàn)通配:
* *..*.saveAccount()
方法名可以采用通配符*實現(xiàn)通配:
* *..*.*()
參數(shù)列表:
可以直接寫數(shù)據(jù)類型:
基本類型直接寫名稱 int
引用類型寫包名.類名的方式 java.long.String
可以使用通配符*表示任意類型,但是必須有參數(shù)
可以使用..表示有無參數(shù)均可奄喂,有參數(shù)可以是任意類型
通配符寫法:
* *..*.*(..)
實際開發(fā)中铐殃,切入點表達式的通常寫法:
切入到業(yè)務(wù)層實現(xiàn)類下的所有方法。
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置前置通知,在切入點方法執(zhí)行之前執(zhí)行-->
<aop:before method="beforePrintLog" pointcut="execution(* *..*.*(..))"></aop:before>
<!--配置后置通知,在切入點方法執(zhí)行之后執(zhí)行-->
<aop:after-returning method="afterReturnPrintLog" pointcut="execution(* *..*.*(..))"></aop:after-returning>
<!--配置異常通知,在切入點方法執(zhí)行產(chǎn)生異常之后執(zhí)行-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(* *..*.*(..))"></aop:after-throwing>
<!--配置最終通知,無論切入點方法是否執(zhí)行跨新,它都會在其后執(zhí)行-->
<aop:after method="afterPrintLog" pointcut="execution(* *..*.*(..))"></aop:after>
</aop:aspect>
</aop:config>
<!--配置AOP-->
<aop:config>
<!--配置切入點表達式富腊,id屬性用于指定表達式的唯一標識,expression屬性用于指定表達式內(nèi)容玻蝌。
此標簽寫在aop:pointcut標簽外部時蟹肘,只能寫在<aop:pointcut>標簽上面,<aop:config標簽下面俯树;可以在所有切面使用帘腹。
-->
<aop:pointcut id="pt1" expression="execution(* *..*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置前置通知,在切入點方法執(zhí)行之前執(zhí)行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<!--配置后置通知,在切入點方法執(zhí)行之后執(zhí)行,它和異常通知兩者只會執(zhí)行一個-->
<aop:after-returning method="afterReturnPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!--配置異常通知,在切入點方法執(zhí)行產(chǎn)生異常之后執(zhí)行许饿,它和后置通知兩者只會執(zhí)行一個-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!--配置最終通知,無論切入點方法是否執(zhí)行阳欲,它都會在其后執(zhí)行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
?(5)、配置環(huán)繞通知:
環(huán)繞通知:aop:before
它是spring框架提供的一種可以在代碼中手動控制增強方法何時執(zhí)行的方式。
問題:當配置了環(huán)繞通知之后球化,切入點方法沒有執(zhí)行秽晚,而通知方法執(zhí)行了。
分析:對比動態(tài)代理中的環(huán)繞通知代碼筒愚,發(fā)現(xiàn)動態(tài)代理的環(huán)繞通知中有明確的
切入點方法調(diào)用赴蝇,而此處的代碼中沒有。
解決:Spring框架提供了一個接口:ProceedingJoinPoint巢掺。該接口中有一個
方法proceed()句伶,此方法就相當于明確調(diào)用切入點方法。該接口可以作為
環(huán)繞通知的方法參數(shù)陆淀,在程序執(zhí)行時考余,Spring框架會為我們提供該接口的實現(xiàn)類供我們使用。
<aop:config>
<!--配置切入點表達式轧苫,id屬性用于指定表達式的唯一標識楚堤,expression屬性用于指定表達式內(nèi)容。
此標簽寫在aop:pointcut標簽外部時含懊,只能寫在<aop:pointcut>標簽上面身冬,<aop:config標簽下面;可以在所有切面使用绢要。
-->
<aop:pointcut id="pt1" expression="execution(* *..*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置環(huán)繞通知-->
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
public class Logger {
/**
* 環(huán)繞通知
*/
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
//得到方法執(zhí)行所需的參數(shù)
Object[] args = pjp.getArgs();
System.out.println("Logger類中的aroundPringLog方法開始記錄日志@艄А^中 重罪!前置通知");
//明確調(diào)用業(yè)務(wù)層方法(切入點方法)
rtValue = pjp.proceed(args);
System.out.println("Logger類中的aroundPringLog方法開始記錄日志!0Ь拧剿配!后置通知");
return rtValue;
}catch (Throwable t){
System.out.println("Logger類中的aroundPringLog方法開始記錄日志!T氖呼胚!異常通知");
throw new RuntimeException(t);
}finally {
System.out.println("Logger類中的aroundPringLog方法開始記錄日志!O⒙恪蝇更!最終通知");
}
}
}
?(5)、五種通知的分類依據(jù):
<1>呼盆、基于注解的配置:
(1)年扩、相關(guān)約束:
<!--配置spring創(chuàng)建容器時要掃描的包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--配置Spring開啟注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
(2)、相關(guān)注解:
①访圃、@Aspect:
將"<aop:aspect id="logAdvice" ref="logger"></aop:aspect>"替換成
實現(xiàn)類上的注解@Aspect厨幻,表示當前類是一個切面。
②、@Pointcut:
將"<aop:pointcut id="pt1" expression="execution(* *..*.*(..))"></aop:pointcut>"
替換成方法上的注解
@Pointcut("execution(* *..*.*(..))")
private void pt1(){}
③况脆、@Before("pt1()"):
前置通知饭宾,注解在前置通知的實現(xiàn)方法上,替換
"<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>"
注解的參數(shù)指的是切入點表達式注解的方法的方法名格了。
④看铆、@AfterReturning("pt1()"):
后置通知,注解在后置通知的實現(xiàn)方法上盛末,替換
"<aop:after-returning method="afterReturnPrintLog" pointcut-ref="pt1"></aop:after-returning>"
注解的參數(shù)指的是切入點表達式注解的方法的方法名性湿。
⑤、@AfterThrowing("pt1()"):
異常通知满败,注解在異常通知的實現(xiàn)方法上肤频,替換
"<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>"
注解的參數(shù)指的是切入點表達式注解的方法的方法名。
⑥算墨、@After("pt1()"):
最終通知宵荒,注解在最終通知的實現(xiàn)方法上,替換
"<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>"
注解的參數(shù)指的是切入點表達式注解的方法的方法名净嘀。
⑦报咳、@Around("pt1()"):
環(huán)繞通知,注解在環(huán)繞通知的實現(xiàn)方法上挖藏,替換
"<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>"
注解的參數(shù)指的是切入點表達式注解的方法的方法名暑刃。
⑧、特別注意:
Spring基于注解的通知膜眠,有順序調(diào)用的問題岩臣,采用注解時,
后置通知/異常通知會在最終通知之后執(zhí)行宵膨;而采用環(huán)繞通知時架谎,就不會有順序問題。