AOP切面編程
- 💗AOP-官方文檔
- 🍝AOP 講解
- 🍝AOP APIs
- 💗動態代理
- 🍝初探動態代理
- 🍝動態代理深入
- 🍝AOP問題提出
- 📗使用土方法解決
- 📗 對土方法解耦-開發最簡單的AOP類
- 📗 土方法缺點
上文中, 我們學習到了 Spring系列三:基于注解配置bean
接下來我們學習, AOP切面編程
Spring項目
💗AOP-官方文檔
🍝AOP 講解
AOP 講解: spring-framework-5.3.8\docs\reference\html/core.html
🍝AOP APIs
AOP APIs: spring-framework-5.3.8\docs\reference\html/core.html
spring-framework-5.3.8/docs/javadoc-api/index.html
💗動態代理
🍝初探動態代理
需求說明
1.由Vehicle (交通工具接口, 有一個run方法), 下面有兩個類 Car 和 Ship
2.當運行Car對象的 run 方法 和 ship對象的 run 方法時, 輸入如下內容
交通工具開始運行了…
輪船在海上航行…
交通工具停止運行了…
交通工具開始運行了…
小汽車在路上跑…
交通工具停止運行了…
解決方案一: 傳統方案
1.新建com.zzw.spring.aop.proxy.Vehicle
接口
//接口, 該接口有run方法
public interface Vehicle {void run();
}
2.新建com.zzw.spring.aop.proxy.Car
public class Car implements Vehicle {@Overridepublic void run() {System.out.println("交通工具開始運行了....");System.out.println("小汽車在路上 running....");System.out.println("交通工具停止運行了....");}
}
3.新建com.zzw.spring.aop.proxy.Ship
public class Ship implements Vehicle {@Overridepublic void run() {System.out.println("交通工具開始運行了....");System.out.println("大輪船在路上 running....");System.out.println("交通工具停止運行了....");}
}
4.新建com.zzw.spring.aop.proxy.TestVehicle
public class TestVehicle {@Testpublic void run() {//OOP基礎=>java基礎Vehicle vehicle = new Ship();//動態綁定vehicle.run();}
}
來思考一下, 這個解決方案好嗎? ====> 代碼冗余, 其實就是單個對象的調用, 并沒有很好的解決.
解決方案二: 動態代理方式
動態代理解決思路: 在調用方法時, 使用反射機制, 根據方法去決定調用哪個對象方法
1.新建com.zzw.spring.aop.proxy.VehicleProxyProvider
public class VehicleProxyProvider {//定義一個屬性//target_vehicle 表示真正要執行的對象//該對象實現了Vehicle接口private Vehicle target_vehicle;//構造器public VehicleProxyProvider(Vehicle target_vehicle) {this.target_vehicle = target_vehicle;}//編寫一個方法, 可以返回一個代理對象public Vehicle getProxy() {//得到類加載器ClassLoader classLoader =target_vehicle.getClass().getClassLoader();//得到要代理對象/被執行對象 的接口信息, 底層是通過接口來完成調用Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();//創建InvocationHandler 對象//因為 InvocationHandler 是接口, 所以我們可以通過匿名對象的方式來創建該對象/*** public interface InvocationHandler {* public Object invoke(Object proxy, Method method, Object[] args)* throws Throwable;* }* invoke 方法是將來執行target_vehicle的方法時, 會調用到*/InvocationHandler invocationHandler = new InvocationHandler() {/*class VehicleProxyProvider$01 implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("交通工具開始運行了....");//這里是我們的反射基礎 => OOPObject result = method.invoke(target_vehicle, args);System.out.println("交通工具停止運行了....");return result;}}InvocationHandler invocationHandler = new VehicleProxyProvider$01();*//*** invoke 方法是將來執行我們的target_vehicle的方法時, 會調用到** @param proxy 表示代理對象* @param method 就是通過代理對象調用方法時, 的那個方法 代理對象.run()* @param args 表示調用 代理對象.run(xx) 傳入的參數* @return 表示 代理對象.run(xx) 執行后的結果.* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("交通工具開始運行了....");//這里是我們的反射基礎 => OOP//method 是 public abstract void com.zzw.spring.aop.proxy.Vehicle.run()//target_vehicle 是 Ship對象//args 是 null//這里通過反射+動態綁定機制, 就會執行到被代理對象的方法//執行完畢就返回Object result = method.invoke(target_vehicle, args);System.out.println("交通工具停止運行了....");return result;}};/*public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)解讀1.Proxy.newProxyInstance() 可以返回一個代理對象2.ClassLoader loader: 類加載器,3.Class<?>[] interfaces 就是將來要代理的對象的接口信息4.InvocationHandler h 調用處理器/對象, 有一個非常重要的方法invoke*/Vehicle proxy =(Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);return proxy;}
}
2.修改com.zzw.spring.aop.proxy.Vehicle
public interface Vehicle {void run();public String fly(int height);
}
3.修改com.zzw.spring.aop.proxy.Ship
public class Ship implements Vehicle {@Overridepublic void run() {//System.out.println("交通工具開始運行了....");System.out.println("大輪船在路上 running....");//System.out.println("交通工具停止運行了....");}@Overridepublic String fly(int height) {return "輪船可以飛行 高度=" + height + "米";}
}
4.com.zzw.spring.aop.proxy.TestVehicle
public class TestVehicle {@Testpublic void proxyRun() {//創建Ship對象Ship ship = new Ship();//創建VehicleProxyProvider對象, 并且我們要傳入代理的對象VehicleProxyProvider vehicleProxyProvider= new VehicleProxyProvider(ship);//獲取代理對象, 該對象可以代理執行方法//解讀//1.proxy 編譯類型Vehicle,//2.運行類型 是代理類型, 即 class com.sun.proxy.$Proxy8Vehicle proxy = vehicleProxyProvider.getProxy();System.out.println("proxy的編譯類型是 Vehicle");System.out.println("proxy的運行類型是" + proxy.getClass());//下面解讀/debug怎么執行到 代理對象的 public Object invoke(Object proxy, Method method, Object[] args)//梳理完畢, proxy的編譯類型是Vehicle, 運行類型是Proxy class com.sun.proxy.$Proxy8//所以當執行run方法時, 會執行到 代理對象的invoke//如果體現動態 [1.被代理的對象 2.方法]//proxy.run();String result = proxy.fly(10000);System.out.println("result=" + result);System.out.println("ok");}
5.debug
🍝動態代理深入
需求說明
1.有一個SmartAnimal
接口, 可以完成簡單的加減法, 要求在執行 getSum()
和 getSub()
時, 輸出執行前, 執行過程, 執行后的日志結果. 輸出內容如下:
日志-方法名-getSum-參數 1.5 4.5
方法內部打印result = 6.0
日志-方法名-getSum-結果result= 6.0
=================================
日志-方法名-getSub-參數 1.4 3.3
方法內部打印result = -1.9
日志-方法名-getSub-結果result= -1.9
解決方案一: 傳統方案
1.新建com.zzw.spring.aop.proxy2.SmartAnimalAble
接口
public interface SmartAnimalAble {//求和float getSum(float i, float j);//求差float getSub(float i, float j);
}
2.com.zzw.spring.aop.proxy2.SmartCat
public class SmartCat implements SmartAnimalAble {@Overridepublic float getSum(float i, float j) {System.out.println("日志-方法名-getSum-參數 " + i + " " + j);float result = i + j;System.out.println("方法內部打印result = " + result);System.out.println("日志-方法名-getSum-結果result= " + (i + j));return result;}@Overridepublic float getSub(float i, float j) {System.out.println("日志-方法名-getSub-參數 " + i + " " + j);float result = i - j;System.out.println("方法內部打印result = " + result);System.out.println("日志-方法名-getSub-結果result= " + (i - j));return result;}
}
3.com.zzw.spring.aop.proxy2.AopTest
public class AopTest {@Testpublic void run() {SmartAnimalAble smartAnimalAble = new SmartCat();smartAnimalAble.getSum(1.5f, 4.5f);System.out.println("=================================");smartAnimalAble.getSub(1.4f, 3.3f);}
}
解決方案二: 動態代理方式
考慮代理對象調用方法(底層是反射調用)時, 可能出現的異常
- [橫切關注點]
1.新建com.zzw.spring.aop.proxy2.MyProxyProvider
//可以返回一個動態代理對象, 可以執行SmartCat對象的方法
public class MyProxyProvider {//這是一個屬性, 是我們要執行的目標對象//該對象實現了SmartAnimalAble接口private SmartAnimalAble target_obj;//構造器MyProxyProvider(SmartAnimalAble target_obj) {this.target_obj = target_obj;}//編寫一個方法, 可以返回一個代理對象//該代理對象可以執行目標方法public SmartAnimalAble getProxy() {//1.得到類加載器ClassLoader classLoader =target_obj.getClass().getClassLoader();//2.得到要執行的目標對象的接口信息Class<?>[] interfaces = target_obj.getClass().getInterfaces();//3.創建InvocationHandler 對象InvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String name = method.getName();//方法名Object result = null;try {System.out.println("方法執行前-日志-方法名-" + name + "-參數 "+ Arrays.asList(args));//這里從aop的角度看,就是一個橫切關注點-前置通知//使用反射調用方法result = method.invoke(target_obj, args);System.out.println("方法執行正常結束-日志-方法名-" + name + "-結果result= "+ result);//這里從aop的角度看, 也是一個橫切關注點-返回通知return result;} catch (Exception e) {e.printStackTrace();//如果反射執行方法時, 出現異常, 就會進入到catch{}System.out.println("方法執行異常-日志-方法名-" + name + "-異常類型="+ e.getClass().getName());//這里從aop的角度看, 又是一個橫切關注點-異常通知} finally {//不管你是否出現了異常, 最終都會執行到 finally {}//這里從aop的角度看, 還是一個橫切關注點-最終通知System.out.println("方法最終結束-日志-方法名-" + name);}return result;}};//創建代理對象SmartAnimalAble proxy =(SmartAnimalAble) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);return proxy;}
}
2.修改com.zzw.spring.aop.proxy2.SmartCat
public class SmartCat implements SmartAnimalAble {@Overridepublic float getSum(float i, float j) {//System.out.println("日志-方法名-getSum-參數 " + i + " " + j);float result = i + j;System.out.println("方法內部打印result = " + result);//System.out.println("日志-方法名-getSum-結果result= " + (i + j));return result;}@Overridepublic float getSub(float i, float j) {//System.out.println("日志-方法名-getSub-參數 " + i + " " + j);float result = i - j;System.out.println("方法內部打印result = " + result);//System.out.println("日志-方法名-getSub-結果result= " + (i - j));return result;}
}
3.com.zzw.spring.aop.proxy2.AopTest
public class AopTest {@Testpublic void smartCatTestProxy() {//創建SmartCat對象SmartAnimalAble smartAnimalAble = new SmartCat();MyProxyProvider myProxyProvider= new MyProxyProvider(smartAnimalAble);//獲取代理對象, 該對象可以代理執行方法SmartAnimalAble proxy = myProxyProvider.getProxy();System.out.println("proxy的編譯類型是 SmartAnimalAble");System.out.println("proxy的運行類型是 " + proxy.getClass());//proxy的編譯類型是SmartAnimalAble, 運行類型是 Class com.sun.proxy.$Proxy8//所以當執行getSum方法時, 會執行到 代理對象的invokeproxy.getSum(1.2f, 2.4f);System.out.println("=================================");proxy.getSub(1.3f, 4.5f);System.out.println("ok");}
}
🍝AOP問題提出
在MyProxyProvider.java
中, 我們的輸出語句功能比較弱, 在實際開發中, 我們希望是以一個方法的形式, 嵌入到真正執行的目標方法前.
如圖分析
📗使用土方法解決
需求分析
使用土方法解決前面的問題, 后面使用Spring的AOP組件完成
1.先建一個包, 把相關文件拷貝過來, 進行修改完成. ----這里只是模擬, 并沒有真的新建包
//我們的一個方法, 在目標對象執行前執行
public void before(Method method, Object[] args) {System.out.println("before方法執行前-日志-方法名-" + method.getName() + "-參數 "+ Arrays.asList(args));//這里從aop的角度看,就是一個橫切關注點-前置通知
}//我們的一個方法, 在目標對象執行后執行
public void after(Method method, Object result) {System.out.println("after方法執行正常結束-日志-方法名-" + method.getName() + "-結果result= "+ result);//這里從aop的角度看, 也是一個橫切關注點-返回通知
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String name = method.getName();//方法名Object result = null;before(method, args);//使用反射調用方法result = method.invoke(target_obj, args);after(method, result);return result;
}
2.該方法問題分析: 耦合度高
📗 對土方法解耦-開發最簡單的AOP類
1.新建com.zzw.spring.aop.proxy2.ZzwAOP
public class ZzwAOP {//我們的一個方法, 在目標對象執行前執行public static void before(Method method, Object[] args) {System.out.println("ZzwHsp-方法執行前-日志-方法名-" + method.getName() + "-參數 "+ Arrays.asList(args));//這里從aop的角度看,就是一個橫切關注點-前置通知}//我們的一個方法, 在目標對象執行后執行public static void after(Method method, Object result) {System.out.println("ZzwHsp-方法執行正常結束-日志-方法名-" + method.getName() + "-結果result= "+ result);//這里從aop的角度看, 也是一個橫切關注點-返回通知}
}
2.修改com.zzw.spring.aop.proxy2.MyProxyProvider
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String name = method.getName();//方法名Object result = null;try {//before(method, args);ZzwAOP.before(method, args);//使用反射調用方法result = method.invoke(target_obj, args);//after(method, result);ZzwAOP.after(method, result);return result;} catch (Exception e) {}
}
📗 土方法缺點
土方法 不夠靈活;
土方法 復用性差;
土方法 是一種硬編碼 (因為沒有注解和反射支撐)
Spring AOP 閃亮登場 - 底層是ASPECTJ