Rpc 框架Link 設計實現完畢(面試要知道)

一、創建項目

在這裏插入圖片描述

二、設計目標

2.1 概論

設計一個基於接口的調用方式的遠程調用框架,和spring 整合在一起

2.2 形式

2.2.1 導入spring的依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sxt</groupId>
    <artifactId>link</artifactId>
    <version>2.0</version>
    <properties>
       <spring.version>4.3.16.RELEASE</spring.version>
    </properties>
  <dependencies>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>${spring.version}</version>
      </dependency>
  </dependencies>
</project>

2.2.2 服務的提供者的啓動類和配置文件

啓動類:

/**
 * 服務提供者的啓動類
 */
public class ProviderApp {

    /**
     * 我們和spring 整合在了一起
     * @param args
     */
    public static void main(String[] args) {
        // 加載spring的容器
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("classpath:provider.xml");
        app.start();

        // 掛起jvm
        try {
            System.in.read() ;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

配置文件:

在這裏插入圖片描述
在這裏插入圖片描述

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.sxt.test.service.impl"></context:component-scan>
</beans>

2.2.3 接口和實現類的準備
在這裏插入圖片描述

2.2.4 服務消費者的啓動類和配置文件
啓動類:

/**
 * 消費者
 */
public class ConsumerApp {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("consumer.xml");
        app.start();

        ConsumerService consumerService = app.getBean(ConsumerService.class);
        consumerService.consumer(); // consumer 底層會觸發遠程調用
        try {
            // 掛起jvm
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.sxt.test.consumer.service.impl"></context:component-scan>
</beans>

2.2.5 服務消費者的接口和實現類
在這裏插入圖片描述
在這裏插入圖片描述

三、設計註解和功能

3.1 對於服務的提供者

要註冊該服務,已經暴露該服務!
在dubbo 裏面使用@service來完成該功能
對於link:
在這裏插入圖片描述
在這裏插入圖片描述
使用該註解:
在這裏插入圖片描述

代表該服務以後將被暴露出去

3.2 對於服務的消費者

去遠程調用一個服務
在這裏插入圖片描述

使用該註解:
在這裏插入圖片描述

3.3 設計工具和接口
在這裏插入圖片描述

3.3.1 負載均衡的功能定義
在這裏插入圖片描述

3.3.2 註冊中心的功能
在這裏插入圖片描述

3.3.3 編碼器(影響性能的關鍵)

我們在遠程調用時,不可避免要把對象轉換爲字節去在socket 上面發送?
我們昨天的Request 還實現序列化的接口了
我們把對象->字節
字節-> 對象
在這裏插入圖片描述

3.3.4 模型對象
在這裏插入圖片描述
3.3.5 代理對象的工具類

/**
 * 代理對象的工具類
 */
public class ProxyUtil {

    /**
     * 通過接口創建代理對象
     * @param interfacee
     * @param <T>
     * @return
     */
    public static <T> T createProxy(Class<T> interfacee){
       return (T) Proxy.newProxyInstance(
                ProxyUtil.class.getClassLoader(),
                new Class<?>[]{interfacee},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        /**
                         * 1 普通方法的處理
                         */
                        boolean isCommonMethod = false ;
                        if(isCommonMethod){
                            return commonInvoker(method.getName());
                        }
                        /**
                         * 2 遠程調用的方法的處理
                         */
                        Request request = new Request();
                        request.setInterfaceName(interfacee.getName());
                        request.setMethodName(method.getName());
                        request.setArgs(args);

                        return rpcInvoker(request); // 遠程調用
                    }
                }
        );
    }

    /**
     * 完成遠程的調用
     * @param request
     * @return
     */
    private static Object rpcInvoker(Request request) {
        return  null ;
    }

    /**
     * 完成普通方法的調用
     * @param name
     * @return
     */
    private static Object commonInvoker(String name) {
        return  null ;
    }
}

3.3.6 方法的調用
在這裏插入圖片描述

方法的調用在2 個場景發生:
1 服務提供者而言
需要一個本地的調用

2 服務的消費者而言
需要遠程的方法調用
在這裏插入圖片描述

使用起來:
在這裏插入圖片描述

在這裏插入圖片描述

3.3.7 網絡的能力

網絡的能力無非就是輸入和輸出(IO)
在這裏插入圖片描述

四、實現接口和工具

牛逼的公司,做設計(標準),菜(實現)

4.1 實現負載均衡

/**
 * 隨機的負載均衡實現
 */
public class RandomLoadBalance implements  LoadBalance{

    /**
     * 單例對象
     */
    public static final  RandomLoadBalance INSTANCE =  new RandomLoadBalance();

    public Random rdm = new Random() ;

    private RandomLoadBalance(){}

    @Override
    public String loadbalance(List<String> servers) {

        if(servers==null || servers.isEmpty()){
            throw  new RuntimeException("輸入的集合爲null") ;
        }
        return servers.get(rdm.nextInt(servers.size()));
    }
}

4.2 實現註冊中心的功能
註冊中心不僅僅可以使用zk 吧,redis 也能實現?在dubbo 裏面講過吧
使用zk 實現註冊中心,需要導入zkclient的依賴

public class ZkRegister  implements  Register{

    private ZkClient zkClient = null ;
    private String zkUrl = "" ;
    private int connectionTime = 300000;
    private int sessionTime = 5000 ;
    {
        zkClient = new ZkClient(zkUrl,sessionTime,connectionTime) ;
    }
    private Map<String,List<String>> cache = new HashMap<>();

    public static final ZkRegister INSTANCE = new ZkRegister() ;

   private ZkRegister (){}

    @Override
    public void register(String serviceName, String serviceAddress) {
        if(!zkClient.exists("/"+serviceName)){
            zkClient.createPersistent("/"+serviceName);
        }
        if(!zkClient.exists("/"+serviceName+"/"+serviceAddress)){
            zkClient.createEphemeral("/"+serviceName+"/"+serviceAddress);
            System.out.println("服務"+serviceName+"註冊成功,地址爲"+serviceAddress);
        }

    }

    @Override
    public List<String> discovery(String serviceName) {
       if(cache.containsKey(serviceName)){
           return cache.get(serviceName) ;
       }
        List<String> servers = zkClient.getChildren("/" + serviceName);
       if(servers!=null && !servers.isEmpty()){
           cache.put(serviceName,servers);
           zkClient.subscribeChildChanges("/" + serviceName, new IZkChildListener() {
               @Override
               public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
                   System.out.println("服務"+serviceName+"的提供者有改變");
                   if(currentChilds==null || currentChilds.isEmpty()){
                       cache.remove(serviceName);
                   }else{
                       cache.put(serviceName,currentChilds) ;// 更新緩存
                       System.out.println("服務列表已經更新完畢");
                   }
               }
           }) ;
           return  servers ;
       }
        return Collections.emptyList();
    }
}

4.3 實現編碼器的功能

/**
 * 利用jdk的序列化實現
 */
public class JDKCoder  implements  Coder{

    public static final JDKCoder INSTANCE = new JDKCoder() ;
    
    private JDKCoder(){}
    /**
     * 將對象轉爲爲字節
     * @param object
     * @return
     */
    @Override
    public byte[] code(Object object) {
//        對象-> 字節 對象的流
        if(object==null){
            return  new byte[0] ;
        }
        byte[] bytes = new byte[0];
        ByteArrayOutputStream byteArrayOutputStream = null  ;
        ObjectOutputStream objectOutputStream = null ;
        try {
            byteArrayOutputStream = new ByteArrayOutputStream();
            objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(object);
            objectOutputStream.flush();
            bytes = byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 關閉資源的操作
        }
        return bytes ; // 將內存裏面的字節讀取出來
    }

    /**
     * 將字節轉換爲對象
     * @param bs
     * @return
     */
    @Override
    public Object deCode(byte[] bs) {
        if(bs==null || bs.length==0){
            return  null ;
        }
        Object object = null;
        ByteArrayInputStream byteArrayInputStream = null ;
        ObjectInputStream objectInputStream = null ;
        try {
             byteArrayInputStream = new ByteArrayInputStream(bs);
             objectInputStream = new ObjectInputStream(byteArrayInputStream);
             object = objectInputStream.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
        }
        return object;
    }
}

4.4 方法的調用實現

4.4.1 遠程方法調用的實現

public class RpcMethodInvoker implements  MethodInvoker{

    private Net net ;
    
    private Coder coder ;
    
    /**
     * 遠程調用的實現
     * @param request
     * @return
     */
    
    @Override
    public Object invoker(Request request) {
        
        net.write(coder.code(request)); // 發送請求對象給服務的提供者

        byte[] bs = net.read(); // 接受服務提供者的答案
        
        return coder.deCode(bs);
    }
}

4.4.2 本地方法調用的實現

public class LocalMethodInvoker implements  MethodInvoker {

    
    private ApplicationContext applicationContext  ;// 它就是ioc的容器

    /**
     * 完成真實方法的調用
     * @param request
     * @return
     */
    @Override
    public Object invoker(Request request) {
        String interfaceName = request.getInterfaceName();
        
        Object result = null;
        try {
            Class<?> interfacee = Class.forName(interfaceName);
            // 怎麼通過接口得到實現類的對象  我們現在位於ioc 裏面
            Object bean = applicationContext.getBean(interfacee); // 真實對象
            String methodName = request.getMethodName();
            Object[] args = request.getArgs();
            Class<?> types[] = null ;
            if(args!=null && args.length > 0){ // 得到方法的參數類型
                types = new Class[args.length] ;
                for (int i = 0; i < args.length; i++) {
                    types[i] = args[i].getClass() ;
                }
            }
            Method method = interfacee.getMethod(methodName, types);
            result = method.invoke(bean, args);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return result;
    }
}

4.5 網絡能力的實現
實現io

public class SocketNet  implements  Net{

    @Override
    public void write(byte[] bs) {
        OutputStream outputStream = null ;
        try {
            outputStream.write(bs); // 可以直接寫
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // TODO 關閉資源的操作
        }
    }

    @Override
    public byte[] read() {
        InputStream inputStream = null ;
        /**
         * 讀取的操作
         */
        byte[] buff = new byte[1024];
        int len = 0 ;
        byte[] bytes = new byte[0];
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            while((len=inputStream.read(buff)) != -1){ // 從input 讀取到buff ,長度爲0-len
                byteArrayOutputStream.write(buff,0,len);
            }
            bytes = byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //TODO 關閉資源的操作
        }
        return bytes;
    }
}

4.5.1 針對服務端

ServerSockert

4.5.2 針對客戶端
Socket

public class ClientSocketNet extends  SocketNet{

    private Register register ;

    private LoadBalance loadBalance ;

//    private  Socket socket  ;
//    答案是不行的,因爲我們ClientSocketNet 是個單例對象,那單例對象裏面的屬性也是唯一的,若有線程給它賦值後,它就有值了
    // 和springmvc 裏面的對象的變量是一個道理 // 怎麼解決springMVC 裏面變量的安全問題?
    /**
     * 輸出流用於發送一個數據
     * @return
     */
    @Override
    protected OutputStream getOutputStream() {
        // 利用ThreadLocal 取出當前要調用的服務的名稱
        //TODO
        String serviceName = "" ;

        /**
         * 服務的發現
         */
        List<String> servers = register.discovery(serviceName);

        /**
         * 服務的負載均衡
         */
        String server = loadBalance.loadbalance(servers);
        String[] serverAddress = server.split(":");

        OutputStream outputStream = null;
        Socket socket = null ;
        try {
            socket = new Socket(serverAddress[0],Integer.valueOf(serverAddress[1])); //ip :port ->服務的地址-> 服務的發現+ 負載均衡
            outputStream = socket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 把Socket 存儲起來 TODO
        // 使用ThreadLocal 做

        return outputStream;
    }

    @Override
    protected InputStream getInputStream() {
        Socket socket = null ;//  // 從ThreadLocal 裏面獲取Socket
        InputStream inputStream = null;
        try {
            inputStream = socket.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return inputStream;
    }
}

4.6 ThreadLocal 利用起來

/**
 * ThreadLocal的工具類
 */
public class ThreadLocalUtil {

    private static ThreadLocal<Map<String,Object>> threadLocal = new ThreadLocal<Map<String, Object>>(){

        @Override
        protected Map<String, Object> initialValue() {
            return new HashMap<>();
        }
    };

    /**
     * 從ThreadLocal 裏面取值
     * @param name
     * @return
     */
    public static Object get(String name){
        Map<String, Object> objectMap = threadLocal.get();
        if(objectMap.containsKey(name)){
            return objectMap.get(name) ;
        }
        return  null ;
    }

    /**
     * 給ThreadLocal 裏面保存值
     * @param name
     * @param value
     */
    public static void set(String name,Object value){
        Map<String, Object> objectMap = threadLocal.get();
        objectMap.put(name,value) ;
    }

    /**
     * 清理ThreadLocal 裏面的值
     */
    public static void clear(){
        Map<String, Object> stringObjectMap = threadLocal.get();
        stringObjectMap.clear();
    }

}

4.6.1 利用ThreadLocal 在一個線程之間來共享值

public class ClientSocketNet extends  SocketNet{

    private Register register ;

    private LoadBalance loadBalance ;

//    private  Socket socket  ;
//    答案是不行的,因爲我們ClientSocketNet 是個單例對象,那單例對象裏面的屬性也是唯一的,若有線程給它賦值後,它就有值了
    // 和springmvc 裏面的對象的變量是一個道理 // 怎麼解決springMVC 裏面變量的安全問題?
    /**
     * 輸出流用於發送一個數據
     * @return
     */
    @Override
    protected OutputStream getOutputStream() {
        // 利用ThreadLocal 取出當前要調用的服務的名稱
        String serviceName = (String)ThreadLocalUtil.get("serviceName");

        /**
         * 服務的發現
         */
        List<String> servers = register.discovery(serviceName);

        /**
         * 服務的負載均衡
         */
        String server = loadBalance.loadbalance(servers);
        String[] serverAddress = server.split(":");

        OutputStream outputStream = null;
        Socket socket = null ;
        try {
            socket = new Socket(serverAddress[0],Integer.valueOf(serverAddress[1])); //ip :port ->服務的地址-> 服務的發現+ 負載均衡
            outputStream = socket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 把Socket 存儲起來 TODO
        // 使用ThreadLocal 做
         ThreadLocalUtil.set("client",socket); // 往線程裏面設置值
        return outputStream;
    }

    @Override
    protected InputStream getInputStream() {
        Socket socket = (Socket)ThreadLocalUtil.get("client") ;//  // 從ThreadLocal 裏面獲取Socket
        InputStream inputStream = null;
        try {
            inputStream = socket.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return inputStream;
    }
}

4.7 ServerSocketNet的實現
在這裏插入圖片描述

/**
 * serverSocket
 * ServerSocket 只需要一個就有可以了,不管有多少個客戶端,只需要一個ServerSocket 就可以了
 * 在服務端和客戶不同的點,是服務端需要監聽
 *
 */
public class ServerSocketNet extends  SocketNet{

    private Coder coder ;

    private ServerSocket serverSocket ;

    private MethodInvoker methodInvoker ;

    public ServerSocketNet(int port){
        try {
            this.serverSocket = new ServerSocket(port) ;
            // 服務端一啓動就需要監聽,而且是死循環的監聽
            while(true){
                listener();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void listener(){
        Socket socket = null ;
        try {
            socket  = this.serverSocket.accept(); // 代表監聽
            // 該socket 就是客戶端 , 客戶端會被請求對象發送過來,我們在本地調用成功後,又把該結果發個client
            ThreadLocalUtil.set("client",socket); // 把該變量放在線程裏面
            byte[] read = read(); // 調用父類裏面的讀取數據的方法,因爲服務的消費者要把請求對象發送過來
            Request request = (Request)coder.deCode(read);
            // 服務的提供者裏面,需要本地調用
            Object result = methodInvoker.invoker(request);
            write(coder.code(result)); // 向服務的消費者寫答案過去
            // 一次調用結束
            ThreadLocalUtil.clear(); // 調用結束,釋放線程裏面的數據
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(socket!=null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    socket = null ;
                }
            }
        }
    }

    @Override
    protected OutputStream getOutputStream() {
        Socket client =(Socket) ThreadLocalUtil.get("client");// 把該變量放在線程裏面
        OutputStream outputStream = null;
        try {
            outputStream = client.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return outputStream;
    }

    /**
     * 獲取輸入流
     * @return
     */
    @Override
    protected InputStream getInputStream() {
        Socket client = (Socket)ThreadLocalUtil.get("client");
        InputStream inputStream = null;
        try {
            inputStream = client.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return inputStream;
    }
}

五、完成和spring的整合(重點內容)

在這裏插入圖片描述
5.1 使用ExposeService 註解標記的類,我們怎麼獲取到它

5.2.1 修改ExposeService 註解,讓Spring 來管理該對象

@Service @Controller
在這裏插入圖片描述

5.2.2 服務提供者和spring 整合的類

public class ProviderSpringStater implements ApplicationContextAware , InitializingBean {

    private ApplicationContext applicationContext ; // 這就是ioc

    private Register register ;

    private String ipAddr ;

    private int port ;

    Map<String,String> regiesters = new HashMap<>();

    /**
     * ioc 容器裏面的對象已經創建完畢了 我們使用ExposeService 標記的類,它的對象也創建完畢了
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext ;
        // 取出使用ExposeService 標記的對象
        /**
         * String 代表對象的名稱
         * Object:代表實例對象
         * 使用ExposeService標記的類,我們需要把他註冊到註冊中心上面
         * 註冊時需要2 個屬性
         * 1 服務的名稱  一般就是接口的名稱
         * 2 服務的地址
         */
        Map<String, Object> beansWithAnnotation = this.applicationContext.getBeansWithAnnotation(ExposeService.class);
        if(!beansWithAnnotation.isEmpty()){
            beansWithAnnotation.forEach((name,object)->{
                Class<?> interfacee = object.getClass().getInterfaces()[0];
                regiesters.put(interfacee.getName(),ipAddr + ":" +port) ;// 把以後要註冊到註冊中心裏面的服務都放在map 集合裏面
                /**
                 * 爲什麼不直接註冊呢?
                 * 因爲現在對象的屬性好沒有注入,如果你的服務已經上線了,但是你的功能實現不了,那就是ppt的模型而已
                 * 那什麼時候註冊:
                 * 我們完事具備後,才上線,讓別人使用
                 */
            });
        }

    }

    /**
     * 就是當ioc 容器裏面所以的屬性都被注入完畢後執行的方法,那代表我們已經準備好了
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        if(!this.regiesters.isEmpty()){ // 不管你list 還是map ,都有foreach
            this.regiesters.forEach((serviceName,serviceAddress)->{
                register.register(serviceName,serviceAddress); // 完成註冊了
            });
        }
        /**
         * 服務註冊完畢後幹啥?
         * 就開始要幹活了,就有開始監聽了
         */
        new Thread(
                ()->{
                    new ServerSocketNet(port); // 我們ServerSocketNet 有個死循環,代碼還能往下走嗎?不能走了,那有什麼問題發生
                }
        ).start();

    }
}

老師,我們寫代碼的風格不一樣,可以,只要你掌握思想,你怎麼都行。我教你你的只是思想!

5.2 @Ref的完成屬性注入實現
@Autowired

代表屬性的注入,那怎麼注入屬性,就有利用 BeanPostProcessor
public class ConsumerProxyInject implements BeanPostProcessor {


   /**
    *   <bean init-method=""></bean>
    *  初始化方法之前執行
    * @param bean
    * @param beanName
    * @return
    * @throws BeansException
    */
   @Override
   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
       return bean; // 這裏不能返回null
   }
   /**
    * 我們注入代理對象就在init-method 方法之後執行
    * 初始化方法之後
    * @param bean
    * @param beanName
    * @return
    * @throws BeansException
    */
   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
       // 只有使用@Ref 標記的屬性,我們要給他注入一個代理的對象
       Class<?> clazz = bean.getClass();
       Field[] fields = clazz.getDeclaredFields();
       for (Field field : fields) {
           Ref ref = field.getAnnotation(Ref.class);
           if(ref!=null){ // 該屬性由ref 標記
               field.setAccessible(true); // 暴力反射
               Class<?> type = field.getType();
               try {
                   field.set(bean, ProxyUtil.createProxy(type)); // ref 注入的是代理對象
               } catch (IllegalAccessException e) {
                   e.printStackTrace();
               }
               field.setAccessible(false);// 開啓安全策略
           }
       }

       return bean;
   }
}

六、代碼的完善

6.1 本地調用獲取ioc 容器LocalMethodInvoker
在這裏插入圖片描述
6.2 遠程方法的調用裏面RpcMethodInvoker
在這裏插入圖片描述
6.3 ClientSocketNet 裏面
單例的實現:
在這裏插入圖片描述
在這裏插入圖片描述

6.4 ProxyUtil 裏面
在這裏插入圖片描述

6.5 RpcMethodInvoker
在這裏插入圖片描述

6.6 使用ThreadLocal 獲取服務名稱的地方
在這裏插入圖片描述

6.7 ProviderSpringStater的補充
在這裏插入圖片描述

6.8 ServerSocketNet的補充
在這裏插入圖片描述
6.9 LocaMethodInvoker的補充
在這裏插入圖片描述

6.10 LocalMethodInvoker 的ioc 的獲取

因爲LocalMethodInvoker 是我們自己new ,導致,setApplicationContext不執行,我們獲取不到ioc 容器。
我們在ProviderSpringStater 添加一個:
在這裏插入圖片描述
,就可以使用這裏面的了
在這裏插入圖片描述

七、開始測試

7.1 修改provider.xml 文件
在這裏插入圖片描述

7.2 啓動測試
在這裏插入圖片描述

服務提供者沒有任何問題

7.3 修改conusmer.xml
在這裏插入圖片描述

7.4 嘗試遠程調用的實現
在這裏插入圖片描述

7.5 bug的修復
在這裏插入圖片描述

爲啥長度只有288 個,已經讀取完了,爲啥還再次循環讀了
在這裏插入圖片描述

讀取的最後一個不是-1,導致又循環讀取了
我們需要給他添加一個結束的標記

在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述
我多加了一個-1 ,對我反序列化又影響嗎?
沒有影響,因爲-1 在計算機是個非常特殊的標識,我們的程序讀取到-1 ,一般任務它已經結束了,而且會把-1 去掉

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章