AOP 1.官方文檔 AOP講解:下載的spring文件-->spring-framework-5.3.8/docs/reference/html/core.html#aop AOP APIs:下載的spring文件-->spring-framework-5.3.8/docs/reference/h ...
AOP
1.官方文檔
AOP講解:下載的spring文件-->spring-framework-5.3.8/docs/reference/html/core.html#aop
AOP APIs:下載的spring文件-->spring-framework-5.3.8/docs/reference/html/core.html#aop-api
2.動態代理
2.1案例說明
需求說明:
-
有Vehicle(交通工具介面,有一個run方法),下麵有兩個實現類Car,Ship
-
當運行Car對象的run()方法和Ship對象的run()方法時,輸出如下內容,註意觀察前後有統一的輸出。
-
請思考如何完成?
2.2傳統方式解決
Vehicle介面:
package com.li.proxy;
/**
* @author 李
* @version 1.0
* 介面,定義了run方法
*/
public interface Vehicle {
public void run();
}
Ship類,實現Vehicle介面:
package com.li.proxy;
/**
* @author 李
* @version 1.0
*/
public class Ship implements Vehicle {
@Override
public void run() {
System.out.println("交通工具開始運行了...");
System.out.println("大輪船在水上 running...");
System.out.println("交通工具停止運行了...");
}
}
Car類,實現Vehicle介面:
package com.li.proxy;
/**
* @author 李
* @version 1.0
*/
public class Car implements Vehicle {
@Override
public void run() {
System.out.println("交通工具開始運行了...");
System.out.println("小汽車在公路 running...");
System.out.println("交通工具停止運行了...");
}
}
TestVehicle測試類:
package com.li.proxy;
import org.testng.annotations.Test;
/**
* @author 李
* @version 1.0
*/
public class TestVehicle {
@Test
public void run() {
Vehicle vehicle = new Car();//Vehicle vehicle = new Ship();
vehicle.run();//動態綁定,根據實際運行類型調用run方法
}
}
上面的方式,代碼冗餘,其實就是單個對象的調用,並沒有很好的解決問題。
2.3動態代理方式解決
解決思路:在調用方法的時候,使用反射機制,根據方法去決定調用哪個對象方法
Vehicle介面不變:
package com.li.proxy;
/**
* @author 李
* @version 1.0
* 介面,定義了run方法
*/
public interface Vehicle {
public void run();
}
Ship:
package com.li.proxy;
/**
* @author 李
* @version 1.0
*/
public class Ship implements Vehicle {
@Override
public void run() {
System.out.println("大輪船在水上 running...");
}
}
Car:
package com.li.proxy;
/**
* @author 李
* @version 1.0
*/
public class Car implements Vehicle {
@Override
public void run() {
System.out.println("小汽車在公路 running...");
}
}
創建VehicleProxyProvider,該類返回一個代理對象:
package com.li.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author 李
* @version 1.0
* VehicleProxyProvider類可以返回一個代理對象
*/
public class VehicleProxyProvider {
//定義一個屬性
//target_vehicle 表示真正要執行的對象
//要求該對象的類實現了Vehicle介面
private Vehicle target_vehicle;
//構造器
public VehicleProxyProvider(Vehicle target_vehicle) {
this.target_vehicle = target_vehicle;
}
//編寫一個方法,可以返回一個代理對象
public Vehicle getProxy() {
//(1)得到類載入器
ClassLoader classLoader =
target_vehicle.getClass().getClassLoader();
//(2)得到要代理的對象/被執行的對象 的介面信息,底層通過介面來完成調用
Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();
//(3)創建一個調用處理對象
/**
* public interface InvocationHandler {
* public Object invoke(Object proxy, Method method, Object[] args)
* throws Throwable;
* }
* invoke 方法在將來執行我們的 target_vehicle的方法時,會調用到
*/
//如上,因為InvocationHandler是介面,不能直接實例化
// 以匿名內部類的方式來獲取 InvocationHandler 對象
//這個對象有一個方法:invoke, 到時可以通過反射,動態調用目標對象的方法
InvocationHandler invocationHandler = new InvocationHandler() {
/**
* invoke()方法,在將來執行我們的target_vehicle的方法時會調用到
* @param proxy 表示代理對象
* @param method 就是通過代理對象調用方法時,的哪個方法
* @param args 表示調用代理對象的方法時,傳入方法的參數
* @return 表示代理對象.方法(xx) 執行後的結果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("交通工具開始運行了...");
//這個地方的方法,就是你調用時動態傳入的,可能是 run , 可能是 hi 等
Object result = method.invoke(target_vehicle, args);
System.out.println("交通工具停止運行了...");
return result;
}
};
/*(4)
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
1.Proxy.newProxyInstance() 可以返回一個代理對象
2.ClassLoader loader :類載入器
3.Class<?>[] interfaces:要代理的對象的介面信息
4.InvocationHandler h:調用處理器/對象,該對象有一個非常重要的方法-invoke
*/
//將上面的 loader, interfaces, invocationHandler構建一個 Vehicle的代理對象.
Vehicle proxy =
(Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
TestVehicle測試類:
@Test
public void proxyRun(){
//創建Ship對象
Vehicle vehicle = new Ship();
//創建VehicleProxyProvider對象,並且傳入要代理的對象
VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(vehicle);
//獲取代理對象,該對象可以代理執行方法
//1.proxy 編譯類型是Vehicle
//2.proxy 運行類型是 代理類型: class com.sun.proxy.$Proxy3
Vehicle proxy = vehicleProxyProvider.getProxy();
System.out.println("proxy 的運行類型是="+proxy.getClass());
proxy.run();
}
2.4動態代理機制debug
如上所示,當執行proxy.run();
時,成功輸出了目標信息,那麼代碼是怎麼執行到代理對象的invoke方法上的?
1.在proxy.run();
旁打上斷點,點擊step into,游標跳轉至VehicleProxyProvider的invoke方法:
2.點擊step over,游標跳轉到下一行,此時method,target_vehicle代表了什麼?
如上,此時的method就是我們Vehicle介面中的run方法,target_vehicle對象的實際運行類型是Ship,因為我們沒有傳值進去所以args為null。
3.在這行點擊step into,進入invoke方法的源碼,點擊step over跳到return ma.invoke(obj, args);
:
在這行點擊step into:
可以看到,底層最終執行了invoke0()方法:
這裡可以發現method 就是Vehicle的run方法,該方法會通過動態綁定,根據實際運行對象的類型(在這裡即Ship),調用Ship中的run方法;var2就是我們調用代理對象的方法時傳入的參數,這裡為null:
4.在這行再次點擊step into,可以看到游標跳轉到了Ship對象的run方法中,此時調用了Ship中的run方法:
5.點擊step over,因為底層代碼執行完畢。游標層層返回到之前的method.invoke(target_vehicle,args)語句:
6.因為Ship的run方法返回void,因此result的值為null:
7.當執行到return result;
時,相當於使用代理對象執行run方法執行完畢,因此返回上一層:
至此,總體的流程就執行完畢了。
總結梳理:
1.proxy的編譯類型是Vehicle,因此可以調用run方法;proxy的運行類型是class com.sun.proxy.$Proxy3,所以當執行run方法時會執行到代理對象的invoke方法
2.invoke 方法使用反射機制來調用 run()方法(註意這個 run 方法也可以是Vehicle 的其它方法),這時就可以在調用 run()方法前,進行前置處理和後置處理
3.也就是說 proxy 的 target_vehicle 運行類型只要是實現了 Vehicle 介面,就可以去調用不同的方法,是動態的,變化的,底層就是使用反射完成的。
3.動態代理練習
3.1案例說明
需求說明:
有一個SmartAnimal介面,可以完成簡單的加減法,要求在執行getSum()和getSub()時,輸出執行前、執行過程、執行後的日誌輸出(參考如下),請思考如何實現
方法執行開始-日誌-方法名-getSub-參數 [10.0, 2.0]
方法內部列印 result = 8.0
方法執行正常結束-日誌-方法名-getSub-結果 result= 8.0
//可能的異常信息
方法最終結束-日誌-方法名-getSub//finally的輸出
======================
方法執行開始-日誌-方法名-getSum-參數 [10.0, 2.0]
方法內部列印 result = 12.0
方法執行正常結束-日誌-方法名-getSum-結果 result= 12.0
//可能的異常信息
方法最終結束-日誌-方法名-getSum//finally的輸出
- 請使用傳統方法完成
- 請使用動態代理方式完成,並要求考慮代理對象調用方法(底層是反射調用)時,可能出現的異常
3.2傳統方式解決
SmartAnimal介面:
package com.li.aop.proxy2;
/**
* @author 李
* @version 1.0
*/
public interface SmartAnimal {
//求和
float getSum(float a, float b);
//求差
float getSub(float a, float b);
}
SmartCat類實現介面SmartAnimal:
package com.li.aop.proxy2;
/**
* @author 李
* @version 1.0
*/
public class SmartCat implements SmartAnimal {
@Override
public float getSum(float a, float b) {
System.out.println("日誌-方法名-getSum-參數 " + a + " " + b);
float result = a + b;
System.out.println("方法內部列印 result = " + result);
System.out.println("日誌-方法名-getSum-結果result= " + result);
return result;
}
@Override
public float getSub(float a, float b) {
System.out.println("日誌-方法名-getSub-參數 " + a + " " + b);
float result = a - b;
System.out.println("方法內部列印 result = " + result);
System.out.println("日誌-方法名-getSub-結果result= " + result);
return result;
}
}
AopTest測試類:
package com.li.aop.proxy2;
import org.testng.annotations.Test;
/**
* @author 李
* @version 1.0
*/
public class AopTest {
@Test
public void testSmartAnimal() {
SmartAnimal smartAnimal = new SmartCat();
smartAnimal.getSum(10, 2);
System.out.println("=======================");
smartAnimal.getSub(10, 2);
}
}
傳統方法的特點:
優點:實現簡單直接
缺點:日誌代碼維護不方便,代碼復用性差
解決思路:
- 使用動態代理來更好地處理日誌記錄問題
- 其他比如封裝函數,或者類的繼承在這裡都不是特別合適
3.3動態代理方法解決
SmartAnimal介面不變。
SmartDog實現SmartAnimal介面:
package com.li.aop.proxy2;
/**
* @author 李
* @version 1.0
*/
public class SmartDog implements SmartAnimal {
@Override
public float getSum(float a, float b) {
float result = a + b;
System.out.println("方法內部列印 result = " + result);
return result;
}
@Override
public float getSub(float a, float b) {
float result = a - b;
System.out.println("方法內部列印 result = " + result);
return result;
}
}
MyProxyProvider類:
package com.li.aop.proxy2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* @author 李
* @version 1.0
* 返回一個動態代理對象,可以執行被代理的對象的方法
*/
public class MyProxyProvider {
//定義要執行的目標對象,該對象需要實現 SmartAnimal介面
private SmartAnimal target_animal;
//構造器
public MyProxyProvider(SmartAnimal target_animal) {
this.target_animal = target_animal;
}
//定義方法返回代理對象,該代理對象可以執行目標對象
public SmartAnimal getProxy() {
//(1)先得到類載入器對象
ClassLoader classLoader = target_animal.getClass().getClassLoader();
//(2)得到要執行的目標對象的介面信息
Class<?>[] interfaces = target_animal.getClass().getInterfaces();
//(3)使用匿名內部類 創建 InvocationHandler對象
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
System.out.println("方法執行開始-日誌-方法名-" + method.getName() +
"-參數 " + Arrays.toString(args));//這裡從AOP的角度看,就是一個橫切關註點-前置通知
//使用反射真正調用方法
result = method.invoke(target_animal, args);
System.out.println("方法執行正常結束-日誌-方法名-" + method.getName()
+ "-結果 result = " + result);//也是一個橫切關註點-返回通知
} catch (Exception e) {
//如果反射出現異常,就會進入到catch塊
System.out.println("方法執行異常-日誌-方法名" + method.getName()
+ "-異常類型=" + e.getClass().getName());//也是一個橫切關註點-異常通知
e.printStackTrace();
} finally {//無論是否出現異常,最終都會執行到 finally{}
//也是一個橫切關註點-最終通知
System.out.println("方法最終結束-日誌-方法名-" + method.getName());
}
return result;
}
};
//(4)創建代理對象
SmartAnimal proxy = (SmartAnimal) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return proxy;
}
}
測試方法:
@Test
public void testSmartAnimal() {
SmartAnimal smartAnimal = new SmartDog();
MyProxyProvider myProxyProvider
= new MyProxyProvider(smartAnimal);
SmartAnimal proxy = myProxyProvider.getProxy();
proxy.getSum(10, 2);
System.out.println("=======================");
proxy.getSub(10, 2);
}
3.4問題提出
在MyProxyProvider類中,我們的輸出語句功能比較弱,在實際開發中,我們希望是以一個方法的形式,嵌入到真正執行的目標方法前,怎麼辦?