1. 程式人生 > 其它 >03、Java進階--代理模式

03、Java進階--代理模式

代理

在Java中沒有委託的語言特性,只有通過代理設計模式來實現委託。

在代理設計模式中代理類和委託類都實現同樣的介面,代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等。

我們在訪問實際物件時,是通過代理物件來訪問的,代理模式就是在訪問實際物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。

Subject類,定義RealSubject和Proxy的共用介面,這樣就可以在任何使用RealSubject的地方都可以使用Proxy。

RealSubject類:定義Proxy所代表的真實實體。

Proxy類:儲存一個引用使得代理可以訪問實體並提供一個與Subject的介面相同的介面,這樣代理就可以用來替換實體。

靜態代理

靜態代理:由程式設計師建立或特定工具自動生成原始碼,也就是在編譯時就已經將介面,被代理類,代理類等確定下來。在程式執行之前,代理類的.class檔案就已經生成。

假如一個班的同學要向老師交班費,但是都是通過班長把自己的錢轉交給老師。這裡,班長就是代理學生上交班費,班長就是學生的代理。

首先,我們建立一個IPerson介面。這個介面就是學生(被代理類),和班長(代理類)的公共介面,他們都有上交班費的行為。這樣,學生上交班費就可以讓班長來代理執行。

public interface IPerson {
    // 上交班費
    void giveMoney();
}

Student類實現IPerson介面。Student可以具體實施上交班費的動作。

public class Student implements IPerson{
    private String name;

    public Student(String name) {
        this.name = name;
    }

    @Override
    public void giveMoney() {
        System.out.println(name + "上交班費50元");
    }
}

接下來是班長類,也就是學生的代理類,它也實現了IPerson介面,所以它持有學生物件,他就可以代理學生類來執行上交班費的行為。

public class StudentProxy implements IPerson{
    Student student;

    public StudentProxy(IPerson student) {
        if (student.getClass() == Student.class){
            this.student = (Student) student;
        }
    }

    @Override
    public void giveMoney() {
        student.giveMoney();
    }
}

下面測試一下,看如何使用代理模式:

public class Main {
    public static void main(String[] args) {
        // /被代理的學生legend,他的班費上交有代理物件monitor(班長)完成
        IPerson legend = new Student("Legend");
        //生成代理物件,並將legend傳給代理物件
        StudentProxy studentProxy = new StudentProxy(legend);
        //班長代理上交班費
        studentProxy.giveMoney();
    }
}

代理模式最主要的就是有一個公共介面(IPerson),一個具體的類(Student),一個代理類(StudentsProxy),代理類持有具體類的例項,代為執行具體類例項方法。

代理模式就是在訪問實際物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。

加入班長在幫張三上交班費之前想要先反映一下張三最近學習有很大進步,通過代理模式很輕鬆就能辦到:

public class StudentProxy implements IPerson{
    Student student;

    public StudentProxy(IPerson student) {
        if (student.getClass() == Student.class){
            this.student = (Student) student;
        }
    }

    @Override
    public void giveMoney() {
        System.out.println(student.getName() + "最近學習有進步!");
        student.giveMoney();
    }
}

在Spring中的面向切面程式設計(AOP),我們能在一個切點之前執行一些操作,在一個切點之後執行一些操作,這個切點就是一個個方法。這些方法所在類肯定就是被代理了,在代理過程中切入了一些其他操作。

動態代理

代理類在程式執行時建立的代理方式被成為動態代理。

相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函式進行統一的處理,而不用修改每個代理類中的方法。

比如說,想要在每個代理的方法前都加上一個處理方法:

 public void giveMoney() {
      //呼叫被代理方法前加入處理方法
      beforeMethod();
      stu.giveMoney();
}

假設有很多個giveMoney方法,如果每個都修改程式碼則很麻煩,使用動態代理的話則可以無汙染無侵入的對方法進行加強操作。

動態代理實現

在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler介面,通過這個類和這個介面可以生成JDK動態代理類和動態代理物件。

建立一個動態代理物件步驟,具體程式碼見後面:

1、建立一個InvocationHandler物件

//建立一個與代理物件相關聯的InvocationHandler
 InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);

2、使用Proxy類的getProxyClass靜態方法生成一個動態代理類stuProxyClass

 Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});

3、獲得stuProxyClass 中一個帶InvocationHandler引數的構造器constructor

Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);

4、通過構造器constructor來建立一個動態例項stuProxy

Person stuProxy = (Person) cons.newInstance(stuHandler);

一個動態代理物件就建立完畢,當然,上面四個步驟可以通過Proxy類的newProxyInstances方法來簡化:

//建立一個與代理物件相關聯的InvocationHandler
  InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
//建立一個代理物件stuProxy,代理物件的每個執行方法都會替換執行Invocation中的invoke方法
  Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

下面基於上面的靜態代理的例項進行修改,班長需要幫學生代交班費:

public interface IPerson {
    // 上交班費
    void giveMoney();
}

建立需要被代理的實際類:

public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }
    
    @Override
    public void giveMoney() {
        try {
          //假設數錢花了一秒時間
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       System.out.println(name + "上交班費50元");
    }
}

再定義一個檢測方法執行時間的工具類,在任何方法執行前先呼叫start方法,執行後呼叫finsh方法,就可以計算出該方法的執行時間,這也是一個最簡單的方法執行時間檢測工具。

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");
    }
}

建立StuInvocationHandler類,實現InvocationHandler介面,這個類中持有一個被代理物件的例項target。InvocationHandler中有一個invoke方法,所有執行代理物件的方法都會被替換成執行invoke方法。

public class StudentInvocationHandler<T> implements InvocationHandler {
    T target;

    public StudentInvocationHandler(T target) {
        this.target = target;
    }

    @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;
    }
}

做完上面的工作後,我們就可以具體來建立動態代理物件:

public class Main {
    public static void main(String[] args) {
        //建立一個例項物件,這個物件是被代理的物件
        IPerson legend = new Student("legend");
        //建立一個與代理物件相關聯的InvocationHandler
        InvocationHandler invocationHandler = new StudentInvocationHandler<>(legend);
        //建立一個代理物件stuProxy來代理legend,代理物件的每個執行方法都會替換執行Invocation中的invoke方法
        IPerson studentProxy = (IPerson) Proxy.newProxyInstance(IPerson.class.getClassLoader(),
                new Class<?>[]{IPerson.class}, invocationHandler);
        //代理執行上交班費的方法
        studentProxy.giveMoney();
    }
}

所有執行代理物件的方法都會被替換成執行invoke方法,也就是說,最後執行的是StuInvocationHandler中的invoke方法。

注意:java動態代理只能對介面進行代理,Java的繼承機制註定了這些動態代理類們無法實現對class的動態代理。