Java-代理模式的理解


  • 引言

    設計模式是語言的表達方式,它能讓語言輕便而富有內涵、易讀卻功能強大。代理模式在Java中十分常見,有為擴展某些類的功能而使用靜態代理,也有如Spring實現AOP而使用動態代理,更有RPC實現中使用的調用端調用的代理服務。代理模型除了是一種設計模式之外,它更是一種思維,所以探討並深入理解這種模型是非常有必要的。

  • 代理模式拳譜總綱

     代理模式這種設計模式是一種使用代理對象來執行目標對象的方法並在代理對象中增強目標對象方法的一種設計模式。代理對象代為執行目標對象的方法,並在此基礎上進行相應的擴展。看起來是有點拗口,首先介紹一個原則:開閉原則(對擴展開放,對修改關閉)。一種好的設計模式甚至是架構,都是在不修改原有形態的基礎上擴展出新的功能。

    代理模式的元素是:共同接口、代理對象、目標對象。

    代理模式的行為:由代理對象執行目標對象的方法、由代理對象擴展目標對象的方法。

    代理模式的宏觀特性:對客戶端只暴露出接口,不暴露它以下的架構。

    代理模式的微觀特性:每個元由三個類構成,如圖。

                   

     代理模式的種類:靜態代理、動態代理(jdk動態代理、cglib動態代理、Spring和AspectJ實現的動態代理)

  • 靜態代理

     靜態代理模式就是如上圖所示,構造三個類實現他們的關系。

     首先會思考的一點就是為什么需要實現同一個接口,如果不實現同一個接口,一樣可以“代理”功能,所以為什么非要實現同一個接口。我個人認為不實現統一接口的話代理方法有可能會不能完全實現(因為實現接口必須實現它的抽象方法),其次就是方法名稱了,已經由接口定義的方法就是目標對象實現了的功能,也算是一種提醒,最后我能想到的就是不實現統一接口的話應該叫做聚合而不是代理。

 

package Proxy.Static;

public interface DAOInterface {
    public void add();
    public void delete();
    public void update();
    public void query();
}

 

package Proxy.Static;

public class UserDao implements DAOInterface{

    @Override
    public void add() {
        System.out.println("在目標對象中執行add");
    }

    @Override
    public void delete() {
        System.out.println("在目標對象中執行delete");
    }

    @Override
    public void update() {
        System.out.println("在目標對象中執行update");
    }

    @Override
    public void query() {
        System.out.println("在目標對象中執行query");
    }

}
package Proxy.Static;
/**
 * 代理對象
 * @author ctk
 *
 */
public class UserDaoProxy implements DAOInterface{
    UserDao userDao = null;
    public UserDaoProxy(UserDao userDao){
        this.userDao = userDao;
    }
    @Override
    public void add() {
        userDao.add();
        System.out.println("記錄日志add");
    }

    @Override
    public void delete() {
        userDao.delete();
        System.out.println("記錄日志delete");
    }

    @Override
    public void update() {
        userDao.update();
        System.out.println("記錄日志update");
    }

    @Override
    public void query() {
        userDao.query();
        System.out.println("記錄日志query");
    }

}

    靜態代理就是寫死了在代理對象中執行這個方法前后執行添加功能的形式,每次要在接口中添加一個新方法,則需要在目標對象中實現這個方法,並且在代理對象中實現相應的代理方法,幸而Java有獨特的反射技術,可以實現動態代理。

  • 動態代理

     實際上在原理上講我只認識Jdk動態代理和Cglib動態代理,Spring和AspectJ的動態代理是基於前面兩種來實現的。

     Jdk的動態代理,是使用反射技術獲得類的加載器並且創建實例,根據類執行的方法在執行方法的前后發送通知。

     接口和實現類還是使用上面貼出來的。

     在代理對象Proxy的新建代理實例方法中,必須要獲得類的加載器、類所實現的接口、還有一個攔截方法的句柄。

     在句柄的invoke中如果不調用method.invoke則方法不會執行。在invoke前后添加通知,就是對原有類進行功能擴展了。

     創建好代理對象之后,proxy可以調用接口中定義的所有方法,因為它們實現了同一個接口,並且接口的方法實現類的加載器已經被反射框架獲取到了。

package Proxy.jdkProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) {
        DAOInterface userDao = new UserDao();
        DAOInterface proxy = (DAOInterface) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces()
                , new InvocationHandler() {
                    //回調方法 攔截到目標對象的時候執行
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("在 代理對象 中攔截到:"+method.getName());
                        Object o = method.invoke(userDao, args);//調用攔截到的方法
                        return o;
                    }
                });
        proxy.delete();
    }
}

      另一種動態代理的實現方式是使用Cglib,所以得先導入兩個包

  

     實現方式如下

package Proxy.Cglib;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * cglib代理 要求類不能使final 代理對象繼承目標對象
 * 
 * @author ctk
 *
 */
