設計模式--代理模式(動態代理)
代理模式是常用的java設計模式,他的特徵是代理類與委託類有同樣的介面,代理類主要負責為委託類預處理訊息、過濾訊息、把訊息轉發給委託類,以及事後處理訊息等。代理類與委託類之間通常會存在關聯關係,一個代理類的物件與一個委託類的物件關聯,代理類的物件本身並不真正實現服務,而是通過呼叫委託類的物件的相關方法,來提供特定的服務。簡單的說就是,我們在訪問實際物件時,是通過代理物件來訪問的,代理模式就是在訪問實際物件時引入一定程度的間接性,因為這種間接性,可以附加多種用途。
在Spring中的面向切面程式設計(AOP),我們能在一個切點之前執行一些操作,在一個切點之後執行一些操作,這個切點就是一個個方法。這些方法所在類肯定就是被代理了,在代理過程中切入了一些其他操作。
在java的動態代理機制中,有兩個重要的類或介面,一個是 InvocationHandler(Interface)、另一個則是 Proxy(Class),這一個類和介面是實現我們動態代理所必須用到的。首先我們先來看看java的API幫助文件是怎麼樣對這兩個類進行描述的:
InvocationHandler:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance. Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
每一個動態代理類都必須要實現InvocationHandler這個介面,並且每個代理類的例項都關聯到了一個handler,當我們通過代理物件呼叫一個方法的時候,這個方法的呼叫就會被轉發為由InvocationHandler這個介面的 invoke 方法來進行呼叫。我們來看看InvocationHandler這個介面的唯一一個方法invoke方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
程式中解釋這三個引數分別代表什麼。
接下來我們來看看Proxy這個類:
Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.
Proxy這個類的作用就是用來動態建立一個代理物件的類,它提供了許多的方法,但是我們用的最多的就是newProxyInstance這個方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.
這個方法的作用就是得到一個動態的代理物件,程式中解釋這三個引數分別代表什麼。
在介紹完這兩個介面(類)以後,我們來通過一個例項來看看我們的動態代理模式是什麼樣的:
首先我們定義了一個Person介面,宣告一個無參方法和一個帶參方法
package dynamic;
public interface Person {
void giveMoney(String name);
void Money();
}
接著定義一個類來實現這個介面,這個類就是被代理物件,Student類
package dynamic;
public class Student implements Person {
@Override
public void giveMoney(String name) {
System.out.println(name+"同學已經班費100");
}
@Override
public void Money() {
System.out.println(".........");
}
}
下一步,我們就要定義一個動態代理類了,前面說個,每一個動態代理類都必須要實現 InvocationHandler 這個介面,因此我們這個動態代理類也不例外:
package dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxy implements InvocationHandler{
//這個就是我們要代理的真實物件
private Object stu;
//構造方法,給我們要代理的真實物件賦初值
public DynamicProxy(Object stu) {
this.stu = stu;
}
@Override
/**
* proxy:指代我們所代理的那個真實物件
* method:指代的是我們要呼叫真實物件的某個方法的Method物件
* args:指代的是呼叫真實物件某個方法時接受的引數
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//當代理真物件前我們可以新增一些自己的操作
System.out.println("瞭解該學生的學習情況");
System.out.println("Method:"+method);
method.invoke(stu, args);
System.out.println("鼓勵下該同學");
return null;
}
}
最後,來看看我們的Client類:
package dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
//被代理的物件
Person realStu = new Student();
//傳入被代理物件,最後通過被代理物件呼叫方法
InvocationHandler handler = new DynamicProxy(realStu);
/*
* 通過Proxy的newProxyInstance方法來建立我們的代理代謝,我們來看看其三個引數
*
* first:handler.getClass().getClassLoader(),我們這裡使用handle這個類
* 的ClassLoader物件來載入我們的代理物件
*
* second:stu.getClass().getInterfaces(),我們這裡為代理物件提供的介面是被
* 代理物件所實現的介面,表示我要代理的是該被代理物件,這樣我就能呼叫這組介面中的方法
*
* third:我們這裡將這個代理物件關聯到了上方的InvocationHandle這個物件上
*
*/
Person proxyStu = (Person)Proxy.newProxyInstance(handler.getClass().getClassLoader(),
realStu.getClass().getInterfaces(), handler);
System.out.println(proxyStu.getClass().getName());
proxyStu.giveMoney("李林");
proxyStu.Money();
}
}
我們先來看看控制檯的輸出:
我們首先來看看 $Proxy0 這東西,我們看到,這個東西是由 System.out.println(subject.getClass().getName()); 這條語句打印出來的,那麼為什麼我們返回的這個代理物件的類名是這樣的呢?
Person proxyStu = (Person)Proxy.newProxyInstance(handler.getClass().getClassLoader(),
realStu.getClass().getInterfaces(), handler);
可能我以為返回的這個代理物件會是Person型別的物件,或者是InvocationHandler的物件,結果卻不是,首先我們解釋一下為什麼我們這裡可以將其轉化為Person型別的物件?原因就是在newProxyInstance這個方法的第二個引數上,我們給這個代理物件提供了一組什麼介面,那麼我這個代理物件就會實現了這組介面,這個時候我們當然可以將這個代理物件強制型別轉化為這組介面中的任意一個,因為這裡的介面是Person型別,所以就可以將其轉化為Person型別了。
同時我們一定要記住,通過 Proxy.newProxyInstance 建立的代理物件是在jvm執行時動態生成的一個物件,它並不是我們的InvocationHandler型別,也不是我們定義的那組介面的型別,而是在執行是動態生成的一個物件,並且命名方式都是這樣的形式,以$開頭,proxy為中,最後一個數字表示物件的標號。
接著我們來看看這兩句
proxyStu.giveMoney("李林");
proxyStu.Money();
這裡是通過代理物件來呼叫實現的那種介面中的方法,這個時候程式就會跳轉到由這個代理物件關聯到的 handler 中的invoke方法去執行,而我們的這個 handler 物件又接受了一個 Student型別的引數,表示我要代理的就是這個真實物件,所以此時就會呼叫 handler 中的invoke方法去執行:
@Override
/**
* proxy:指代我們所代理的那個真實物件
* method:指代的是我們要呼叫真實物件的某個方法的Method物件
* args:指代的是呼叫真實物件某個方法時接受的引數
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//當代理真物件前我們可以新增一些自己的操作
System.out.println("瞭解該學生的學習情況");
System.out.println("Method:"+method);
method.invoke(stu, args);
System.out.println("鼓勵下該同學");
return null;
}