1. 程式人生 > 其它 >RPC遠端過程呼叫小demo

RPC遠端過程呼叫小demo

技術標籤:學習記錄網路rpc分散式

1.流程簡述

客戶端通過呼叫本地增強的方法,將想要增強的方法的資訊傳送給服務端,服務端解析資訊後把方法進行實現,將返回值返回

2.實現

2.0導包

就一個cglib的包

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency
>

2.1:客戶端實現


public class AOPClient {
    private String ip;
    private int port;

    private final Map<String, Class> map = new HashMap<>();

    public AOPClient(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }

    /**
     * 註冊方法
     *
     * @param c 要代理的類
     */
public void register(Class c) { map.put(c.getName(), c); } /** * 註冊方法 * * @param name 要代理的類的名字 * @param c 要代理的類 */ public void register(String name, Class c) { map.put(name, c); } /** * 遠端實現方法 * * @param name 要實現的類名 * @return 增強後的方法,如果不使用的話不會執行 * @throws IOException */
public Object send(String name, boolean j2c) throws IOException { Class aClass = map.get(name); if (aClass == null) { throw new RuntimeException("沒註冊這個方法"); } Socket socket = new Socket(ip, port); return j2c ? getProxy(socket, aClass, name) : getProxy2(socket, aClass, name); } /** * 不註冊直接進行遠端實現 * * @param ip ip * @param port 埠 * @param aClass 要實現的類名 * @return 增強後的方法,如果不使用的話不會執行 * @throws IOException */ public Object send(String ip, int port, Class aClass) throws IOException { Socket socket = new Socket(ip, port); return getProxy(socket, aClass); } /** * 不註冊直接進行遠端實現 * * @param ip ip * @param port 埠 * @param aClass 要實現的類名 * @param name 給要實現的類名起個名,要和服務端想要呼叫的相同 * @return 增強後的方法,如果不使用的話不會執行 * @throws IOException */ public Object send(String ip, int port, Class aClass, String name) throws IOException { Socket socket = new Socket(ip, port); return getProxy(socket, aClass); } /** * JDK動態代理 * * @param socket 套接字 * @param aClass 代理的類 * @return 增強後的方法,如果不使用的話不會執行 */ public Object getProxy(Socket socket, Class aClass) { return getProxy(socket, aClass, aClass.getName()); } /** * JDK動態代理 * * @param socket 套接字 * @param aClass 代理的類 * @param name 代理的類在遠端實現的方法的名字 * @return 代理類實現後的值 */ public Object getProxy(Socket socket, Class aClass, String name) { return Proxy.newProxyInstance(aClass.getClassLoader(), new Class<?>[]{aClass}, (proxy, method, args) -> { return WriteAndRead(socket, name, method, args); } ); } /** * cglib動態代理 * * @param socket 套接字 * @param aClass 代理的類 * @return 代理類實現後的返回值 */ public Object getProxy2(Socket socket, Class aClass) { return getProxy2(socket, aClass, aClass.getName()); } /** * cglib動態代理 * * @param socket 套接字 * @param aClass 代理的類 * @param name 代理的類在遠端實現的方法的名字 * @return 代理類實現後的值 */ public Object getProxy2(Socket socket, Class aClass, String name) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(aClass); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return WriteAndRead(socket, name, method, objects); } }); return enhancer.create(); } /** * 兩個不同的代理共有的方法 * 通過socket把要實現的類資訊傳送並將返回值接收 * * @param socket 套接字 * @param name 類名 * @param method 方法 * @param objects 方法引數s * @return 方法執行的返回值 * @throws IOException * @throws ClassNotFoundException */ public Object WriteAndRead(Socket socket, String name, Method method, Object[] objects) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream()); /*把要實現的介面資訊傳送出去*/ objectOutputStream.writeObject(new NetData(name, method.getName(), objects, method.getParameterTypes())); /*接收對面傳送的返回值*/ Object object = objectInputStream.readObject(); /*關流,關閉外層流,會自動關閉內層,關流後socket會自動關閉*/ objectInputStream.close(); objectOutputStream.close(); /*將返回值返回*/ return object; } }

2.2:服務端實現

public class AOPServer {
    private final ServerSocket server;
    private volatile boolean flage = true;
    /*
     * key 介面全路徑
     * value 介面對應的實現類
     * */
    private final Map<String, Object> map = new HashMap<>();

    public AOPServer(int port) throws IOException {
        this.server = new ServerSocket(port);
    }

    /**
     * 新增暴露的方法
     *
     * @param o 方法
     */
    public void expose(Object o) {
        //啟動後不讓改了
        if (flage) {
            String name = o.getClass().getName();
            map.put(name.substring(0, name.length() - 3), o);
        }
    }

    /**
     * 自定義暴露的方法的名稱
     *
     * @param name 名稱
     * @param o    方法
     */
    public void expose(String name, Object o) {
        map.put(name, o);
    }

    /**
     * 啟動
     */
    public void start() throws Exception {
        System.out.println("服務啟動了");
        while (flage) {
            Socket socket = server.accept();

            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            //將呼叫的方法執行並將結果返回
            objectOutputStream.writeObject(enhance(objectInputStream));
            /*關流
             * 當Socket的流關閉時,Socket會自動關閉
             * */
            objectOutputStream.close();
            objectInputStream.close();
            //stop();
        }
    }

    /**
     * 停止
     */
    public void stop() {
        flage = false;
    }

