基於 SpringBoot 手寫 RPC 框架

文件目錄

在這裏插入圖片描述

Message

傳遞的消息載體類

package com.shen.api;

import lombok.Builder;
import lombok.Data;

import java.io.Serializable;

@Data
@Builder
public class Message implements Serializable {
	//服務接口名
    private String className;
    //方法名
    private String methodName;
    //參數
    private Object[] args;
    //接口類型
    private Class[] types;
}

consumer

服務消費方

@EnableRpcConsumer

打在SpringBoot啓動類上

package com.shen.api.consumer;

import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({ReferenceInvokeProxy.class,RpcHandler.class})
public @interface EnableRpcConsumer {
}

@Reference

打在注入的接口上

package com.shen.api.consumer;

import org.springframework.stereotype.Component;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Reference {
}

ReferenceInvokeProxy

對於每個有 @Reference 註解的接口,生成代理

package com.shen.api.consumer;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;

public class ReferenceInvokeProxy implements BeanPostProcessor {

    @Autowired
    RpcHandler invocationHandler;


    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Field[] fields=bean.getClass().getDeclaredFields();
        for(Field field:fields){
            if(field.isAnnotationPresent(Reference.class)){
                field.setAccessible(true);
                Object proxy= Proxy.newProxyInstance(field.getType().getClassLoader(),new Class<?>[]{field.getType()},invocationHandler);
                try {
                    field.set(bean,proxy);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
        return bean;
    }
}

RpcHandler

讓接口生成代理

調用服務的方法,實際上是在進行 BIO 通信

通過在 application.properties 配置 provider.host,provider.port,確定提供方主機號、端口號

package com.shen.api.consumer;

import com.shen.api.Message;
import org.springframework.beans.factory.annotation.Value;

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

public class RpcHandler implements InvocationHandler {

    @Value("${provider.host}")
    private String host;
    @Value("${provider.port}")
    private int port;



    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Socket socket = new Socket(host, port);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
        Message message = Message.builder()
                .className(method.getDeclaringClass().getName())
                .methodName(method.getName())
                .args(args)
                .types(method.getParameterTypes()).build();
        objectOutputStream.writeObject(message);
        objectOutputStream.flush();
        ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
        return objectInputStream.readObject();

    }
}

provider

服務提供方

@EnableRpcProvider

打在SpringBoot啓動類上

package com.shen.api.provider;

import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({SocketServerInitial.class,InitialMediator.class,})
public @interface EnableRpcProvider {

}

@Service

打在提供的服務實現類上

package com.shen.api.provider;

import org.springframework.stereotype.Component;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface Service {

}

BeanMethod

封裝所有提供的類和方法

package com.shen.api.provider;

import lombok.Data;

import java.lang.reflect.Method;

@Data
public class BeanMethod {
    private Object bean;

    private Method method;

}

InitialMediator

對於每個打了自定義的 @Service 的實例化後的 Bean,遍歷所有方法,以"接口名.方法名"爲key,存儲實現類類名和方法名的封裝到 map 中

package com.shen.api.provider;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

import java.lang.reflect.Method;

public class InitialMediator implements BeanPostProcessor {


    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if(bean.getClass().isAnnotationPresent(Service.class)){
            Method[] methods=bean.getClass().getDeclaredMethods();
            for(Method method:methods){
                String key=bean.getClass().getInterfaces()[0].getName()+"."+method.getName();
                BeanMethod beanMethod=new BeanMethod();
                beanMethod.setBean(bean);
                beanMethod.setMethod(method);
                Mediator.map.put(key,beanMethod);
            }
        }
        return bean;
    }
}

Mediator

"接口名.方法名"爲key,存儲實現類類名和方法名的封裝

根據實現類類名和方法名,反射調用本地方法,給出返回值

package com.shen.api.provider;

import com.shen.api.Message;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Mediator {


    public static Map<String ,BeanMethod> map=new ConcurrentHashMap();

    private volatile static Mediator instance;

    private Mediator(){}

    public static Mediator getInstance(){
        if(instance==null){
            synchronized (Mediator.class){
                if(instance==null){
                    instance=new Mediator();
                }
            }
        }
        return instance;
    }

    public  Object process(Message message){
        String key=message.getClassName()+"."+message.getMethodName();
        BeanMethod beanMethod=map.get(key);
        if(beanMethod==null){
            return null;
        }
        Object bean=beanMethod.getBean();
        Method method=beanMethod.getMethod();
        try {
            return method.invoke(bean,message.getArgs());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

}

ServiceHandler

僞異步處理 Message

package com.shen.api.provider;

import com.shen.api.Message;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

public class ServiceHandler implements Runnable {

    private Socket socket;

    public ServiceHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        ObjectInputStream objectInputStream = null;
        ObjectOutputStream outputStream = null;
        try {
            objectInputStream = new ObjectInputStream(socket.getInputStream());
            Message message = (Message) objectInputStream.readObject();

            Mediator mediator=Mediator.getInstance();
            Object rs=mediator.process(message);

            outputStream = new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(rs);
            outputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(objectInputStream != null) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(outputStream != null) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}

SocketServerInitial

容器 Refresh 後,調用此方法監聽請求。

通過在 application.properties 配置 provider.port,確定提供方端口號。

package com.shen.api.provider;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


public class SocketServerInitial implements ApplicationListener<ContextRefreshedEvent> {

    private final ExecutorService executorService= Executors.newCachedThreadPool();

    @Value("${provider.port}")
    private int port;


    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {

        ServerSocket serverSocket=null;
        try {
            serverSocket=new ServerSocket(port);
            while(true){
                Socket socket=serverSocket.accept(); 
                executorService.execute(new ServiceHandler(socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(serverSocket!=null){
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

示例

consumer

在這裏插入圖片描述

啓動類

package com.shen.consumer;

import com.shen.api.consumer.EnableRpcConsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan("com.shen.consumer")
@EnableRpcConsumer
public class BootStrap {
    public static void main(String[] args) {
        SpringApplication.run(BootStrap.class,args);
    }

}

配置文件

provider.host = localhost
provider.port = 8888

server.port=8080

TestController

package com.shen.consumer;

import com.shen.api.ExampleService;
import com.shen.api.consumer.Reference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @Reference
    ExampleService exampleService;

    @GetMapping("/test")
    public String test(){
        return exampleService.info();
    }
    
}

provider

在這裏插入圖片描述

啓動類

package com.shen.provider;


import com.shen.api.provider.EnableRpcProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan("com.shen.provider")
@EnableRpcProvider
public class BootStrap {
    public static void main(String[] args) {
        SpringApplication.run(BootStrap.class,args);
    }

}

配置文件

provider.port = 8888

server.port=8081

ExampleServiceImpl

package com.shen.provider.service;

import com.shen.api.ExampleService;
import com.shen.api.provider.Service;


@Service
public class ExampleServiceImpl implements ExampleService {
    public String info() {
        return "example";
    }
}

結果

在這裏插入圖片描述

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