動態代理(一)java動態代理應用介紹、原理簡介
java動態代理的應用介紹、原理簡介
嘚不嘚
好久沒有更新blog了,理論只有應用起來才能有更加深刻的體會。雖然同類的部落格到處都是,但是每個人對同一個技術的理解是不一樣的,每個人注重的細節也不太一樣,不管有沒有人看,寫部落格只是作為個人的一個習慣和自我的學習的總結過程。
概述
- 作用:在不變原有功能的基礎上增加新的功能。
- 應用場景:AOP,日誌的輸出,事務。
- 技術前提:為了更好的理解動態代理,要熟悉java反射。
應用介紹
- 為了方便理解代理宣告幾個變數:
- 代理類入口:實現invocationHandler的類就是代理類的入口,其實僅僅是代理類的,而不是真正的代理類。他的作用:建立真正的代理類,執行被代理類的要執行的方法和呼叫功能增強的方法。
- 被代理類:需要被代理的介面。
- 代理類:真實的代理類,是在程式碼中無法直接看到的,對被代理類方法的呼叫是從代理類呼叫開始的。
- 代理實現(程式碼我是用我參考的博主的):
- 業務定義介面:也就是被代理類實現的介面
public interface ICook {
void dealWithFood();
void cook();
}
- 被代理類:實現業務定義介面,同時該類是需要功能增強的類
public class CookManager implements ICook { @Override public void dealWithFood() { System.out.println("food had been dealed with"); } @Override public void cook() { System.out.println("cook food"); } }
- 代理類入口:該類的作用是建立真實的代理類,同時完成功能的增強。
public class DynamicProxyHandler implements InvocationHandler{ Object realCookManager; DynamicProxyHandler(ICook realCookManager){ this.realCookManager = realCookManager; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("invoke start"); System.out.println(method.getName()); method.invoke(realCookManager,args); System.out.println("invoke end"); return null; } }
- 測試類:
public class Main {
public static void main(String[] args){
CookManager cookManager = new CookManager();
DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(cookManager);
ICook iCook =(ICook)Proxy.newProxyInstance(dynamicProxyHandler.getClass().getClassLoader(),cookManager.getClass().getInterfaces(), dynamicProxyHandler);
//列印一下代理類的類名
System.out.println(iCook.getClass().getName());
iCook.dealWithFoot();
iCook.cook();
}
}
這裡的程式碼我是使用其他博主的程式碼,因為我每太搞明白,真實代理類是怎麼搞來的,下面貼出我的代理類入口類程式碼:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JavaProxy implements InvocationHandler {
/**
* 被代理類的物件
*/
private Object proxyObj;
/**
* Proxy 動態代理類動態建立代理物件
* newProxyInstance() ->建立代理物件
*
* 僅僅提供一個代理類的入口,建立一個繼承Proxy,實現被代理的類的所有介面,
* 代理類會實現介面中定義的所有方法,
* @param proxyObj 被代理類的物件
* @param <T>
* @return
*/
public <T> T getProxyObj(Object proxyObj) {
this.proxyObj = proxyObj;
/**
* 被代理類的類載入器,被代理類實現的介面,實現invocationHandler的代理物件
*/
Object o = Proxy.newProxyInstance(proxyObj.getClass().getClassLoader(), proxyObj.getClass().getInterfaces(), this);
return (T) o;
}
/**
* @param proxy 代理類的真實物件,
* @param method 呼叫真實物件的某個方法的method物件
* @param args 呼叫方法的引數
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before,,,,,");
System.out.println("proxy method......");
Method[] methods = proxy.getClass().getDeclaredMethods();
// Lists.newArrayList(methods).forEach(mm -> System.out.println(mm.getName()));
System.out.println();
System.out.println("被代理類proxy="+proxyObj.getClass().getName());
System.out.println("代理類proxy="+proxy.getClass().getName());
System.out.println("代理類->father="+proxy.getClass().getSuperclass().getName());
System.out.println("代理類->father interface=");
Class [] classes = proxy.getClass().getInterfaces();
for (int i = 0; i < classes.length; i++) {
System.out.println(classes[i].getName());
}
System.out.println("method=" + method);
Object o = method.invoke(proxyObj, args);
System.out.println((String)o);
//Object o1 = method.invoke(proxy, args);
System.out.println("after,,,,,");
return null;
}
}
- 我的入口代理類說明:引數比較多剛開始看的我也很迷惑,理解引數有助於理解代理類是如何實現的和使用的。先提及一下反射,反射獲取某個型別(也就是類)的方法、屬性或者完成對某個型別方法的呼叫是通過對該型別的位元組碼檔案操作進行呼叫的。java動態代理生成了真實代理類的.class檔案,通過對.class檔案的操作完成對真實代理類方法的呼叫。
擴:.java->.class,.class檔案是位元組碼檔案,是JVM可以識別的檔案,該檔案內容不是JVM的執行指令,JVM執行的指令是將.class檔案解釋之後的指令。
-
getProxyObj方法:生成代理類真實物件的方法。該方法的引數proxyObj型別Object ,該類是真實的需要被代理的類,注意這個不是介面類,而是真實的物件類,該物件的真實型別決定了呼叫那個類的方法(多型)。例如:A介面是業務介面類,B、C是A介面的實現類,代理類中傳入的真實型別是B的物件還是C的物件決定了呼叫的方法。
-
Proxy.newProxyInstance:生成代理類的方法。該方法的引數
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
。引數依次是類載入器,被代理類實現的介面,代理類入口。注意這個代理類的入口物件,最後能呼叫
JavaProxy.invoke()
方法就是該引數決定的。 -
invoke:在該方法中完成了對代理類功能的增強。該方法的引數:
(Object proxy, Method method, Object[] args)
引數依次是代理類的真實物件,被呼叫的方法和呼叫方法的引數。 -
遇到的問題:起初我是這麼寫的
method.invoke(proxy, args)
方法一直就一執行下去,根本停不下來,這樣換一下好了method.invoke(proxyObj, args)
,它倆之間的區別是一個用真實的代理類proxy
物件,另一個使用被代理類的物件proxyObj
。出現死迴圈的主要原因,真實的代理類自己對自己的方法進行呼叫當然會出現死迴圈。想知道invoke中 的各個物件的含義,可以通過反射獲取其名稱。
原理介紹
- 直接看博主生成的真實的代理程式碼:就是這段程式碼我不知道怎麼搞出來的。。。
public final class $Proxy0 extends Proxy implements ICook {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} 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 void cook() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void dealWithFoot() throws {
try {
super.h.invoke(this, m4, (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)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.company.ICook").getMethod("cook", new Class[0]);
m4 = Class.forName("com.company.ICook").getMethod("dealWithFoot", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
如果對這個類怎麼產生的感興趣自己研究下哈,看了這個類我還是比較困惑,就算生成了這個真實的代理類,那麼究竟是如何完成對這個DynamicProxyHandler.invoke()
方法進行呼叫的呢?看兩個地方:public $Proxy0(InvocationHandler var1) throws { super(var1); }
super.h.invoke(this, m4, (Object[])null);
。首先DynamicProxyHandler
這個類實現了InvocationHandler這個介面,通過構造方法完成了物件的初始化,然後看``super.h.invoke`,如果h的真實物件是DynamicProxyHandler物件的話,那麼一切就解決了。這兩個super指的物件都是在Proxy代理物件中的,java動態代理其實就是通過反射對java的真實代理類進行呼叫,關鍵在於生成了一個新的.class檔案。
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){
//所有被實現的業務介面
final Class<?>[] intfs = interfaces.clone();
//尋找或生成指定的代理類
Class<?> cl = getProxyClass0(loader, intfs);
//通過反射類中的Constructor獲取其所有構造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
//通過Constructor返回代理類的例項
return cons.newInstance(new Object[]{h});
}
cl是生成的真實代理類,也就是$Proxy0
,通過cons.newInstance(new Object[]{h})
完成對類Proxy
中屬性protected InvocationHandler h;
的賦值,真實代理類呼叫父類的構造方法是protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; };
下一篇介紹CGLIB代理,如果有問題各位大神多多指教,說的比較亂,思路我也不太很清晰,有問題請大神不吝賜教。
參考地址:https://www.jianshu.com/p/23d3f1a2b3c7