前言
容器是OOP的高級工具:
以低耦合低侵入的方式打通從上到下的開發通道
按部就班填充代碼邏輯實現業務功能,每層邏輯都可無縫替換
OOP將業務程序分解成各個層次的對象,通過對象聯動完成業務
無法很好地處理分散在各業務裏的通用系統需求
系統需求
碼農才去關係的需求
添加日誌信息:為每個方法添加統計時間
添加系統權限校驗:針對某些方法進行限制
軟件工程中有個基本原則:關注點分離 Concern Separation
不同的問題交給不同的部分去解決,每部分專注解決自己的問題
Aspect Oriented Programming就是其中一種關注點分離的技術
通用化功能的代碼實現即切面Aspect
Aspect之於AOP,就相當於Class之於OOP,Bean之於Spring
框架搭建圖:
AOP的子民們
切面Aspect:將橫切關注點邏輯進行模塊化封裝的實體對象
通知Advice:好比是Class裏面的方法,還定義了織入邏輯的時機
連接點Joinpoint,允許使用Advice的地方(對於SpringAOP來講默認只支持方法級別的Joinpoint)
切入點Pointcut:定義一系列規則對Joinpoint進行篩選
目標對象Target:符合Pointcut條件,要被織入橫切邏輯的對象
AOP是OOP裏的"寄生蟲"
織入:將Aspect模塊化的橫切關注點集成到OOP裏
織入器︰完成織入過程的執行者,如ajc
Spring AOP則會使用一組類來作為織入器以完成最終的織入操作
Advice的種類
BeforeAdvice:在JoinPoint前被執行的Advice(該方法並不能阻止JoinPoint的執行,除非拋出異常)
AfterAdvice: 就好比try..catch..finaly裏面的finaly(無論Joinpoint能否正常執行,都會執行到)
AfterReturningAdvice:在Joinpoint執行流程正常返回後被執行(如果Joinpoint拋出異常,則不會執行)
AfterThrowingAdvice:在Joinpoint執行過程中拋出異常才會觸發(如果trycatch了,是不會到這的)
AroundAdvice:在Joinpoin前和後都執行,最常用的Advice(常用)
Introduction——引入型Advice
為目標類引入新接口,而不需要目標類做任何實現
使得目標類在使用的過程中轉型成新接口對象,調用新接口的方法
注:
這種方式的操作,對於代碼的閲讀理解是比較複雜的
使用方法:
首先聲明好一個接口,一個實現類
public interface LittleUniverse {
void burningup();
}
public class LittleUniverseImpl implements LittleUniverse {
@Override
public void burningup() {
System.out.println("燃燒吧小宇宙");
}
}
然後在Aspect中添加上如下代碼:
@DeclareParents(value = "com.imooc.controller..*",defaultImpl = com.imooc.introduction.impl.LittleUniverseImpl.class)
public LittleUniverse littleUniverse;//這樣子我們便賦予了每個Controller都能變身為littleUniverse的權力
此時我們在容器中getBean獲取到了Controller之後,便可以將他強轉類型並調用接口的方法。
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Entrance.class);
HiController hiController = (HiController)applicationContext.getBean("hiController");
((LittleUniverse)hiController).burningup();
}
SpringAOP 的 實 現 原 理
在學習SpringAOP內部的實現原理之前,首先我們要了解代理模式
通常代理模式由(抽象主題)(被代理角色)(代理角色)三個模塊完成
抽象主題:可以是抽象類或者抽象接口(用户只需要只需要面向抽象主題即可)
代理類:實現或者繼承自抽象主題(實例化的時候使用代理類)(SpringAOP需要做的便是生成這麼個代理類,替換掉真正的實現類來提供服務)
被代理類:實現或者繼承自抽象主題
代碼演示:
首先我們聲明一個(個人支付)接口及其接口實現類 (被代理對象)
public interface toCPayment {
void pay();
}
public class toCPaymentImpl implements toCPayment {
@Override
public void pay() {
System.out.println("以用户的名義進行支付");
}
}
然後我們對於(個人支付)接口的實現類定義一個類(代理對象)
public class AlipayToC implements toCPayment {
toCPayment toCPayment;
public AlipayToC(toCPayment toCPayment){
this.toCPayment = toCPayment;
}
@Override
public void pay() {
beforPay();
toCPayment.pay();
afterPay();
}
private void afterPay() {
System.out.println("支付給慕課網");
}
private void beforPay() {
System.out.println("從招行取款");
}
}
這時候我們去調用支付方法
public class ProxyDemo {
public static void main(String[] args) {
toCPayment toCProxy = new AlipayToC(new toCPaymentImpl());
toCProxy.pay();
}
}
執行結果:
從招行取款
以用户的名義進行支付
支付給慕課網
-----------------------
上面這種代理模式是靜態的代理模式,如果我們要添加多一個(企業支付)接口,即使支付前後的方法一樣,只是裏頭的pay()方法改變,那麼我們又要重新按照剛剛的流程走一步。這樣也是相對比較麻煩的
這種模式的思路是沒有問題的,只是相關的實現方式需要改進。
尋求改進:
在改進前我們重新回顧一下ClassLoader(類加載器)
溯源ClassLoader
通過帶有包名的類來獲取對應class文件的二進制字節流
根據讀取的字節流,將代表的靜態存儲結構轉化為(方法區的)運行時數據結構
生成一個代表該類的Class對象,作為方法區該類的數據訪問入口
在複習完ClassLoader之後呢,我們可以根據ClassLoader對切入點進行改進:
根據一定規則去改動或者生成新的字節流,將切面邏輯織入其中(既然字節流能定義類的行為,我們將切面邏輯織入其中,使其動態生成天生織入了切面邏輯的類。)
行之有效的方案就是取代被代理類的動態代理機制
根據接口或者目標類,計算出代理類的字節碼並加載到JVM中去
SpringAOP的實現原理之JDK動態代理
JDK1.3之後,java就為我們引入了JDK的動態代理機制
靜態代理,需要在編譯前實現,編譯完成後,代理類是一個實際的Class文件
JDK動態代理,是在運行時動態生成的,編譯完成後並沒有實際的Class文件,而是在運行時動態生成類的字節碼,並加載到JVM中(要求【被代理的類】必須實現接口)(並不要求【代理對象】去實現接口,所以可以複用代理對象的邏輯)
(採用JDK動態代理相比靜態代理,邏輯可複用)
其中涉及兩個比較重要的類:
java.lang.reflect.InvocationHandler:並非代理類,只是用來統一管理橫切邏輯的Aspect。
java.lang.reflect.Proxy:真正的代理類是由Proxy創建出來的。該類最關鍵的方法就是newProxyInstance方法,該方法最終會返回創建好的動態代理實例。
代碼演示:
首先創建管理橫切邏輯的,將被包裝類通過構造函數傳遞進來。
public class AlipayInvocationHandler implements InvocationHandler {
private Object targetObject;
public AlipayInvocationHandler(Object targetObject){
this.targetObject = targetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforPay();
Object result = method.invoke(targetObject,args);
afterPay();
return result;
}
private void afterPay() {
System.out.println("支付給慕課網");
}
private void beforPay() {
System.out.println("從招行取款");
}
}
創建工具類,去創建出真正的Proxy
public class jdkDynamicProxyUtil {
public static <T>T newProxyInstance(T targetObject, InvocationHandler handler){
ClassLoader classLoader = targetObject.getClass().getClassLoader();
Class<?>[] interfaces = targetObject.getClass().getInterfaces();
return (T)Proxy.newProxyInstance(classLoader,interfaces,handler);
}
}
最後main方法調用
public class ProxyDemo {
public static void main(String[] args) {
toCPayment toCPayment = new toCPaymentImpl();//首先創建被代理的類
InvocationHandler handler = new AlipayInvocationHandler(toCPayment);
toCPayment toCProxy = jdkDynamicProxyUtil.newProxyInstance(toCPayment,handler);
toCProxy.pay();
ToBPayment toBPayment = new ToBPaymentImpl();
InvocationHandler handlerToB = new AlipayInvocationHandler(toBPayment);
ToBPayment toBProxy =jdkDynamicProxyUtil.newProxyInstance(toBPayment,handlerToB);
toBProxy.pay();
}
}
SpringAOP的實現原理之CGLIB動態代理
cgLib代碼生成庫:Code Generation Library
不要求被代理類實現接口
內部主要封裝了ASM java字節碼操控框架(轉化字節碼產生新類)
動態生成子類以覆蓋非final的方法,綁定鈎子回調自定義攔截器
CGLib庫中,和動態代理最相關的就是net.sf.cglib.proxy.Proxy類。
(1)在net.sf.cglib.proxy包下有個類叫做Callback,這個類就是一個比較核心的底層接口了,看名字就知道該類是用來提供回調實現了,該類只是一個空接口(標記),主要幹活的是MethodInterceptor類
(2)MethodInterceptor類中有一個intercept(Object var1, Method var2, Object[] var3, MethodProxy var4)方法,方法的前三個參數跟JDK動態代理的invork方法一樣
1.動態代理對象 2.被代理的方法實例 3.被代理方法實例裏需要的參數數組。第四個參數則是動態代理Method的對象
(3)有了上面這些類,還需要個核心類來創建出最終代理對象(Enhancer),該類的核心方法create(),既可以用動態的方式調用,也可以通過靜態(static)的方式調用。
代碼演示:
首先我們創建被代理對象類:
public class CommonPayment {
public void pay(){
System.out.println("個人或者公司都可以走這個支付通道");
}
}
然後創建切面Aspect類
public class AlipayMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
beforPay();
Object result = methodProxy.invokeSuper(o,args);
afterPay();
return result;
}
private void afterPay() {
System.out.println("支付給慕課網");
}
private void beforPay() {
System.out.println("從招行取款");
}
}
然後針對cglib創建一個工具類
public class CglibUtil {
public static <T>T createProxy(T targetObject, MethodInterceptor methodInterceptor){
return (T)Enhancer.create(targetObject.getClass(),methodInterceptor);
}
}
最後main方法執行
public static void main(String[] args) {
//CGLib動態代理
CommonPayment commonPayment = new CommonPayment();
MethodInterceptor methodInterceptor = new AlipayMethodInterceptor();
CommonPayment commonPaymentProxy = CglibUtil.createProxy(commonPayment,methodInterceptor);
commonPaymentProxy.pay();
//哪怕是切換了代理對象也不需要對應的去創建接口
toCPayment toCPayment = new toCPaymentImpl();
toCPayment toCProxy = CglibUtil.createProxy(toCPayment,methodInterceptor);
toCProxy.pay();
}
總結:
實現機制:
JDK動態代理:基於反射機制實現,要求業務類必須實現接口
CGLIB:基於ASM機制實現,生成業務類的子類作為代理類
優勢:
JDK動態代理:
JDK原生,在JVM裏運行較為可靠
平滑支持JDK版本的升級
CGLIB:
被代理對象無需實現接口,能實現代理類的無侵入
在SpringAOP中的底層機制中靠的就是CGLIB和JDK動態代理共存
默認策略:Bean實現了接口則使用JDK,否則使用CGLIB
AspectJ框架
AspectJ框架是Eclipse託管給apache基金會的一個開源項目,提供了完整的AOP解決方案。AspectJ是AOP的java實現版本,定義了AOP的語法,可以説是對java的擴展。
AspectJ提供了兩套強大的機制:
1.定義切面語法以及切面語法的解析機制
2.提供了強大的織入工具
Spring-AOP與AspectJ框架 所支持的織入點:
如圖所示,我們可以看出SpringAOP只支持方法上的織入點,而AspectJ可以説是支持了所有的連接點(所以我們可以稱AspectJ是一套AOP的完整解決方案)
既然AspectJ這麼強大,為什麼Spring沒有完整複用呢。因為在方法上的織入已經可以滿足大多數的業務,而且在AspectJ的學習與使用上也遠比Spring複雜。
SpringAOP的脈絡分析圖:
日拱一卒無有盡,功不唐捐終入海