1. 動态代理相關概念
- 目标類:程式員自己寫的、普通的業務類,是需要被代理的類;
- 目标方法:目标類中的實作業務的具體方法,為了精簡,隻寫核心業務代碼,是以需要代理類來增強功能;
- 增強器:是給目标方法增強功能的類,需要繼承InvocationHandler接口,實作invoke(Object proxy, Method method, Object[] args)方法;
- 代理類:由JDK相關類在程式執行過程中、為了給目标類的相關目标方法增強功能而生成的、存在于記憶體中的類;
- 動态代理:在程式執行過程中,由JDK相關類來建立代理對象,通過代理對象來執行相關方法,在不改變目标類的業務代碼的前提下,達到給目标類相關方法增強功能的目的,使得程式的可擴充性大大增強;
2. 動态代理實作方式
- jdk動态代理:使用jdk的Proxy、InvocationHandler來建立代理對象,
- 原理:通過實作目标類的父接口,重寫目标類的相關方法
- 要求:目标類必須實作接口
- cglib動态代理:第三方的工具庫,建立動态代理,
- 原理:繼承,通過繼承目标類,建立具有增強功能的子類,
- 要求:目标類不能是final類型,目标增強方法不能是final
3. JDK動态代理執行個體示範
- 我現在要科普一下水的作用,核心類是Water,水可以飲用、清洗;
public interface Drinkable{
void drink();
}
public interface Washable{
void wash();
}
- 在核心類Water中,我們隻說核心功能:
public class Water implements Drinkable, Washable{
@Override
public void drink(){
System.out.println("喝水能夠解渴");
}
@Override
public void wash(){
System.out.println("水能用來清洗衣服");
}
}
- 但是科普講解還需要把事情的來龍去脈講清楚,增加的講解放在增強器中進行:
public class DrinkHandler implements InvocationHandler{
private Object target = null;//目标對象,通過構造器傳入
public DrinkHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println("身體内産生能量的化學反應要消耗水...");
method.invoke(target, args);
System.out.println("身體産生的廢物會溶解在水中排出體外...");
return null;//目标方法沒有傳回值的話,就傳回null
}
}
public class WashHandler implements InvocationHandler{
private Object target = null;
public WashHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println("衣服穿過就髒了...");
method.invoke(target, args);
System.out.println("洗過的衣服需要在太陽下晾曬才會幹的...");
return null;
}
}
- 要生成代理對象需要的步驟:
1> 建立目标對象
2> 建立增強器
3> 根據JDK相關類來建立代理對象(底層機制是反射)
4> 調用代理對象的方法,實作功能增強
public class WaterScience{
public static void main(String[] args){
//1.建立目标對象
Water water = new Water();
//2.建立增強器
DrinkHandler drinkHandler = new DrinkHandler(water);
WashHandler washHandler = new WashHandler(water);
//3.根據JDK相關類來建立代理對象(底層機制是反射)、
Drinkable drinkProxy = (Drinkable) Proxy.newProxyInstance(
water.getClass().getClassLoader(),
water.getClass().getInterfaces(),
drinkHandler);
Washable washProxy = (Washable) Proxy.newProxyInstance(
water.getClass().getClassLoader(),
water.getClass().getInterfaces(),
washHandler);
//4.調用代理對象的方法,實作功能增強
drinkProxy.drink();
System.out.println("========================");
washProxy.wash();
}
}
//以下是輸出結果:
身體内産生能量的化學反應要消耗水...
喝水能夠解渴
身體産生的廢物會溶解在水中排出體外...
========================
衣服穿過就髒了...
水能用來清洗衣服
洗過的衣服需要在太陽下晾曬才會幹的...
4. 源碼解析
1> 建立目标對象
2> 建立增強器
3> 根據JDK相關類來建立代理對象(底層機制是反射)
4> 調用代理對象的方法,實作功能增強
- 四個步驟中,有難點的是2、3兩步
- 增強器要實作InvocationHandler必須實作的invoke方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
return null;
}
- 建立代理對象的JDK相關API:
Proxy.newProxyInstance(
water.getClass().getClassLoader(),
water.getClass().getInterfaces(),
drinkHandler);
- 先進入
方法(隻保留核心語句):Proxy.newProxyInstance
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException{
//記住此處參數清單是目标類的類加載器、目标類的父接口、增強器對象
Class<?> cl = getProxyClass0(loader, intfs);
//此方法,能夠得到代理對象的類對象,是個核心方法,進入該方法
- 進入
方法:getProxyClass0(loader, intfs)
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
return proxyClassCache.get(loader, interfaces);
}
- 進入
方法(隻保留最核心語句):proxyClassCache.get(loader, interfaces)
public V get(K key, P parameter) {
// create subKey and retrieve the possible Supplier<V> stored by that subKey from valuesMap
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
//此處仍然沒有到最底層,記住key是目标類的類加載器,parameter數組記憶體放目标類的所有父接口
- 進入
方法(注意此處subKeyFactory是BiFunction接口類型的,要到它的實作類ProxyClassFactory中尋找核心方法apply):subKeyFactory.apply(key, parameter)
//這個是核心方法,涉及【代理類名稱】、【代理類位元組碼檔案】、【代理類對象的生成】
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 所有代理類名的字首
private static final String proxyClassNamePrefix = "$Proxy";
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
long num = nextUniqueNumber.getAndIncrement();
/*【代理類名稱】拼接:這就是為什麼代理類後面會有數字*/
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*【代理類位元組碼檔案】:這個位元組碼以數組的形式存在于記憶體,沒在磁盤盤上,
後續我們可以把這個位元組數組寫入磁盤,以便直覺檢視代理類的位元組碼檔案*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
/*【代理類對象的生成】:這是一個native方法,得到代理類的類對象,
就是類加載中把磁盤的位元組碼讀入方法區的那一步*/
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}
}
- 為了直覺感受到代理類,我們把代理類的位元組碼檔案輸出到磁盤上:
//把上文中:JDK動态代理執行個體中的代理類位元組碼檔案輸出到磁盤上:
public static void createProxyClass() throws IOException{
String proxyName = "$proxy0";
/*生成指定的代理類位元組碼:*/
Class<?>[] interfaces = Water.class.getInterfaces();
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
/*指定位元組碼存放路徑*/
String path = "E:\\IDEA_2020_2_4\\IdeaProjects\\springLearn\\aop-leadin\\src\\com\\cpf\\proxyClass\\";
File file = new File(path + proxyName + ".class");
if(!file.exists()){
file.createNewFile();
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(proxyClassFile);
fileOutputStream.flush();
fileOutputStream.close();
}
- 我們來解析一下這個代理類位元組碼:
import com.cpf.service.Drinkable;
import com.cpf.service.Washable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
/*代理類繼承了Proxy,唯一的用處是複用了Proxy中的InvocationHandler增強器對象,
但其實完全可以在代理類中出現增強器對象,這就是cglib的思路*/
/*代理類實作了目标類的所有父接口,這就是為什麼要求目标類一定要有接口,因為代理類已經有父類了,隻能實作接口*/
public final class $proxy0 extends Proxy implements Drinkable, Washable {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m0;
/*代理類的構造器,傳入增強器對象,這在Proxy.newProxyInstance()方法的後續代碼中會用到,
你或許已經發現,建立代理類的類對象隻使用了【目标類加載器】【目标類父接口】這兩個參數,
第三個參數【增強器】就是通過這個構造器指派給Proxy的屬性h的,由三個參數來形成代理對象*/
public $proxy0(InvocationHandler var1) throws {
super(var1);
}
/*這就是目标類中需要增強的兩個目标方法drink wash,是從兩個父接口中得到的資訊;
由動态綁定機制,我們在用代理對象調用方法時,實際上會執行這裡的drink方法,而不是目标類的drink方法*/
public final void drink() throws {
try {
/*super就是Proxy,h就是那第三個參數【增強器對象】,invoke就是我們在增強器中寫的方法;
第一個參數this,表明invoke的第一個參數就是代理對象
第二個參數m3,檢視後面的靜态代碼塊可知,就是通過反射獲得的drink的方法對象
第三個參數null,這是因為drink()方法沒有參數
*/
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void wash() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
/*系統順手幫忙生成了類中最常用的三個方法equals toString hasnCode*/
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
/**靜态代碼塊,使用反射,獲得目标類的目标方法,這些資訊,是通過第一個參數【目标類加載器】獲得的/
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.cpf.service.Drinkable").getMethod("drink");
m4 = Class.forName("com.cpf.service.Washable").getMethod("wash");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
-
至此,我們就搞清楚了代理類的類對象的來源,也弄明白了代理類的基本結構,
接下來繼續回到第一步中
的核心代碼,繼續分析:newProxyInstance()
@CallerSensitive
//記住這個Proxy類中的屬性,他就是增強器的類對象,用來獲得代理類的構造器對象
private static final Class<?>[] constructorParams = { InvocationHandler.class };
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Class<?> cl = getProxyClass0(loader, intfs);
//通過代理類的類對象得到構造器對象,就是那個需要傳入增強器h的構造器
final Constructor<?> cons = cl.getConstructor(constructorParams);
//由于Constructor類的newInstance方法時可變長參數清單,是以此處把增強器對象僞裝進一個數組中,
//實際上就是把這個增強器對象傳入代理類的構造器中來構造器代理對象...
return cons.newInstance(new Object[]{h});
}
- 至此,我們了解了【代理類的位元組碼】【代理類對象的由來】【代理類與invoke的互動】,接下來解析一下增強器中的invoke()方法:
public class DrinkHandler implements InvocationHandler{
private Object target = null;//目标對象,通過構造器傳入
/*目标對象必須傳入增強器,因為目标方法還必須由目标對象來執行,
增強器隻負責增強的功能*/
public DrinkHandler(Object target){
this.target = target;
}
/**
* @param proxy 回憶一下代理類位元組碼檔案中代碼: super.h.invoke(this, m3, (Object[])null);
* 這個super就是Proxy類,h就是它的InvocationHandler屬性,
* this就是代理類對象,是以此處的名字就是proxy,實至名歸.
* @param method 代理類位元組碼中靜态代碼塊:m3 = Class.forName("com.cpf.service.Drinkable").getMethod("drink");
* 可以知道這個就是反射得到的方法對象.
* @param args 由于drink()方法沒有參數清單,是以位元組碼檔案中是null,args代表了目标方法的參數類對象,
* 注意是參數類對象,不是參數對象,如果原參數是一個String,此處就應該是String.class
* @return 由于目标方法可能有傳回值,此處有必要設定一個傳回值,如此例中drink沒有傳回值,invoke就傳回null.
* @throws Throwable 目标方法的執行是通過反射,反射有可能找不到對應的方法,是以要抛出異常.
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println("身體内産生能量的化學反應要消耗水...");
method.invoke(target, args);
System.out.println("身體産生的廢物會溶解在水中排出體外...");
return null;//目标方法沒有傳回值的話,就傳回null
}
}
- 至此,動态代理底層分析完畢
5. 結語
- 動态代理是一種技術,更是一個思想,在不修改業務層代碼的前提下,增強業務層的功能,增強器改名換姓,搖身一變就形成了面向切面程式設計;
- 由于動态代理的代碼寫法很多,步驟也可以有變動,為了規範動态代理,就形成了aop(面向切面程式設計)技術,可以這樣說:aop是動态代理的規範;
- spring本身實作了aop技術,但是非常的備援和繁瑣,業内公認的aop實作架構是aspectJ,spring也支援aspectJ架構,隻是需要另外引入aspectJ的依賴;