代理設計模式

代理設計模式

傳送門java

  • 模擬計算面試時間
  • 建立一個接口
//面試
public interface InterView {
    void chatting();
}
複製代碼
  • 真實對象實現接口
public class Persion implements InterView{
    @Override
    public void chatting() {
        System.out.println("is chatting ...");
        try {
            //模擬面試時間
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
複製代碼
  • 用Thread.sleep()模擬面試的時間,那麼我若是想知道這個時間怎麼作
public class Persion implements InterView{
    @Override
    public void chatting() {
        Long start = System.currentTimeMillis();
        System.out.println("is chatting ...");
        try {
            //模擬面試時間
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Long end = System.currentTimeMillis();
        System.out.println("chatting time: " + (end - start) );
    }
}
複製代碼
  • 很簡單,在方法的先後捕捉當前時間,相減就出來了
  • 若是這個方法來自三方庫沒有改動源碼的權限,怎麼辦

  • 咱們可使用繼承,重寫他的方法
public class Persion2 extends Persion {
    @Override
    public void chatting() {
        long start = System.currentTimeMillis();
        super.chatting();
        long end = System.currentTimeMillis();
        System.out.println("chatting time : " + (end - start));
    }
}
複製代碼
  • 咱們也可使用聚合(注入),同時也實現面試的接口
public class Person3 implements InterView {

    private Person person;

    public Person3(Person person){
        this.person = person;
    }

    @Override
    public void chatting() {
        long start = System.currentTimeMillis();
        person.chatting();
        long end = System.currentTimeMillis();
        System.out.println("chatting time : " + (end - start));
    }
}
複製代碼
  • 這兩種方法均可以實現計算面試時間,哪一種好點呢
  • 咱們繼續增長需求,若是我還要在面試先後作記錄(打印日誌)呢,很簡單,繼承person2,並在先後添加打印日誌便可
  • 若是我要改變執行順序呢,先獲取時間,再打印日誌呢,能夠再用一個繼承類,重寫方法,這樣會致使無限擴增
  • 那前面提到的聚合(注入)不會有這樣的問題嗎,只要稍微改一下就能夠,把注入的Person修改成InterView接口
public class Person3 implements InterView {

    private InterView interView;
    
    public Person3(InterView interView){
        this.interView = interView;
    }

    @Override
    public void chatting() {
        long start = System.currentTimeMillis();
        interView.chatting();
        long end = System.currentTimeMillis();
        System.out.println("chatting time : " + (end - start));
    }
}
複製代碼
  • 爲了看的更清楚,將P3改成PersonProxy,用於獲取方法執行時間代理的意思,同時新建PersonLogProxy代理類用於打印日誌
public class PersonLogProxy implements Interview {
    private Interview interview;

    public PersonLogProxy(Interview interview) {
        this.interview = interview;
    }

    @Override
    public void chatting() {
        System.out.println("chatting start...");
        interview.chatting();
        System.out.println("chatting end...");
    }
}
複製代碼
  • 若是要先記錄日誌,再獲取面試時間,能夠這樣作
public class Test {
    public static void main(String[] args) {
        Person person = new Person();
        PersonLogProxy p1 = new PersonLogProxy(person);
        PersonProxy p2 = new PersonProxy(p1);
        p2.chatting();
    }

}
複製代碼
  • 若是需求變動,反過來,能夠這樣作
public class Test {
    public static void main(String[] args) {
        PersonProxy p3 = new PersonProxy(person);
        PersonLogProxy p4 = new PersonLogProxy(p3);
        p4.chatting();

    }
}
複製代碼

這裏會出現一個問題,從表現來看,聚合能夠實現靈活的執行順序,而繼承不能夠,爲何 面試

image

  • 繼承實際上是一種包裹關係,若是要改變順序,則須要建立新的模板來實現
  • 因爲聚合對象和代理對象哦都是先了interView接口,相似繼承的包裹關係,能夠經過傳入不一樣的interview來改變
  • 這是利用了java多態特性,其實全部的設計模式都或多或少的用到多態

靜態代理

在上面的PersonTimeProxy的chatting方法,咱們直接調用了interview().chatting,換而言之,PersonProxy代理傳入的interview對象,這就是典型的靜態代理實現設計模式

  • 問題: 若是須要對全部方法都執行這樣的操做,一樣的代碼至少須要重複多少次,同時也多少次建立代理類
    • 若是溶蝕代理多各種,依然致使類無限擴展
    • 若是類中有多個方法,須要反覆實現
  • 是否能夠用同一個代理類來代理任意對象呢,甚至代理的邏輯也能夠自定義

動態代理

jdk動態代理

  • 能夠傳入任意對象,即動態生成代理對象
  • 能夠傳入任意方法

image

  • 其實就是經過jdk自帶的Proxy.newProxyInstance()方法動態生成代理類,動態編譯,在經過反射建立對象並加載到內存中bash

  • 能夠查看一下Proxy->newProxyInstance()的源碼,代理類的動態建立經過InvocationHandler自定義dom

  • 咱們能夠看一下源碼中的InvocationHandler接口jvm

import java.lang.reflect.Method;

/**
 * proxy:指定動態生成的代理類
 * method:接口中傳入的全部方法對象
 * args:當前傳入方法的參數
 */
public interface InvocationHandler {
    void invoke(Object proxy, Method method,Object[] args);
}

複製代碼

使用:ide

  • 實現InvocationHandler接口
public class MyInvocationHandler implements InvocationHandler {

    //被代理對象,object類型
    private Object target;

    public MyInvocationHandler(Object target){
        this.target = target;
    }

    /**
    *proxy:指定動態生成的代理類,
    *method:接口中傳入的對象的全部方法
    *當前傳入方法的參數
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("chatting start ...");
        Object returnValue = method.invoke(target, args);
        System.out.println("chatting end");
        return returnValue;
    }

}
複製代碼
  • 測試
public class DynamicProxyTest {

    public static void main(String[] args) {
        Person person = new Person();
        MyInvocationHandler handler = new MyInvocationHandler(person);
        /**
         * 第一個參數指定代理類的類加載器,這裏傳入當前測試類加載器
         * 第二個參數是代理類須要實現的接口的實例類
         * 第三個參數是invocation handler,用來處理方法的調用,這裏傳入本身實現的handler
         */
        InterView proxyObject = (InterView) Proxy.newProxyInstance(DynamicProxyTest.class.getClassLoader()
                , person.getClass().getInterfaces(),
                handler);

        proxyObject.chatting();
    }
}
複製代碼

此時,整個方法的調用棧變成了這樣 測試

image

  • 簡而言之就是:ui

    • 真實的業務類須要實現業務接口,代理類經過反射獲取真實業務對象的類加載器,從而在內存中建立一個真實的代理對象(靜態代理手動擴展那個),代理類須要實現invocationhandler接口,重寫invoke方法,invoke方法三個參數,分別是代理的接口,代理的方法,和方法參數,在該方法裏對真實的方法進行包裝,實現動態代理
  • 和靜態代理的區別:靜態代理是本身去建立代理類對象,動態代理本質是在內存中根據傳入的參數,動態的生成一個代理類對象,經過生成的class文件反編譯能夠看到this


cglib 動態代理

cglib是針對類來實現代理的,原理是針對指定的業務類生成一個子類,並覆蓋其中的業務方法,實現代理,由於用的是繼承,因此不能代理final修飾的類

  • 實現MethodInterceptor接口,建立代理類
public class PersonCglib implements MethodInterceptor {

    //業務對象
    private Object target;
    public Object getInstance(Object target){
        this.target = target;
        //建立加強器
        Enhancer enhancer = new Enhancer();
        //指定要代理的類(指定生成子類的父類)
        enhancer.setSuperclass(this.target.getClass());
        //設置回調,對於代理類上全部方法的調用,都會調用callback,因此callback則須要實現intercept()方法進行攔截
        enhancer.setCallback(this);
        //包含三個步驟
        //生成源代碼
        //編譯成class文件
        //加載到jvm中,建立實例並返回
        return enhancer.create();
    }

    //實現方法回調
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("chatting start");
        //這個obj對象是cglib給咱們new出來的
        //cglib new出來的對象是被代理對象的子類(繼承了被代理類),繼承至關於間接持有父類的引用
        methodProxy.invokeSuper(o,objects);
        System.out.println("chatting end");
        return null;
    }
}
複製代碼
  • 使用
public static void main(String[] args) {
        Person person = new Person();
        PersonCglib cglib = new PersonCglib();
        InterView interView = (InterView) cglib.getInstance(person);

        interView.chatting();
    }
複製代碼
  • 基於字節碼,動態的建立須要代理的子類
  • 建立一個本來的業務對象,建立一個代理類對象,在代理類對象中實現MethodInterceptor接口,經過傳入本來的業務對象,設置要繼承的父類對象,並設置回調,返回一個加強對象,代理類對象實現intercept方法,傳入參數是被代理對象,攔截的方法,攔截的參數,原有業務類指向的代理對象,每次調用業務方法都會回調intercept方法,實現動態代理
相關文章
相關標籤/搜索