寫程式碼的過程中看到許多 Proxy 的類,知道是代理模式,也知道程式碼如何走下去。
寫程式碼的過程中看到許多 Proxy 的類,知道是代理模式,也知道程式碼如何走下去。但是對於代理模式的細節和原理卻不太懂。
通過網上的學習得知
『代理模式是 Java 中常見的一種設計模式,根據創建代理類的時間點分為靜態代理和動態代理。』
靜態代理#
靜態代理就是在編譯時就已經將介面,被代理類,代理類等確定下來。在程式運行之前,代理類的. class 文件就已經生成。
靜態代理模式最主要的就是有一個公共介面,一個具體的類,一個代理類,代理類持有具體類的實例,代為執行具體類實例方法。
靜態代理的例子#
根據我的想法編了一個小例子,作為明星肯定要出席活動,Jisoo 要出席香奈兒的活動,但是由於身體原因。現在有 Jennie 代為出席。假如就和 Jisoo 出席的效果一樣。
首先創建一個 Person 介面
public interface Person{
//出席活動
public void attendance();
}
然後是 Idol 的實現類:
public class Idol implements Person{
String name;
int age;
Idol(String n,int a){
this.name=n;
this.age=a;
}
//實現介面
@Override
public void attendance() {
System.out.print(this.name+"出席了Channl活動");
}
}
然後是 IdolProxy 代理類,持有 Idol 的對象。也和 Idol 對象一樣實現了 Person Interface
public class IdolProxy implements Person{
Idol idol;
public IdolProxy(Person stu){
if(stu.getClass()==Idol.class) {
this.idol=(Idol)stu;
}
}
@Override
public void attendance() {
idol.attendance();
}
}
接下來就是測試類
public static void main(String[] args) {
Person Jisoo=new Idol("Jisoo",18);
Person Jennie=new IdolProxy(Jisoo);
Jennie.attendance();
}
輸出結果為:
Jisoo出席了Channl活動
沒有通過 Jisoo 去參加活動,而是通過 Jennie 的代理去的,這就是代理模式;
有一個公共的介面(Person),一個實現類(Idol)和一個代理類(IdolProxy)都實現了介面,
代理類還包含一個實現類的實例對象。並且代理這個對象執行具體的方法。
代理模式就是在訪問實際對象時引入一定程度的間接性,因為這種間接性,可以附加多種用途。
這裡的間接性就是指不直接調用實際對象的方法,那麼我們在代理過程中就可以加上一些其他用途。比如 AOP 的一些功能
動態代理#
靜態代理的時候代理類是已經實現的,就是我們寫好的,而動態代理就是在程式運行時動態創建的代理類。事先沒寫代理類的實現。
主要的點就是在 Proxy.newProxyInstance (loader, interfaces, h); 這個方法,這個方法是 Proxy 下的靜態方法,而 Proxy 是 Java.lang.reflect 下的類
這個方法就相當於對上面靜態代理的代理類的一個抽象,他可以有我們程式碼指示去如何代理一個對象。下面通過上面的那個例子來簡單實現以下:
動態代理的例子#
我們要實現的仍然是 Jennie 去代理身體不適的 Jisoo 出席 Channal 的活動。我看了網上的大部分教程都是從上往下講,從建立介面到實現 Invocation 介面的類
從我自己的理解來看,是從下往上理解較容易理解一些。那就直接從測試的類開始:
//創建一個實例對象,這個對象是被代理的對象
Person Jisoo = new Idol("Jisoo",18);
//創建一個與代理對象相關聯的InvocationHandler,這個就相當於靜態代理的代理類idolProxy了 只不過比他牛逼,他含有所有的類的對象是一個Object
InvocationHandler idolHandler = new IdolInvocationHandler<Person>(Jisoo);
//創建一個代理對象Jennie來代理Jisoo,代理對象的每個執行方法都會替換執行Invocation中的invoke方法
Person Jennie = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, idolHandler);
//代理執行方法
Jennie.attendance();
第一步 Person Jisoo = new Idol (“Jisoo”,18); 不需多說了創建一個實現 Person 介面的實例化對象。
關鍵就是第二步 InvocationHandler idolHandler = new IdolInvocationHandler (Jisoo);
這裡面的 InvocationHandler 是 java.lang.reflect.InvocationHandler; 是 Java 提供的實現動態代理類的介面,我們需要實現這個介面:
public class IdolInvocationHandler<T> implements InvocationHandler{
//invocationHandler持有的被代理對象
T target;
public IdolInvocationHandler(T target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("11111");
method.invoke(target, args);
System.out.println("2222");
return null;
}
}
這個介面定義了 invoke 方法,invoke 就是援引,調用的意思。就是說所有被代理類的方法都通過這個方法調用。
如何獲取的被代理類的方法呢?就是通過 Java 的反射機制。invoke 方法有三個參數,第一個就是被代理的對象,第二個是方法對象,第三個就是參數
接下來就是 :
Person Jennie = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, idolHandler);
這就生成了一個代理的對象 Jennie, Proxy.newProxyInstance (loader, interfaces, h) 包含三個參數,第一個是介面的加載器,第二個是代理介面的列表
第三個就是上面實現 Invocation 介面的類 IdolInvocationHandler 的對象 idolHandler,這樣在執行 Jennie 的 attendance 方法的時候就會輸出:
11111
Jisoo出席了Channl活動
2222
這樣就實現了動態代理的功能。。注意:動態代理只能對介面進行代理,不能對類進行代理。究其原因就是 Java 只支持單繼承,只有通過介面實現多繼承的關係。
原理:
其實大概就是把介面複製出來,通過這些介面和類加載器,拿到這個代理類 cl。然後通過反射的技術複製拿到代理類的構造函數(這部分程式碼在 Class 類中的 getConstructor0 方法),最後通過這個構造函數 new 個一對象出來,同時用 InvocationHandler 綁定這個對象。
PS:
Person idolProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, (proxy,method,agrs)->{
if(method.getName()=="attendance") {
System.out.println("1111");
method.invoke(Jisoo, args);
System.out.println("2222");
return null;
}
return null;
});
在創建動態代理對象的那步還可以這麼寫,用 java8?的新特性 Lmbada 表達式來寫,就不用寫 Invocation 的實現類了。