    /**
     * 根據遠端傳送的資訊找到本地的實現,然後執行方法,將返回值返回
     *
     * @param oin 從套接字(Socket)得到的輸入流
     * @return 增強的方法的返回值
     */
    public Object enhance(ObjectInputStream oin) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        /*直接讀取傳送過來的NetData*/
        NetData netData = (NetData) oin.readObject();
        System.out.println("傳送過來的資料 = " + netData);
        /*
         * ------------由於是實現的介面,所以方法不會是私有的,即使不是介面,私有方法也不能讓其他人隨意呼叫
         * 從map根據介面類名得到實現類
         * */
        Class<?> aClass = map.get(netData.getClassName()).getClass();
        /*反射得到方法*/
        Method method = aClass.getMethod(netData.getFunctionName(), netData.getParameterTypes());
        /*反射執行方法並返回方法的返回值*/
        return method.invoke(map.get(netData.getClassName()), netData.getParameters());
    }
}

2.3:網路中傳輸的實體類


/**
 * 因為要在網路中傳輸,所以要實現Serializable介面
 */
public class NetData implements Serializable {
    private String className;
    private String functionName;
    private Object[] parameters;
    private Class<?>[] parameterTypes;

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getFunctionName() {
        return functionName;
    }

    public void setFunctionName(String functionName) {
        this.functionName = functionName;
    }

    public NetData() {
    }

    /**
     * 存放類的資訊
     *
     * @param className      類名
     * @param functionName   代理的方法名
     * @param parameters     引數列表
     * @param parameterTypes 引數的型別列表
     */
    public NetData(String className, String functionName, Object[] parameters, Class<?>[] parameterTypes) {
        this.className = className;
        this.functionName = functionName;
        this.parameters = parameters;
        this.parameterTypes = parameterTypes;
    }

    public Object[] getParameters() {
        return parameters;
    }

    public void setParameters(Object[] parameters) {
        this.parameters = parameters;
    }

    public Class<?>[] getParameterTypes() {
        return parameterTypes;
    }

    public void setParameterTypes(Class<?>[] parameterTypes) {
        this.parameterTypes = parameterTypes;
    }

    @Override
    public String toString() {
        return "NetData{" +
                "className='" + className + '\'' +
                ", functionName='" + functionName + '\'' +
                ", parameters=" + Arrays.toString(parameters) +
                ", parameterTypes=" + Arrays.toString(parameterTypes) +
                '}';
    }
}

2.4:一個待增強的介面和兩個(一個半?)實現類

介面:

public interface DataServiceI {
    public String getData(Long i);
}

實現類:

public class DataServiceImpl implements DataServiceI {

    @Override
    public String getData(Long i) {
        return "實現:算術題,兩邊的時間差" + (System.currentTimeMillis() - i);
    }
}

實現類(偽)

public class DataServiceImplClone {

    public String getData(Long i) {
        return "--實現:你的方法已經被我NTR啦" + new Date(i);
    }
}

2.5 測試類

客戶端


public class ClientTest {
    public static void main(String[] args) throws IOException {
        AOPClient aopClient = new AOPClient("127.0.0.1", 8080);
        aopClient.register("asdf", DataServiceI.class);
        aopClient.register(DataServiceI.class);

        //這個是為了測試jdk動態代理和cglib代理非介面的類
        aopClient.register("impl", DataServiceImpl.class);
        //JDK動態代理,自定義名字
        DataServiceI dataServiceI1 = (DataServiceI) aopClient.send("asdf", true);
        //JDK動態代理,類全路徑
        DataServiceI dataServiceI2 = (DataServiceI) aopClient.send(DataServiceI.class.getName(), true);
        //cglib動態代理,自定義名字
        DataServiceI dataServiceI3 = (DataServiceI) aopClient.send("asdf", false);
        //cglib動態代理,類全路徑
        DataServiceI dataServiceI4 = (DataServiceI) aopClient.send(DataServiceI.class.getName(), false);

        System.out.println("J自定義:" + dataServiceI1.getData(System.currentTimeMillis()));
        System.out.println("J" + dataServiceI2.getData(System.currentTimeMillis()));
        System.out.println("C自定義:" + dataServiceI3.getData(System.currentTimeMillis()));
        System.out.println("C" + dataServiceI4.getData(System.currentTimeMillis()));

        /**
         * 測試jdk和cglib代理非介面的類
         */
        DataServiceI dataServiceI5 = (DataServiceI) aopClient.send("impl", false);
        System.out.println(dataServiceI5.getData(123456789l));
        //這裡是jdk動態代理,很明顯,代理不成功,因為jdk動態代理就是不能這麼幹,
        // 不過因為遠端代理做的就是實現介面這個活,所以還是用jdk動態代理
        //DataServiceI dataServiceI6 = (DataServiceI) aopClient.send("impl", true);
        //System.out.println(dataServiceI6.getData(987654321l));
    }
}

服務端


public class ServerClient {
    public static void main(String[] args) throws Exception {
        /*在哪個埠啟動*/
        AOPServer aopServer = new AOPServer(8080);
        /*
         * 需要暴露哪個方法
         * 這個名字叫什麼其實隨便,只要另一邊知道這個名字是這個類就行
         * 甚至還可以更過分,只要你這邊的方法名、引數列表、引數型別和那邊傳過來的相同,隨便你用哪個類
         * */
        aopServer.expose("ServiceI.DataServiceI", new DataServiceImpl());
        aopServer.expose("asdf", new DataServiceImplClone());
        aopServer.expose("impl",new DataServiceImplClone());
        /*啟動*/
        aopServer.start();
    }
}

3.測試

客戶端:
在這裡插入圖片描述
服務端
在這裡插入圖片描述

4.總結

用到的東西還是挺多的
動態代理
反射
Socket
序列化

實現這個的重點就是在代理時把方法偷樑換柱換成連線遠端取得結果就行了
在這裡插入圖片描述