Java動態代理與在Android的應用
一、前言
1.1、什麼是代理?
大道理上講代理是一種軟體設計模式,目的地希望能做到程式碼重用。具體上講,代理這種設計模式是通過不直接訪問被代理物件的方式,而訪問被代理物件的方法。這個就好比 A---->B—>C 這種模式。A可以不通過直接與C對話的情況下,而通過B與其產生間接對話。
Java動態代理之所以叫做動態,因為它能避免傳統代理模式實現中人工一個一個的將java函式轉發過去,而是能夠讓程式碼自動做到這一點,這樣代理類的程式碼是和業務無關的,不會因為業務類的方法增多而逐漸龐大。使程式碼更易維護更易修改,實現自動化搬磚。
1.2、什麼情況下使用動態代理?
1、需要對較難修改的類方法進行功能增加。
2、RPC即遠端過程呼叫,通過動態代理的建立一箇中間人進行通訊。
3、實現切面程式設計(AOP)可以採用動態代理的機制來實現。
二、靜態代理和動態代理
根據載入被代理類的時機不同,將代理分為靜態代理和動態代理。編譯時就確定了被代理的類是哪一個,那麼就可以直接使用靜態代理;執行時才確定被代理的類是哪個,那麼可以使用類動態代理。
2.1、靜態代理
我們先建立一個介面,Java API代理機制求被代理類必須要實現某個介面,對於靜態代理方式 代理類 也要實現和 被代理類 相同的介面;
定義一個介面,這個介面定義了被代理類需要實現的功能:
public interface Subject {
public void sayGoodBye();
public void sayHello(String str);
}
定義被代理類(原來功能類)並實現被代理類的功能邏輯:
public class RealSubject implements Subject {
@Override
public void sayGoodBye() {
System.out.println("RealSubject sayGoodBye");
}
@Override
public void sayHello(String str) {
System.out.println("RealSubject sayHello " + str);
}
}
定義靜態代理類(功能增加類),這個代理類也必須要實現和被代理類相同的Subject介面,便於對原有功能的增強:
public class ProxySubject implements Subject {
private Subject subject;
public ProxySubject(Subject subject) {
this.subject = subject;
}
@Override
public void sayGoodBye() {
//代理類,功能的增強
System.out.println("ProxySubject sayGoodBye begin");
//在代理類的方法中 間接訪問被代理物件的方法
subject.sayGoodBye();
System.out.println("ProxySubject sayGoodBye end");
}
@Override
public void sayHello(String str) {
//代理類,功能的增強
System.out.println("ProxySubject sayHello begin");
//在代理類的方法中 間接訪問被代理物件的方法
subject.sayHello(str);
System.out.println("ProxySubject sayHello end");
}
}
客戶端程式碼:
public static void main(String[] args) {
//被代理的物件,某些情況下 我們不希望修改已有的程式碼,我們採用代理來間接訪問
RealSubject realSubject = new RealSubject();
//代理類物件
ProxySubject proxySubject = new ProxySubject(realSubject);
//呼叫代理類物件的方法
proxySubject.sayGoodBye();
System.out.println("******");
proxySubject.sayHello("Test");
}
測試程式碼輸出:
ProxySubject sayGoodBye begin
RealSubject sayGoodBye
ProxySubject sayGoodBye end
******
ProxySubject sayHello begin
RealSubject sayHello Test
ProxySubject sayHello end
靜態代理看起來是比較簡單的,它是運用原理跟裝飾設計模式類似,Subject介面相當於一個抽象構建Component ,被代理類RealSubject相當於一個具體構建ConcreteComponent,而代理類ProxySubject則相當於裝飾角色(Decorator)和具體裝飾角色(ConcreteDecorator)的結合。不管是靜態代理和裝飾設計模式其實都是為了對原有功能的增強 ,遮蔽或改變。
靜態代理(傳統代理模)的實現方式比較暴力直接,需要將所有被代理類的所有方法都寫一遍,並且一個個的手動轉發過去。在維護被代理類的同時,作為java碼工還需要同時維護代理類的相關程式碼,實在是累心。因此就需要我們的動態代理登場了,通過使用動態代理,動態代理能夠自動將代理類的相關方法轉發到被代理類。
2.2、動態代理
在java的動態代理機制中,有兩個重要的類或介面,一個是 InvocationHandler(Interface)、另一個則是Proxy(Class),這一個類和介面是實現我們動態代理所必須用到的。
InvocationHandler:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
每一個代理實類例的invocation handler都要實現InvocationHandler這個介面。並且每個代理類的例項都關聯到了一個handler,當我們通過代理物件呼叫一個方法的時候,這個方法的呼叫就會被轉發為由InvocationHandler這個介面的 invoke 方法來進行呼叫。
- proxy:指代生成的代理物件;
- method:指代的是我們所要呼叫真實物件的某個方法的Method物件;
- args:指代的是呼叫真實物件某個方法時接受的引數;
我們來看看Proxy這個類,這個類的作用就是用來動態建立一個代理物件的類,它提供了許多的方法,但用的最多的就是 newProxyInstance 這個方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
- loader:一個ClassLoader物件,定義了由哪個ClassLoader物件來對生成的代理物件進行載入
- interfaces:一個Interface物件的陣列,表示的是我將要給我需要代理的物件提供一組什麼介面,如果我提供了一組介面給它,那麼這個代理物件就宣稱實現了該介面(多型),這樣我就能呼叫這組介面中的方法了
- h:一個InvocationHandler物件,表示的是當我這個動態代理物件在呼叫方法的時候,會關聯到哪一個InvocationHandler物件上。
通過上面的需要傳入介面的引數可以看出,JDK動態代理需要藉助介面來實現,如果我們要代理的物件功能沒有抽成任何介面,那麼我們就無法通過JDK動態代理的方式來實現。
好了,在介紹完這兩個介面(類)以後,我們來通過一個例項來看看我們的動態代理模式是什麼樣的。首先我們定義了一個Subject型別的介面,為其聲明瞭兩個方法,這兩個方法表示被代理類需要實現的功能:
public interface Subject {
public void sayGoodBye();
public void sayHello(String str);
}
接著,定義了一個類來實現這個介面,這個類就是我們的真實物件(被代理類),RealSubject類:
public class RealSubject implements Subject {
@Override
public void sayGoodBye() {
System.out.println("RealSubject sayGoodBye");
}
@Override
public void sayHello(String str) {
System.out.println("RealSubject sayHello " + str);
}
}
下一步,我們就要定義一個InvocationHandler了,相當於一個代理處理器。前面說個,每一個動態代理類例項的invocation handler 都必須要實現 InvocationHandler 這個介面:
public class SubjectInvocationHandler implements InvocationHandler {
//這個就是我們要代理的真實物件
private Object subject;
//構造方法,給我們要代理的真實物件賦初值
public SubjectInvocationHandler(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
//在代理真實物件前我們可以新增一些自己的操作
System.out.println("before Method invoke");
System.out.println("Method:" + method);
//當代理物件呼叫真實物件的方法時,其會自動的跳轉到代理物件關聯的handler物件的invoke方法來進行呼叫
method.invoke(subject, args);
//在代理真實物件後我們也可以新增一些自己的操作
System.out.println("after Method invoke");
return null;
}
}
SubjectInvocationHandler並不是真正的代理類,而是用於定義代理類需要擴充套件、增強那些方法功能的類。在invoke函式中,對代理物件的所有方法的呼叫都被轉發至該函式處理。在這裡可以靈活的自定義各種你能想到的邏輯。
最後,來看看我們的Client類:
public static void main(String[] args) {
//被代理類
Subject realSubject = new RealSubject();
//我們要代理哪個類,就將該物件傳進去,最後是通過該被代理物件來呼叫其方法的
SubjectInvocationHandler handler = new SubjectInvocationHandler(realSubject);
//生成代理類
Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(), handler);
//輸出代理類物件
System.out.println("Proxy : "+ subject.getClass().getName());
System.out.println("Proxy super : "+ subject.getClass().getSuperclass().getName());
System.out.println("Proxy interfaces : "+ subject.getClass().getInterfaces()[0].getName());
//呼叫代理類sayGoodBye方法
subject.sayGoodBye();
System.out.println("--------");
//呼叫代理類sayHello方法
subject.sayHello("Test");
}
輸出結果:
Proxy : com.sun.proxy.$Proxy0
Proxy super : java.lang.reflect.Proxy
Proxy interfaces : com.company.ha.Subject
before Method invoke
Method:public abstract void com.company.ha.Subject.sayGoodBye()
RealSubject sayGoodBye
after Method invoke
--------
before Method invoke
Method:public abstract void com.company.ha.Subject.sayHello(java.lang.String)
RealSubject sayHello Test
after Method invoke
與靜態代理相比,動態代理具有如下的優點:
1、代理轉發的過程自動化了,實現自動化搬磚;
2、代理類的程式碼邏輯和具體業務邏輯解耦,與業務無關;
我們首先來看看 $Proxy0 這東西,這個東西就是真正的代理類物件,我們定義SubjectInvocationHandler類則是用於新增對代理類的功能擴充套件!而 $Proxy0類繼承java.lang.reflect.Proxy類 並實現Subject介面 ,因此它的類宣告方式如下:
public class $Proxy0 extends Proxy implements Subject
同時我們一定要記住,通過 Proxy.newProxyInstance 建立的代理物件是在jvm執行時動態生成的一個物件,它並不是我們的InvocationHandler型別,也不是我們定義的那組介面的型別,而是在執行是動態生成的一個物件,並且命名方式都是這樣的形式,以$開頭,proxy為中,最後一個數字表示物件的標號。
如果我們定義的方法有返回值,那麼可以通過invoke中把該方法的返回值進行返回,因為返回值的物件是Object,所以支援返回值為空(void)的返回。
@Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
//直接返回呼叫物件的返回值
return method.invoke(subject,args);
}
那麼在呼叫代理類該方法的時候就能獲取其返回值了。
當然我們可以針對某些方法的返回值進行加工再進行返回,比如我們假設public int sayGoodBye();方法是具有返回值的,那麼我們希望在呼叫代理類的 sayGoodBye()方法的時候,返回值總能在原有的基礎上+10,那麼invoke中可以這麼寫:
@Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
if (method.getName().equals("sayGoodBye")) {//在呼叫sayGoodBye方法的時候 對返回值進行處理
int result = (int) method.invoke(subject, args);
return result + 10;
} else {//其他方法一律不處理
return method.invoke(subject, args);
}
}
客戶端呼叫:
Subject subject = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
int result = subject.sayGoodBye();
System.out.println("reslut "+result);
客戶端獲得result的值就總是對原有返回值基礎上+10的返回值了。
三、動態代理原始碼分析
動態類Proxy的是通過newProxyInstance方法來生成的:
Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
因此我們從newProxyInstance來入手,為了更好的理解其原理,以下是精簡後的程式碼:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){
//所有被實現的業務介面
final Class<?>[] intfs = interfaces.clone();
//1、尋找或生成指定的代理類:com.sun.proxy.$ProxyX
Class<?> cl = getProxyClass0(loader, intfs);
//通過反射類中的Constructor獲取其所有構造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
//2、用構造方法建立代理類com.sun.proxy.$ProxyX的例項,並傳入InvocationHandler引數
return cons.newInstance(new Object[]{h});
}
上面的程式碼主要實現兩個操作:1、生成ProxyX 的Class類物件 ,2、通過該Class物件建立對應的例項,並傳入InvocationHandler參與構造。
生成ProxyX的Class類物件的操作在getProxyClass0方法中:
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//...
return proxyClassCache.get(loader, interfaces);
}
proxyClassCache會快取所有的代理類,如果快取中有這個業務代理類,則會從快取中取出,否則從ProxyClassFactory中生成。ProxyClassFactorys是Proxy的靜態內部類,而ProxyClassFactory中的apply方法就是主要生成Class物件的地方:
Proxy$ProxyClassFactory:
/**
* A factory function that generates, defines and returns the proxy class given
* the ClassLoader and array of interfaces.
*/
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// next number to use for generation of unique proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
...
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = "$Proxy" + ".";
}
// nextUniqueNumber是一個靜態類每次生成一個代理類都會自增
long num = nextUniqueNumber.getAndIncrement();
//生成後的類名為com.sun.proxy.$ProxyX
String proxyName = proxyPkg + proxyClassNamePrefix + num;
//傳入介面、類名,生成了我們要的位元組碼形式的代理類
//Generate the specified proxy class.
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
//defineClass0是個native方法,生成二進位制資料對應的Class類物件
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
}
}
真正核心生成位元組碼的方法是ProxyGenerator.generateProxyClass方法,這裡就不深究內部是如何生成的,由於ProxyX是以位元組碼形式存在於記憶體中,我們無法看到其全貌,因此我們可以仿ProxyGenerator.generateProxyClass方法的方式生成其class位元組碼,檢視其全貌:
//真實物件
Subject realSubject = new RealSubject();
//生成代理類
byte[] proxyClassFile = ProxyGenerator.generateProxyClass("MyName", realSubject.getClass().getInterfaces());
//儲存在本地檔案中
try (FileOutputStream fis = new FileOutputStream(new File("./myname.class"))){
fis.write(proxyClassFile);
} catch (Exception e) {
e.printStackTrace();
}
開啟myname.class檔案,最終代理類$ProxyX類是這樣的:
public final class MyName extends Proxy implements Subject {
private static Method m1;
private static Method m4;
private static Method m2;
private static Method m3;
private static Method m0;
public MyName(InvocationHandler var1) throws {
super(var1);
}
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 void sayHello(String var1) throws {
try {
super.h.invoke(this, m4, 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 void sayGoodBye() throws {
try {
super.h.invoke(this, m3, (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"));
m4 = Class.forName<