1. AOP
- 概念:面向方面編程
- 實(shí)現(xiàn)AOP的語言為AOL Aspect-Oriented language
1.1 靜態(tài)AOP
- 特點(diǎn) 相應(yīng)的橫切關(guān)注點(diǎn)以Aspect形式實(shí)現(xiàn)之后,會通過特定的編譯器,將實(shí)現(xiàn)后的Aspect編譯并織入到系統(tǒng)的靜態(tài)類中。比如,Aspect會使用ajc編譯器將各個Aspect以Java字節(jié)碼的形式編譯到系統(tǒng)的各個功能模塊中,以達(dá)到融合Aspect和Class的目的
- 優(yōu)點(diǎn) 沒有任何性能受損失
- 缺點(diǎn) 靈活性不夠。如果橫切關(guān)注點(diǎn)需要改變織入到系統(tǒng)的位置,就需要重新修改Aspect定義文件,p。
1.2 動態(tài)AOP
- 特點(diǎn) AOP的織入過程在系統(tǒng)運(yùn)行開始之后進(jìn)行,而不是預(yù)先編譯到系統(tǒng)類中,甚至在系統(tǒng)運(yùn)行的時(shí)候,也可以動態(tài)更改織入邏輯。
- 優(yōu)點(diǎn) 靈活易用
- 缺點(diǎn) 可以容忍的性能損失
1.3 java平臺上AOP實(shí)現(xiàn)機(jī)制
-
動態(tài)代理
- Spring AOP默認(rèn)情況下采用這種機(jī)制實(shí)現(xiàn)AOP機(jī)能
-
動態(tài)字節(jié)碼增強(qiáng)
- 使用ASM或者CGLIB等Java工具庫,在程序運(yùn)行期間,動態(tài)構(gòu)建字節(jié)碼的class文件
-
Java代碼生成
- 如 EJB容器根據(jù)部署描述符文件提供的織入信息,會為相應(yīng)的功能模塊類根據(jù)描述符所提供的信息生成對應(yīng)的Java代碼,然后通過部署工具或者部署接口編譯Java代碼生成相應(yīng)的Java類。之后,部署到EJB容器的功能模塊類就可以正常工作了。
-
自定義類加載器
- 通過自定義類加載器的方式完成橫切邏輯到系統(tǒng)的織入,自定義類加載器通過讀取外部文件規(guī)定的織入規(guī)則和必要信息,在加載class文件期間就可以將橫切邏輯添加到系統(tǒng)模塊類的現(xiàn)有邏輯中,然后將改動后的class交給Java虛擬機(jī)運(yùn)行。
- 某些應(yīng)用服務(wù)器會控制整個的類加載體系,所以,在這樣的場景下使用可能會造成一定的問題。
-
AOL擴(kuò)展
- AOL擴(kuò)展是最強(qiáng)大、也最難掌握的一種方式,我們之前提到的Aspect就屬于這種方式
1.4 AOP成員
- Joinpoint(進(jìn)行織入操作的系統(tǒng)執(zhí)行點(diǎn))
- 常見Joinpoint類型:
- 方法調(diào)用 某個方法被調(diào)用的時(shí)候所處的程序執(zhí)行點(diǎn)(調(diào)用對象)
- 方法執(zhí)行 某個方法內(nèi)部執(zhí)行開始時(shí)點(diǎn)(被調(diào)用到的方法)
- 構(gòu)造方法調(diào)用
- 字段設(shè)置
- 字段獲取
- 異常處理執(zhí)行
- 常見Joinpoint類型:
- Pointcut(Joinpoint的表述方式)
- 將橫切邏輯織入當(dāng)前系統(tǒng)的過程中,需要參照Pointcut規(guī)定的Joinpoint信息,才可以知道應(yīng)該往系統(tǒng)的哪些Joinpoint上織入橫切邏輯
- Pointcut的表達(dá)方式
- 直接指定Joinpoint所在方法名稱
- 正則表達(dá)式
- 使用特定的Pointcut表述語言
- Pointcut運(yùn)算符
- Advice
- dvice是單一橫切關(guān)注點(diǎn)邏輯的載體,它代表將會織入到Joinpoint的橫切邏輯。如果將Aspect比t作OOP中的Class,那么Advice就相當(dāng)于Class中的Method
- 形式:
- Before Advice
- 前置通知,方法之前執(zhí)行,可以通過在BeforeAdvice中拋出異常的方式來中斷當(dāng)前程序流程
- After Advice 具體分為三種
- After returning advice
- 當(dāng)前Joinpoint處執(zhí)行流程正常完成后, After returning Advice才會執(zhí)行
- After throwing advice
- 當(dāng)前Joinpoint執(zhí)行過程中拋出異常的情況下,才會執(zhí)行
- After Advice
- 不管Joinpoint處執(zhí)行流程是正常終了還是拋出異常都會執(zhí)行,就好像Java中的finally塊一樣
- After returning advice
- Around Advice
- 環(huán)繞通知,在Joinpoint之前和之后都指定相應(yīng)的邏輯,甚至于中斷或者忽略Joinpoint處原來程序流程的執(zhí)行。
- Introduction
- 為原有的對象添加新的特性或者行為,這就好像你是一個普通公民,當(dāng)讓你穿軍裝,帶軍帽,添加了軍人類型的Introduction之后,你就擁有軍人的特性或者行為。
- Before Advice
2 spring AOP
- 描述:spring AOP 僅支持方法執(zhí)行類型的joinpoint,SpringAop,設(shè)計(jì)哲學(xué)也是簡單而強(qiáng)大。以有限的20%的AOP支持,來滿足80%的AOP需求,如果SpringAOP無法滿足你所需要的那80%之外的需求,SpringAOP對Aspecti也提供了很好的集成。
2.1 spring AOP 實(shí)現(xiàn)機(jī)制
描述: SpringAOP屬于第二代AOP,采用動態(tài)代理機(jī)制和動態(tài)字節(jié)碼生成技術(shù)實(shí)現(xiàn)。動態(tài)代理機(jī)制和字節(jié)碼生成都是在運(yùn)行期間為目標(biāo)對象生成一個代理對象,而將橫切邏輯織入到這個代理對象中,系統(tǒng)最終使用的是織入了橫切邏輯的代理對象,而不是真正的目標(biāo)對象
代理模式:: Spring AOP本質(zhì)上就是采用代理機(jī)制實(shí)現(xiàn)的代理模式參考
-
動態(tài)代理
- JDK1.3以后引入動態(tài)代理機(jī)制,我們可以為指定的接口在系統(tǒng)運(yùn)行期間動態(tài)的生成代理對象。
- InvocationHandler就是我們實(shí)現(xiàn)橫切邏輯的地方,它是橫切邏輯的載體,作用跟Advice是一樣的。
- 動態(tài)代理的不足: 動態(tài)代理機(jī)制只能對實(shí)現(xiàn)了相應(yīng)Interface的類使用如果某個類沒有實(shí)現(xiàn)任何的Interface,就無法使用動態(tài)代理機(jī)制為其生成相應(yīng)的動態(tài)代理對象
package com.mg.springjiemi.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class SubjectInvocationHadndler implements InvocationHandler {
private Object target;
public SubjectInvocationHadndler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try{
System.out.println("-----Before Advice ------");
if(method.getName().equals("request")){
System.out.println("先跟秘書交談,安全代理,虛擬代理,遠(yuǎn)程代理");
}
return method.invoke(target,args);
// System.out.println("----- After returning advice -----");
}catch (Exception e) {
System.out.println("-----After throwing advice ------");
return null;
} finally {
System.out.println("-----After advice ------");
}
}
}
package com.mg.springjiemi.proxy;
import java.lang.reflect.Proxy;
public class InvocationHandlerClient {
public static void main(String[] args) {
ISubject subject = (ISubject) Proxy.newProxyInstance(ISubject.class.getClassLoader(),new Class[]{ISubject.class},new SubjectInvocationHadndler(new SubjectImpl()));
String value = subject.request();
System.out.println(value);
}
}
控制臺輸出:
-----Before Advice ------
先跟秘書交談,安全代理,虛擬代理,遠(yuǎn)程代理
-----After advice ------
跟老板交談
- 默認(rèn)情況下,如果SpringAOP發(fā)現(xiàn)目標(biāo)對象實(shí)現(xiàn)了相應(yīng)Interface,則采用動態(tài)代理機(jī)制為其生成代理對象實(shí)例。而如果目標(biāo)對象沒有實(shí)現(xiàn)任何Interface,SpringAOP會嘗試使用一個稱為CGLIB
- 動態(tài)字節(jié)碼生成
- 原理: 對目標(biāo)對象進(jìn)行繼承擴(kuò)展,為其生成相應(yīng)的子類,而子類可以通過覆寫來擴(kuò)展父類的行為,只要將橫切邏輯的實(shí)現(xiàn)放到子類中,然后讓系統(tǒng)使用擴(kuò)展后的目標(biāo)對象的子類,達(dá)到與代理模式相同效果。
- 缺點(diǎn): 無法對final方法進(jìn)行覆蓋
- 代碼示例
package com.mg.springjiemi.proxy.cglib;
public class SubjectImpl {
public String request() {
return "跟老板交談";
}
}
package com.mg.springjiemi.proxy.cglib;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class SubjectCallback implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
try{
System.out.println("-----Before Advice ------");
if(method.getName().equals("request")){
System.out.println("先跟秘書交談,安全代理,虛擬代理,遠(yuǎn)程代理");
}
return methodProxy.invokeSuper(o,objects);
// System.out.println("----- After returning advice -----");
}catch (Exception e) {
System.out.println("-----After throwing advice ------");
return null;
} finally {
System.out.println("-----After advice ------");
}
}
}
package com.mg.springjiemi.proxy.cglib;
import net.sf.cglib.proxy.Enhancer;
public class Client {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SubjectImpl.class);
enhancer.setCallback(new SubjectCallback());
SubjectImpl proxy = (SubjectImpl) enhancer.create();
String value = proxy.request();
System.out.println(value);
}
}
控制臺輸出:
-----Before Advice ------
先跟秘書交談,安全代理,虛擬代理,遠(yuǎn)程代理
-----After advice ------
跟老板交談
3 三種Spring AOP的使用方式
- 根據(jù)當(dāng)前應(yīng)用環(huán)境、各種工具支持、整體團(tuán)隊(duì)對各種方式的熟悉程度等不同情況來權(quán)衡利弊,最終決定一種合適的方式
- 在2.x版本的AOP中,不論是對AspectJ集成還是基于Schema的AOP的實(shí)現(xiàn),最終的底層實(shí)現(xiàn)還是基于第一代Spring AOP的各種理念和實(shí)體
3.1 Spring AOP 1.x版本發(fā)布的基于接口定義的Advice聲明方式。
- 描述: 各種類型的Advice定義需要實(shí)現(xiàn)特定的接口,Advice的管理可以通過10C容器或者直接編程來進(jìn)行。
3.2 基于Schema的AOP,Spring 2.0發(fā)布之后增加的Spring AOP使用方式
- 描述: Spring-AOP-1.x版本和@AspectJ形式的AOP優(yōu)點(diǎn),不但完全支持Spring-AOP-1.x的配置需求,而且在@AspectJ形式的AOP的基礎(chǔ)之上進(jìn)行了改裝:也可以通過POJO聲明Aspect和Advice定義。不過,通過相應(yīng)注解表達(dá)的各種信息,轉(zhuǎn)移到了XSD形式的容器配置文件中。
- 應(yīng)用環(huán)境: Spring 2.x版本,但是卻無法使用Java 5
3.3 @AspectJ形式的AOP 在Spring 2.0發(fā)布之后新增加的一種Spring AOP使用方式
- 描述: 只需要以普通bean的形式聲明相應(yīng)的Aspect和Advice,然后通過相應(yīng)的注解標(biāo)注一下即可
- 好處: 各種信息的管理統(tǒng)一到了一個位置,并且由于IDE的重構(gòu)支持,AOP實(shí)現(xiàn)的管理更加方便高效。另外,只需要使用注解標(biāo)注POJO中的Advice定義方法即可,而不用實(shí)現(xiàn)規(guī)定的接口來實(shí)現(xiàn)各種Advice類型。
- 應(yīng)用環(huán)境: Java5以及更高版本的Java虛擬機(jī)之上
4 AspectJ形式的AOP使用
- Spring發(fā)布2.0版本之后,SpringAOP框架集成了Aspect的部分功能,這其中就包括Aspecti的Pointcut描述語言支持
4.1 @AspectJ形式Pointcut
- Pointcut Expression
- @Pointcut(標(biāo)志符(表達(dá)式))
- Pointcut Signature 具體化為一個方法定義
- 返回類型必須是void
- public型的Pointcut Signature可以在其他Aspect定義中引用
- private則只能在當(dāng)前Aspect定義中引用
- 可以將Pointcut Signature作為相應(yīng)Pointcut-Expression的標(biāo)志符,在Pointcut-Expression的定義中取代重復(fù)的Pointcut表達(dá)式定義
@Pointcut("execution(* com.lcw.one.util.basetkmybatis.BaseConsumerController+.*(..)) || execution(* com.lcw.one.main.controller.*.*(..))")
public void webLog(){}
@Pointcut("webLog()")
public void webLog2(){}
- Pointcut 標(biāo)志符
- execution
- execution (modifiers-pattern? ret-type-pattern declaring-type-pattern?
name-pattern (param-pattern) throws-pattern?) - execution(<修飾符模式>?<返回類型模式><方法所在類的全路徑名>?<方法名模式>(<參數(shù)模式>)<異常模式>?) 方法的返回類型、方法名以及參數(shù)部分的匹配模式是必須指定的
- execution通配符
- * 可以用于任何部分的匹配模式中,可以匹配相鄰的多個字符,即一個Word
- .. 通配符可以在兩個位置使用,在類型模式中匹配任何數(shù)量子包;而在方法參數(shù)模式中匹配任何數(shù)量參數(shù)。
- + 配指定類型的子類型;僅能作為后綴放在類型模式后邊。
- within 匹配指定類型下所有的Joinpoint
- SpringAOP只支持方法級別的Joinpoint,所以,在我們?yōu)閣ithin指定某個類后,它將匹配指定類所聲明的所有方法執(zhí)行
- this spring-aop匹配目標(biāo)對象的代理對象, AspectJ中代調(diào)用方法一方所在的對象
- target spring-aop匹配目標(biāo)對象,AspectJ中被調(diào)用方法所在的對象
- args 匹配擁有指定參數(shù)類型、指定參數(shù)數(shù)量的,會在運(yùn)行期間動態(tài)檢查參數(shù)的類型。
- @within 匹配持有指定注解類型內(nèi)的方法,靜態(tài)匹配
- @target 匹配目標(biāo)對象持有指定的注解,動態(tài)匹配
- @args 匹配方法傳入的參數(shù)持有指定注解的執(zhí)行;
- @annotation:匹配方法持有指定注解的方法;
- execution (modifiers-pattern? ret-type-pattern declaring-type-pattern?
- execution
public boolean login (object user); 傳入 mg.User 對象。
execution (* * (User)) 匹配不到 靜態(tài)pointcut
args(mg.User) 可以 動態(tài)pointcut
4.2 @AspectJ形式的Advice
@Before
@AfterReturning
-
@AfterThrowing
- throwing 屬性
- 可以限定Advice定義方法的參數(shù)名,并在方法調(diào)用的時(shí)候,將相應(yīng)的異常綁定到具體方法參數(shù)上
- throwing 屬性
@After
@Around
-
@DeclareRarents
- 用于標(biāo)注Introduction類型的Advice,但該注解對應(yīng)標(biāo)注對象的域(Field),而不是方法
- 指定新接口定義的實(shí)現(xiàn)類以及將要加諸其上的目標(biāo)對象
-
可能需要在Advice定義中訪問Joinpoint處的方法參數(shù)
- 通過org.aspectj.lang.JoinPoint
- @Before,@AfterReturning,@AfterThrowing,@After JoinPoint必須是第一個參數(shù)
- 通過arg等標(biāo)志符綁定
- 當(dāng)args標(biāo)志符接受的不是具體的對象類型,而是某個參數(shù)名稱的時(shí)候,它會將這個參數(shù)名稱對應(yīng)的參數(shù)值綁定到對Advice方法的調(diào)用
- 通過org.aspectj.lang.JoinPoint
-
Advice的執(zhí)行順序
- 如果匹配同一個Joinpoint的多個Advice都聲明在同一個Aspect定義中
- Advice的執(zhí)行順序,由它們在Aspect中的聲明順序決定。
- 對于AfterReturngingAdvice,擁有最高優(yōu)先級的則是最后運(yùn)行
- Advice聲明在不同的Aspect內(nèi)的時(shí)候。如果多個Advice聲明所對應(yīng)的Pointcut定義匹配同一個Joinpoin
- Ordered接口 注解 值越小優(yōu)先級越高
- 如果匹配同一個Joinpoint的多個Advice都聲明在同一個Aspect定義中
-
Aspect的實(shí)例化模式
- 使用perthis或者pertarget指定了Aspect的實(shí)例化模式之后,將這些Aspect注冊到容器時(shí),不能為其bean定義指定singleton的scope,否則會出現(xiàn)異常。畢容器先限定了只有一個實(shí)例,就不能為每一個代理對象或者目標(biāo)對象實(shí)例化相應(yīng)實(shí)例。
- singleton 切面只會有一個實(shí)例
- perthis 符合條件的代理對象 aop對象 創(chuàng)建一個切面實(shí)例
- pertarget 符合條件的每個目標(biāo)對象創(chuàng)建一個切面實(shí)例
// 案例
@Aspect("perthis(this(mg.Apple))")
@Component
public class WebLogAspect {
private Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
@Pointcut("this(mg.Apple)")
public void webLog(){}
}
@Pointcut("webLog()")
public void webLog2(){}
5 AOP 實(shí)現(xiàn)
5.1 AnnotationAwareAspectJAutoProxyCreator
- aop的實(shí)現(xiàn)基本靠 AnnotationAwareAspectJAutoProxyCreator 完成。根據(jù)@Pointcut 注解定義的切點(diǎn)來自動代理相匹配的bean
- 創(chuàng)建代理主要包含了兩個步驟
- 獲取增強(qiáng)方法或者增強(qiáng)器
- 根據(jù)獲取的增強(qiáng)進(jìn)行代理
- 織入
- BeanPostProcessor 的 postProcessAfterInitialization 后置處理器 實(shí)現(xiàn)織入。
-
AnnotationAwareAspectJAutoProxyCreator 類圖 image.png
- 繼承了 InstantiationAwareBeanPostProcessor link
- 繼承了 BeanPostProcessor
- 主要作用在于目標(biāo)對象的實(shí)例化過程中需要處理的事情,包括實(shí)例化對象的前后過程以及實(shí)例的屬性設(shè)置
- 繼承了 InstantiationAwareBeanPostProcessor link
