代理模式:給某一對象提供代理對象,並由代理對象控制具體對象的引用.
代理,指的就是一個角色代表另一個角色採取行動,就象生活中,一個紅酒廠商,是不會直接把紅酒零售客戶的,都是通過代理來完成他的銷售業務的.而客戶,也不用爲了喝紅酒而到處找工廠,他只要找到廠商在當地的代理就行了,具體紅酒工廠在那裏,客戶不用關心,代理會幫他處理.
代理模式涉及的角色:
1:抽象主題角色.聲明瞭代理主題和真實主題的公共接口,使任何需要真實主題的地方都能用代理主題代替.
2:代理主題角色.含有真實主題的引用,從而可以在任何時候操作真實主題,代理主題功過提供和真實主題相同的接口,使它可以隨時代替真實主題.代理主題通過持有真實主題的引用,不但可以控制真實主題的創建或刪除,可以在真實主題被調用前進行攔截,或在調用後進行某些操作.
3:真實代理對象.定義了代理角色所代表的具體對象.
下面是代理模式的實現類圖:
根據上圖的關係,我們可以用客戶買紅酒來模擬代理模式的實現,
紅酒代理商和紅酒廠商都有銷售紅酒的只能,我們可以爲他們定義一個共同的抽象主題角色,
- /**
- *抽象主題角色,定義了真實角色和代理角色的公共接口
- */
- public interface SellInterface{
- public Object sell();
- }
接着,我們定義真實主題角色(這裏就是紅酒工廠),它必須實現了SellInterface接口的.
- /**
- *真實主題角色,這裏指紅酒工廠角色,它實現了SellInterface接口
- */
- public class RedWineFactory implements SellInterface{
- public Object sell(){
- System.out.println("真實主題角色RedWineFactory 被調用了");
- return new Object();
- }
- }
下面是代理主題角色(這裏指紅酒代理商),同樣,代理主題也必須實現SellInterface接口.
- /**
- *代理主題角色,這裏指紅酒代理商.它除了也要實現了sellInterface接口外,還持有紅酒
- *廠商RedWineFactory 對象的引用,從而使它能在調用真實主題前後做一些必要處理.
- */
- public class RedWineProxy implements SellInterface{
- //持有一個RedWineFactory對象的引用
- private RedWineFactory redWineFactory;
- //銷售總量
- private static int sell_count = 0;
- public Object sell(){
- if(checkUser()){//在通過代理主題角色,我們可以在真實主題角色被調用前做一些諸如權限判斷的事情
- Object obj = redWineFactory.sell();
- count ++;//同樣,在調用後我們也可以執行一些額外的動作.
- return obj ;
- }else{
- throw new RuntimeException();
- }
- }
- protected boolean checkUser(){
- //do something
- return true;
- }
- }
接下來看看調用代理對象的代碼
- public static void main(String agr[])
- {
- SellInterface sell = new RedWineProxy();
- sell.sell();
- }
從上面的例子可以看出代理模式的工作方式,首先,因爲代理主題和真實主題都實現了共同的接口,這使我們可以在不改變原來接口的情況下,只要用真實主題對象的地方,都可以用代理主題來代替.其次,代理主題在客戶和真實主題之間起了一箇中介作用,利用這個中介平臺,我們可以在把客戶請求傳遞給真實主題之前,做一些必要的預處理.
java對代理模式的支持 ---動態代理
上面的代理,我們強迫代理類RedWineProxy實現了抽象接口SellInterface.這導致我們的代理類無法通用於其他接口,所以不得不爲每一個接口實現一個代理類.幸好,java爲代理模式提供了支持.
java主要是通過Proxy類和InvocationHandler接口來給實現對代理模式的支持的.
下面用java的代理機制來實現上面的例子
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- /**
- *代理類一定要實現了InvocationHandler接口
- */
- public class ProxyObject implements InvocationHandler{
- private Object proxy_obj;
- ProxyObject(Object obj){
- this.proxy_obj = obj;
- }
- public static Object factory(Object obj){
- Class cls = obj.getClass();
- //通過Proxy類的newProxyInstance方法來返回代理對象
- return Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(),new ProxyObject(obj));
- }
- /**
- *實現InvocationHandler接口的invoke
- */
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- System.out.println("函數調用前被攔截了: " + method);
- if(args != null){
- //打印參數列表
- System.out.println("方法有 " + args.length + " 個參數");
- for(int i = 0; i < args.length; i ++){
- System.out.println(args[i]);
- }
- }
- //利用反射機制動態調用原對象的方法
- Object mo = method.invoke(proxy_obj, args);
- System.out.println("函數調用後進行處理 : " + method);
- return mo;
- }
- //測試代碼
- public static void main(String agr[]){
- SellInterface si = (SellInterface)factory(new RedWineFactory());
- si.sell();
- }
- }
通過上面的代碼可以看出,代理主題ProxyObject類並沒有實現我們定義的SellInterface藉口,
而是實現了java的InvocationHandler接口,這樣就把代理主題角色和我們的業務代碼分離開來,使代理對象能通用於其他接口.
其實InvocationHandler接口就是一種攔截機制,當系統中有了代理對象以後,對原對象(真實主題)方法的調用,都會轉由InvocationHandler接口來處理,並把方法信息以參數的形式傳遞給invoke方法,這樣,我們就可以在invoke方法中攔截原對象的調用,並通過反射機制來動態調用原對象的方法.這好象也是spring aop編程的基礎吧
接着,用代理模式實現一個超級簡單的aop攔截機制
這個例子可以攔截我們指定的函數,並在攔截前後根據需要進行處理
- /**
- *切面接口,通過實現這個接口,我們可以對指定函數在調用前後進行處理
- */
- public interface AopInterface {
- public void before(Object obj);//調用的處理
- public void end(Object obj);//調用後的處理
- }
這個是實現了AopInterface 接口,在這裏我們實現了我們的處理邏輯
- public class AopInterfaceImp implements AopInterface{
- public void before(Object obj) {
- System.out.println("調用前攔截");
- }
- public void end(Object obj) {
- System.out.println("調用調用後處理");
- }
- }
接着是代理類
- public class PeoxyObject implements InvocationHandler {
- private AopInterface aop;//定義了切入時調用的方法
- private Object proxy_obj;
- private String methodName;//指定要切入的方法名
- PeoxyObject(){}
- public Object factory(Object obj){
- proxy_obj = obj;
- Class cls = obj.getClass();
- return Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(),this);
- }
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if(this.aop == null)throw new NullPointerException("aop is null");
- if(method == null)throw new NullPointerException("method is null");
- Object o;
- //如果指定了要攔截方法名,並且調用的方法和指定的方法名相同,則進行攔截處理
- //否則當正常方法處理
- if(methodName != null && method.toString().indexOf(methodName) != -1){
- aop.before(proxy_obj);//指定方法調用前的處理
- o = method.invoke(proxy_obj, args);
- aop.end(proxy_obj);//指定方法調用後的處理
- }else{
- //沒有指定的方法,以正常方法調用
- o = method.invoke(proxy_obj, args);
- }
- return o;
- }
- public AopInterface getAop() {
- return aop;
- }
- public void setAop(AopInterface aop) {
- this.aop = aop;
- }
- public String getMethodName() {
- return methodName;
- }
- public void setMethodName(String methodName) {
- this.methodName = methodName;
- }
- }
這裏定義一個用來測試用的類
- public interface SubInterface {
- public void add(String value1,String value2);
- public void acc(String value1);
- }
- public class ImpObject implements SubInterface{
- public void add(String value1,String value2) {
- System.out.println("ImpObject add(String value1,String value2)");
- }
- public void acc(String value1){
- System.out.println("ImpObject acc(String value1)");
- }
- }
這裏是測試代碼
- public static void main(String agr[]){
- PeoxyObject po = new PeoxyObject();
- po.setAop(new AopInterfaceImp());//我們實現的攔截處理對象
- po.setMethodName("acc");//指定要攔截的函數
- SubInterface si = (SubInterface)po.factory(new ImpObject());
- //因爲add方法不是我們指定的攔截函數,AopInterfaceImp是不會被執行
- si.add("tt","dd");
- //acc是我們指定的攔截方法,所以調用acc的前後會先執行AopInterfaceImp
- //對象的兩個方法
- si.acc("tt");
- }
通過上面可以看出,攔截機制是代理模式的重要使用方式之一,
除了攔截,代理模式還常用於資源加載,當我們要加載的資源很大時,我們可以讓真實主題角色在後臺加載資源,讓代理主題角色負責處理前臺的等待提示信息.
還有就是授權機制,通過代理能攔截真實主題的能力,來控制真實主題的訪問權限.