一、代理模式
代理模式是Java常用的設計模式,其特徵是代理類與委託類(被代理類)有同樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類、以及事後處理消息等。代理類和委託類之間會存在關聯關係,代理類的對象本身並不真正實現服務,而是通過調用代理類對象特定的方法來提供服務。簡單的說,我們訪問實際對象的通過代理對象來訪問的,代理模式在訪問對象時引入一定程度的間接性,就是因爲這種間接性,所以可以附加多種用途!
舉個例子:
有一個班級,每個成員有不同的能力,比如繪畫、攝影、主持節目等,這個團隊有一個隊長,隊長負責與外界合作,人們尋找能主持節目的人直接找隊長就行,隊長本身並不提供主持節目的服務,隊長是讓團隊會主持節目的成員提供服務,但是隊長可以在提供主持節目服務的前前後後進行一些相關事宜。那麼這裏的隊長就是代理類,各個成員就是委託類;
代理分類:
靜態代理和動態代理;
二、靜態代理
1、靜態代理
是由程序員創建或特定工具生成源代碼,也就是在編譯時就已經將接口、委託類、代理類等確定下來,在程序運行之前。代理類的.class文件就已經生成。
2、靜態代理的簡單實現
根據上面的例子,我們將能主持節目的人稱爲支持人,我們將隊長稱爲隊長,主持人就是委託類,隊長就是代理類;
第一步:我們創建一個接口Person
此接口是主持人(委託類)和隊長(代理類)的公共接口,他們都有主持節目的行爲(只是隊長主持節目的行爲也是從主持人哪裏借來的,只是作爲主持人的代理,這種主持節目的行爲被附加在了隊長身上)
package com.zibo.design_mode.agency;
//這是代理類和被代理類的公共接口
public interface Person {
//主持節目
void host_a_program();
}
第二步:創建主持人類Compere
實現Person接口,具體實現主持節目的方法;
package com.zibo.design_mode.agency;
//主持人
public class Compere implements Person {
@Override
public void host_a_program() {
System.out.println("主持人主持了節目!");
}
}
第三步:創建隊長類Captain
實現Persion接口,其具體實現主持節目的方法是調用主持人主持節目的方法;
package com.zibo.design_mode.agency;
public class Captain implements Person {
private Person compere;
public Captain(Person compere) {
//只代理主持人
if(compere.getClass() == Compere.class){
this.compere = compere;
}
}
@Override
public void host_a_program() {
//隊長調用主持人主持節目的行爲
compere.host_a_program();
}
}
靜態代理測試類StaticProxyTest:
package com.zibo.design_mode.agency;
public class StaticProxyTest {
public static void main(String[] args) {
//創建主持人
Person compere = new Compere();
//創建隊長
Person captain = new Captain(compere);
//隊長調用主持人主持節目的行爲
captain.host_a_program();//主持人主持了節目!
}
}
3、綜述
這裏並沒有通過主持人直接跟人們對接,去提供主持節目的服務,而是由隊長作爲中間人(也就是主持人的代理人)去促進雙方的合作,隊長的作用是使雙方的合作過程更加順暢,對合作事宜進行優化,比如主持人在與人們達成合作之前需要隊長對其進行宣傳介紹等工作,在合作之後有需要隊長處理一些事宜,也就是代理類對委託類的一些功能進行增強!
三、動態代理
1、動態代理
代理類在程序運行時被創建的方式成爲動態代理。我們靜態代理的例子代理類是自己定義好的,在程序運行之前就已經便已完成。然而動態代理,代理類並不是在Java代碼中定義的,而是在運行時根據我們在Java代碼中的“指示”動態生成的。相比於靜態代理,動態代理的優勢在於可以很方便地對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。比如說,我們想要在每個代理類的前面都增加一個方法:
public void host_a_program() {
//在每個代理類前面都增加一個方法
beforeMethod();
//隊長調用主持人主持節目的行爲
compere.host_a_program();
}
這裏只有一個comere.host_a_program()方法,但是如果有很多方法,就要每個方法前面都加上前置的方法,就會很麻煩;所以我們就可以使用動態代理的方式來解決這個問題;
2、動態代理的簡單實現
在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,通過這個類和這個接口可以生成JDK動態代理類和動態代理對象;
創建一個動態代理對象的步驟:
第一步:創建一個InvocationHandler對象:
//創建一個與被代理對象相關聯的InvocationHandler
InvocationHandler compereHandler = new ComInvocationHandler(compere);
第二步:使用Proxy類的getProxyClass靜態方法生成一個動態代理類comProxyClass:
Class<?> comProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
第三步:獲得comProxyClass中一個帶InvocationHandler參數的構造器constructor:
Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
第四步:通過構造器constructor來創建一個動態實例stuProxy:
Person stuProxy = (Person) cons.newInstance(stuHandler);
簡化步驟:
可以通過Proxy類的newProxyInstances方法來簡化:
//創建一個與被代理對象相關聯的InvocationHandler
InvocationHandler comHandler = new MyInvocationHandler<Person>(com);
//創建一個代理對象comProxy,代理對象的每個執行方法都會替換執行Invocation中的invoke方法
Person comProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, comHandler);
3、簡單案例
主持人類Compere:
package com.zibo.design_mode.agency;
//主持人
public class Compere implements Person {
@Override
public void host_a_program() {
try {
//假設支持節目花了1秒時間
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("主持人主持了節目!");
}
}
計時工具類MonitorUtil:
package com.zibo.design_mode.agency;
public class MonitorUtil {
private static ThreadLocal<Long> tl = new ThreadLocal<>();
public static void start(){
tl.set(System.currentTimeMillis());
}
//結束時打印耗時
public static void finish(String methodName){
long finishTime = System.currentTimeMillis();
System.out.println(methodName + "方法耗時" + (finishTime-tl.get()) + "ms");
}
}
公共接口Person:
package com.zibo.design_mode.agency;
//這是代理類和被代理類的公共接口
public interface Person {
//主持節目
void host_a_program();
}
ComInvocationHandler類:
package com.zibo.design_mode.agency;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ComInvocationHandler implements InvocationHandler {
//InvocationHandler持有的被代理對象
Object target;
//帶參構造函數
public ComInvocationHandler(Object target) {
this.target = target;
}
/**
*
* @param proxy 動態代理的對象
* @param method 正在執行的方法
* @param args 調用目標方法時傳入的參數
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理執行" +method.getName() + "方法");
//代理過程中插入檢測方法,計算方法耗時
MonitorUtil.start();
Object result = method.invoke(target, args);
MonitorUtil.finish(method.getName());
return result;
}
}
測試類ProxyTest:
package com.zibo.design_mode.agency;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class ProxyTest {
public static void main(String[] args) {
//創建一個主持人對象——被代理對象
Person compere = new Compere();
//創建一個與被代理對象相關聯的InvocationHandler
InvocationHandler compereHandler = new ComInvocationHandler(compere);
//創建一個隊長來代理主持人,代理對象執行的每個方法都會替換執行Invocation中的invoke方法
Person captain = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(),new Class<?>[]{Person.class},compereHandler);
//代理執行方法
captain.host_a_program();
}
}
運行結果:
代理執行host_a_program方法
主持人主持了節目!
host_a_program方法耗時1001ms
說明:
上面說的動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。是因爲所有被代理執行的方法,都是通過在InvocationHandler中的invoke方法調用的,所以我們只要在invoke方法中統一處理,就可以對所有被代理的方法進行相同的操作了。例如,這裏的方法計時,所有的被代理對象執行的方法都會被計時,然而我只做了很少的代碼量。
動態代理的過程,代理對象和被代理對象的關係不像靜態代理那樣一目瞭然,清晰明瞭。因爲動態代理的過程中,我們並沒有實際看到代理類,也沒有很清晰地的看到代理類的具體樣子,而且動態代理中被代理對象和代理對象是通過InvocationHandler來完成的代理過程的,其中具體是怎樣操作的,爲什麼代理對象執行的方法都會通過InvocationHandler中的invoke方法來執行。帶着這些問題,我們就需要對java動態代理的源碼進行簡要的分析,弄清楚其中緣由。
四、動態代理原理分析
見https://www.cnblogs.com/gonjan-blog/p/6685611.html