public class Test {
    public static void main(String[] args) {
        UserDao target = new UserDao();
        Enhancer en = new Enhancer();
        //設置代理對象的父類
        en.setSuperclass(target.getClass());
        //設置回調
        en.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
                System.out.println("在 代理對象 中攔截到 "+arg1.getName());
                Object obj = arg1.invoke(target, arg2);
                return obj;
            }
        });
        UserDao proxy = (UserDao) en.create();
        proxy.add();
    }
}

     Cglib實現代理的方式是和目標對象使用同一個父類,無論是繼承還是實現接口,都是為了代理對象能直接調用目標對象的方法。

  • RPC服務分離代理模式

      

     在分布式的書上看到RPC的誕生契機,任何一種技術干學都是不長進的,只有把它的前世今生給摸透了,才能有所領悟。當你的web應用達到了編譯一次1分鍾或者5分鍾或者更多的時候,你就該想到服務解耦這個辦法了。即使是在做一個ERP,但是業務多起來也是如C++編譯一樣的(不是黑C++哈哈),服務解耦的意義就是想,我這個web應用調用的service能不能不運行在同一個web應用中呢?RPC由此誕生了,本地保存一個調用列表,而真正執行是在另外一個應用(或者另外一個服務器)。只要制定好通信框架序列化和反序列化的制度(私有協議棧),就做成了一個RPC框架。雖然如ActiveMQ之流的通信框架底層使用的是這種技術,但是它們的復雜程度也不是一個demo能說清楚的。

     服務調用方

package RPC;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;

public class RPCimporter<S> {
    
    @SuppressWarnings("unchecked")
    public S importer(final Class<?> serviceClass,final InetSocketAddress addr){
        return (S)Proxy.newProxyInstance(serviceClass.getClassLoader(), new Class<?>[]{serviceClass.getInterfaces()[0]}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Socket socket = null;
                ObjectOutputStream out = null;
                ObjectInputStream ins = null;
                try{
                    socket = new Socket();
                    socket.connect(addr);
                    out = new ObjectOutputStream(socket.getOutputStream());
                    out.writeUTF(serviceClass.getName());
                    out.writeUTF(method.getName());
                    out.writeObject(method.getParameterTypes());
                    out.writeObject(args);
                    ins = new ObjectInputStream(socket.getInputStream());
                    return ins.readObject();
                }finally {
                    if(out != null)
                        out.close();
                    if(ins != null)
                        ins.close();
                    if(socket != null)
                        socket.close();
                }
                
            }
        });
    }
}

     服務調用

package RPC;

import java.io.IOException;
import java.net.InetSocketAddress;

/**
 * 測試RPCdemo
 * @author ctk
 *
 */
public class RPCTest {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            
            @Override
            public void run() {
                try {
                    RPCExporter.exporter("localhost", 10000);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        RPCimporter<EchoService> importer = new RPCimporter<>();
        EchoService echo = importer.importer(EchoServiceImpl.class, new InetSocketAddress("localhost", 10000));
        System.out.println(echo.echo("hello world"));
    }
}

     服務列表用接口來實現再好不過了,在調用importer這個方法的時候,會往提供方請求一個執行服務的類和方法並要求返回一個執行結果。

     服務提供方

package RPC;
/**
 * RPC服務代理接口
 * @author MacBook
 *
 */
public interface EchoService {
    public String echo(String ping);
}
package RPC;

public class EchoServiceImpl implements EchoService{

    @Override
    public String echo(String ping) {
        return ping != null ? ping + " ---> I am ok.":"I am ok";
    }

}
package RPC;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class RPCExporter {
    static Executor executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    public static void exporter(String hostName,int port) throws IOException{
        ServerSocket server = new ServerSocket();
        server.bind(new InetSocketAddress(hostName, port));
        try{
            while(true){
                executor.execute(new ExportTask(server.accept()));
                
            }
        }catch (Exception e) {
            
        } finally {
            server.close();
        }
        
    }
    
}
package RPC;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.Socket;

public class ExportTask implements Runnable {
    Socket client = null;
    public ExportTask(Socket socket){
        client = socket;
    }

    @Override
    public void run() {
        ObjectInputStream oins = null;
        ObjectOutputStream oous = null;
        try{
            oins = new ObjectInputStream(client.getInputStream());
            String interfaceName = oins.readUTF();
            Class<?> service = Class.forName(interfaceName);
            String methodName = oins.readUTF();
            Class<?>[] parameterTypes = (Class<?>[])oins.readObject();
            Object[] arguments = (Object[])oins.readObject();
            Method method = service.getMethod(methodName, parameterTypes);
            Object result = method.invoke(service.newInstance(), arguments);
            oous = new ObjectOutputStream(client.getOutputStream());
            oous.writeObject(result);
        }catch (Exception e) {
            
        }finally {
            
                try {
                    if(oins != null)
                        oins.close();
                    if(oous != null)
                        oous.close();
                    if(client != null)
                        client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }

}

     服務器架構和BIO的多線程結構沒有什么區別,只是使用了線程池而已,把accept到的socket交給線程實現類去完成任務。在提供服務的時候先讀取請求的對象名和方法名,並通過反射創建實例,最后接受到參數,運行結束之后返回result結果。

  • 一些感想

     以前覺得,設計模式只是為了代碼更加簡潔,或者實現功能更為有條理而設置的,現在慢慢感悟到框架的設計也可設計模式有緊密的關系。就比如生產者消費者模型和消息中間件的架構是密切相關的,相似的還有觀察者模型、發布訂閱模型。而RPC服務實現服務解耦也有點像代理模式的思想,雖然它沒有使用反射進行動態代理,沒有AOP那么直接。還有一點,就是不同場景下的設計模式需要做不同的思考,比如單例模式在高並發環境下應該如何達到高效、安全。

     多思考,多挖掘,干巴爹。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM