1. 程式人生 > >java-動態代理學習筆記

java-動態代理學習筆記

代理模式
給某個物件提供一個代理物件,並由代理物件控制對於原物件的訪問,即客戶不直接操控原物件,而是通過代理物件間接地操控原物件。
其中代理可以分為兩種方式,分別是靜態代理和動態代理

靜態代理

關於靜態代理的一個UML圖

    大概的思想就是如果我想建立一個對RealSubject類進行代理的代理類,那麼我可以建立一個代理類Proxy,讓它實現和RealSubject同樣的介面或者同樣的函式,也就是實現Subject介面或者繼承Subject,這樣Proxy也就可以被當做為Subject類來使用,然後讓該Proxy類擁有一個RealSubject類的例項,在Proxy類的request()方法中再去呼叫RealSubject例項的request()方法和做一些其他的處理。

直接上程式碼吧

public class ProxyDemo {
    public static void main(String args[]){
        RealSubject subject = new RealSubject();
        Proxy p = new Proxy(subject);
        p.request();
    }
}

interface Subject{
    void request();
}

class RealSubject implements Subject{
    public void request(){
        System.out.println("request"
); } } class Proxy implements Subject{ private Subject subject; public Proxy(Subject subject){ this.subject = subject; } public void request(){ System.out.println("PreProcess"); subject.request(); System.out.println("PostProcess"); } }

動態代理

如果大量使用前面的靜態代理可能就會有人想抱怨了,“我靠,每次實現一個代理我都得去寫多一個類”,而且再考慮下以下這種場景:

假如我有3個類 AC,BC,CC 分別實現了(換成繼承關係也沒關係)A,B,C介面, 而且分別實現了fa(),fb(),fc()函式,然後我想通過代理來實現計算fa(),fb(),fc()的執行時間,在這裡就簡單認為在執行函式前加多個fp()函式吧,如果我是用靜態代理的話,那麼就意味著我需要分別寫多3個代理類,分別為AP,BP,CP類,而且都各自實現A,B,C介面,然後還得在各自代理的函式中加入同一句函式fp(),可見這樣實現多麼死板,這時候我們就需要動態代理來搞定這問題啦!

首先說下幾個詞的概念先

  • 委託類和委託物件:委託類是一個類,委託物件是委託類的例項。
  • 代理類和代理物件:代理類是一個類,代理物件是代理類的例項。

java實現動態代理有兩種:

  • JDK動態代理
  • cglib動態代理

JDK動態代理

考慮這麼一個例子,假如我有下面這些類

public interface HelloService {
    void sayHello();
}
public class HelloServiceImpl implements HelloService {
    public void sayHello(){
        System.out.println("hello world!");
    }
}
public class Main {
    public static void main(String[] args){
        HelloService helloService=new HelloServiceImpl();
        helloService.sayHello();
    }
}

首先執行結果應該為

hello world!

這時候我想通過代理才實現在輸出hello world!之前先輸出welcome yuan!,然後在輸出hello world!之後再輸出bye!,這時候我需要這麼寫:

public class MyProxyFactory implements InvocationHandler{

    //委託物件
    private Object target;

    //建構函式,在此傳入將要被代理的物件
    public MyProxyFactory(Object target){
        this.target=target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在執行委託物件的函式之前
        System.out.println("welcom yuan!");

        //執行委託物件的函式(target是委託物件,args是這個函式需要的形參)
        Object result=method.invoke(target,args);

        //在執行委託物件的函式之後
        System.out.println("bye!");
        return result;
    }


    //自己封裝返回一個代理物件例項
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

}

然後main函式改一下

public class Main {
    public static void main(String[] args){
        HelloService helloService=new HelloServiceImpl();
        MyProxyFactory myProxyFactory=new MyProxyFactory(helloService);
        HelloService helloService1Proxy= (HelloService) myProxyFactory.getProxyInstance();
        helloService1Proxy.sayHello();
    }
}

輸出結果為

welcom yuan!
hello world!
bye!

現在解釋下關於JDK動態代理用到的類

  1. java.lang.reflect.Proxy:這是生成代理類的主類,通過 Proxy 類生成的代理類都繼承了 Proxy 類。我們主要通過Proxy
  2. java.lang.reflect.InvocationHandler:需要自己定義一個實現該介面的類,我們動態生成的代理類需要完成的具體操作將在該介面的invoke方法內實現。 第一個引數是代理物件(表示哪個代理物件呼叫了method方法),第二個引數是 Method 物件(表示哪個方法被呼叫了),第三個引數是指定呼叫方法的引數。這個函式是在代理物件呼叫任何一個方法時都會呼叫的,方法不同會導致第二個引數method不同。如果你想對特定的函式進行處理,可以自己在invoke方法內自己做些判斷,或許還有其他的方法可以實現…

JDK動態代理的侷限性

因為 Java 的單繼承特性(每個代理類都繼承了 Proxy 類),只能針對介面建立代理類,不能針對類建立代理類。

cglib動態代理

使用JDK動態代理使用到一個Proxy類和一個InvocationHandler介面。
Proxy已經設計得非常優美,但是還是有一點點小小的遺憾之處,那就是它僅支援interface代理(也就是代理類必須實現介面),因為它的設計註定了這個遺憾。對於上面說到JDK僅支援對實現介面的委託類進行代理的缺陷,這個問題CGLIB給予了很好的補位,解決了這個問題,使其委託類也可是非介面實現類。
CGLIB內部使用到ASM,所以下面的例子需要引入asm-3.3.jar、cglib-2.2.2.jar

public class Hello {
    public void sayHello(){
        System.out.println("hello");
    }

    public void sayHi(){
        System.out.println("hi");
    }
}
public class MyCglibProxyFactory implements MethodInterceptor{

    //需要實現的介面函式
    //注意!這裡的o物件跟被代理物件例項不是同一個
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {


        //過濾那些由Object類繼承過來的方法
        //boolean flag=!(method.getDeclaringClass().getName().equals("java.lang.Object"));

        //或者檢查要攔截的方法的名字,如果是sayHello方法才進行攔截
        boolean flag=method.getName().equals("sayHello");
        if(flag==true){
            System.out.println("welcome yuan!");
        }

        //我們一般使用proxy.invokeSuper(obj,args)方法。這個很好理解,就是執行原始類的方法。還有一個方法proxy.invoke(obj,args),這是執行生成子類的方法。
      //如果傳入的obj就是子類的話,會發生記憶體溢位,因為子類的方法不挺地進入intercept方法,而這個方法又去呼叫子類的方法,兩個方法直接迴圈呼叫了。
        Object result=methodProxy.invokeSuper(o,args);

        if(flag==true){
            System.out.println("bybe");
        }

        return result;
    }

    /**
     * 返回代理物件例項
     * @param target 被代理的物件例項
     * @return
     */
    public Object getProxyInstance(Object target){
        //該類用於生成代理物件
        Enhancer enhancer=new Enhancer();
        //設定父類
        enhancer.setSuperclass(target.getClass());
        //設定回撥用物件為本身
        enhancer.setCallback(this);
        return enhancer.create();
    }
}
public class Main {

    public static void main(String[] args){
        Hello hello=new Hello();
        MyCglibProxyFactory myCglibProxyFactory=new MyCglibProxyFactory();
        Hello helloProxy=(Hello)myCglibProxyFactory.getProxyInstance(hello);
        helloProxy.sayHello();
        helloProxy.sayHi();
    }
}

輸出結果:

welcome yuan!
hello
bybe
hi

其中sayHello()方法得到處理,而sayHi()方法則沒有。

CGLib所建立的動態代理物件的效能比JDK所建立的代理物件效能高不少,大概10倍,但CGLib在建立代理物件時所花費的時間卻比JDK動態代理多大概8倍,所以對於singleton的代理物件或者具有例項池的代理,因為無需頻繁的建立新的例項,所以比較適合CGLib動態代理技術,反之則適用於JDK動態代理技術。另外,由於CGLib採用動態建立子類的方式生成代理物件,所以不能對目標類中的final,private等方法進行處理。所以,大家需要根據實際的情況選擇使用什麼樣的代理了。同樣的,Spring的AOP程式設計中相關的ProxyFactory代理工廠內部就是使用JDK動態代理或CGLib動態代理的,通過動態代理,將增強(advice)應用到目標類中。