手寫RPC框架V1
gitee地址:https://gitee.com/kylin1991_admin/rpc_handwriting
Api.jar
1、寫一個IHelloService接口,類似dubbo
public interface IHelloService {
String sayHello(String context);
}
2、一個RpcRequest請求模型
@Data
public class RpcRequest implements Serializable {
private String className;
private String methodName;
private Object[] parameters;
}
Server
1、Server 服務註冊
public class Server {
public static void main(String[] args) {
IHelloService helloService = new IHelloServiceImpl();
RpcProxyServer rpcProxyServer = new RpcProxyServer(helloService,8080);
rpcProxyServer.publisher();
}
}
2、RpcProxyServer 服務發佈
public class RpcProxyServer {
private Object service;
private int port;
public RpcProxyServer(Object service, int port) {
this.service = service;
this.port = port;
}
public void publisher() {
try {
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
Socket socket = serverSocket.accept();
executor.execute(new ProcessorHandler(service,socket));
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static final Executor executor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
Runtime.getRuntime().availableProcessors(),
60L,
TimeUnit.MINUTES,
new ArrayBlockingQueue<>(Runtime.getRuntime().availableProcessors()),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
}
3、請求處理ProcessorHandler
public class ProcessorHandler implements Runnable {
private Object service;
private Socket socket;
public ProcessorHandler(Object service, Socket socket) {
this.service = service;
this.socket = socket;
}
@Override
public void run() {
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream())) {
// 接受請求
RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();
// 反射調用並且返回結果
objectOutputStream.writeObject(invoke(rpcRequest));
objectOutputStream.flush();
} catch (IOException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private Object invoke(RpcRequest rpcRequest, Object service) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class clazz = Class.forName(rpcRequest.getClassName());
Class<?>[] parametersTypes = new Class<?>[rpcRequest.getParameters().length];
for (int i = 0; i < parametersTypes.length; i++) {
parametersTypes[i] = rpcRequest.getParameters()[i].getClass();
}
Method method = clazz.getMethod(rpcRequest.getMethodName(), parametersTypes);
return method.invoke(service, rpcRequest.getParameters());
}
}
Client
1、Client 發起請求
public class Client {
public static void main(String[] args) {
RpcProxyClient rpcProxyClient = new RpcProxyClient();
IHelloService helloService = rpcProxyClient.proxyClient(IHelloService.class,"localhost",8080);
System.out.println(helloService.sayHello("lilei"));;
}
}
2、遠程調用(採用jdk動態代理)
- RpcProxyClient
public class RpcProxyClient {
public <T> T proxyClient(Class<?> interfacesCls, String host, int port) {
return (T) Proxy.newProxyInstance(
interfacesCls.getClassLoader(),
new Class<?>[]{interfacesCls},
new RemoteInvocationHandler(host,port));
}
}
- RemoteInvocationHandler 用於遠程方法調用
public class RemoteInvocationHandler implements InvocationHandler {
private String host;
private int port;
public RemoteInvocationHandler(String host, int port) {
this.host = host;
this.port = port;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//包裝請求
RpcRequest rpcRequest = new RpcRequest();
rpcRequest.setClassName(method.getDeclaringClass().getName());
rpcRequest.setMethodName(method.getName());
rpcRequest.setParameters(args);
//發送請求,並且獲得結果
RpcNetTransport rpcNetTransport = new RpcNetTransport(host, port);
return rpcNetTransport.send(rpcRequest);
}
}
3、RpcNetTransport 調用
public class RpcNetTransport {
private String host;
private int port;
public RpcNetTransport(String host, int port) {
this.host = host;
this.port = port;
}
public Object send(RpcRequest rpcRequest) {
Object result = null;
try (Socket socket = new Socket(host, port);
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) {
objectOutputStream.writeObject(rpcRequest);
objectOutputStream.flush();
result = objectInputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return result;
}
}
測試
1、啓動Server
2、啓動Client
測試成功!
問題
- 是否可以用註解來實現?
手寫RPC框架V2
- 通過註解來實現服務發佈,和服務調用
重寫Server
1、加入spring的容器
<!-- 爲了方便用spring的註解 Component -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
2、創建配置類SpringConfig
@Configuration
public class SpringConfig {
@Bean(name = "rpcServer")
public RpcServer rpcServer() {
return new RpcServer(8080);
}
}
3、創建註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component // 被Spring進行掃描,並管理
public @interface RpcService {
Class<?> value();
}
4、RpcServer類
- 實現ApplicationContextAware
- 實現InitializingBean
- setApplicationContext 中獲取被RpcService註解修飾的類。然後根據類引用路徑作爲name,對象作爲值,保存到一個map中
- 把map作爲傳入參數傳入線程processor
@Configuration
public class RpcProxyServer implements ApplicationContextAware, InitializingBean {
private static final Executor executor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(),
Runtime.getRuntime().availableProcessors(),
60L,
TimeUnit.MINUTES,
new ArrayBlockingQueue<>(Runtime.getRuntime().availableProcessors()),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
private int port;
private Map<String, Object> handlerMap = new HashMap<>();
public RpcServer(int port) {
this.port = port;
}
@Override
public void afterPropertiesSet() throws Exception {
try {
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
Socket socket = serverSocket.accept();
executor.execute(new ProcessorHandler(handlerMap, socket));
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(RpcService.class);
if (!serviceBeanMap.isEmpty()) {
for (Object serviceBean : serviceBeanMap.values()) {
RpcService rpcService = serviceBean.getClass().getAnnotation(RpcService.class);
String serviceName = rpcService.value().getName();
handlerMap.put(serviceName, serviceBean);
}
}
}
}
5、修改ProcessorHandler
- 接受handlerMap集合
- 根據類名從集合中取出參數
public class ProcessorHandler implements Runnable {
private Socket socket;
private Map<String, Object> handlerMap;
public ProcessorHandler(Map<String, Object> handlerMap, Socket socket) {
this.handlerMap = handlerMap;
this.socket = socket;
}
@Override
public void run() {
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream())) {
// 接受請求
RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();
// 反射調用並且返回結果
objectOutputStream.writeObject(invoke(rpcRequest));
objectOutputStream.flush();
} catch (IOException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private Object invoke(RpcRequest rpcRequest) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
String className = rpcRequest.getClassName();
Object service = handlerMap.get(className);
if (service == null) {
throw new RuntimeException("service not found :" + className);
}
Class clazz = Class.forName(className);
Class<?>[] parametersTypes = new Class<?>[rpcRequest.getParameters().length];
for (int i = 0; i < parametersTypes.length; i++) {
parametersTypes[i] = rpcRequest.getParameters()[i].getClass();
}
Method method = clazz.getMethod(rpcRequest.getMethodName(), parametersTypes);
return method.invoke(service, rpcRequest.getParameters());
}
}
6、測試是否可行
@ComponentScan("org.example")
public class Server {
public static void main( String[] args ) {
AnnotationConfigApplicationContext configApplicationContext =new AnnotationConfigApplicationContext(Server.class);
}
}
重寫客戶端
1、加入spring的容器
<!-- 爲了方便用spring的註解 Component -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
2、修改RpcProxyClient
public class RpcProxyClient {
private final String host;
private final int port;
public RpcProxyClient(String host, int port) {
this.host = host;
this.port = port;
}
public <T> T proxyClient(Class<?> interfacesCls) {
return (T) Proxy.newProxyInstance(interfacesCls.getClassLoader(), new Class<?>[]{interfacesCls}, new RemoteInvocationHandler(host, port));
}
}
3、創建SpringConfig
@Configuration
public class SpringConfig {
private static final RpcProxyClient PROXY_CLIENT = new RpcProxyClient("localhost", 8080);
@Bean
public IHelloService iHelloService() {
return PROXY_CLIENT.proxyClient(IHelloService.class);
}
}
4、測試是否可行
@ComponentScan("org.example")
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Client.class);
IHelloService iHelloService = context.getBean(IHelloService.class);
String result = iHelloService.sayHello("lilei");
System.out.println(result);
}
}
備註,如果客戶端向實現更簡化的註解,那麼可以利用BeanDefinition來實現,具體看鏈接
如何把接口加入到Spring容器(任何類/接口都可以用這個方式,例如動態代理)
支持多版本的實現
//todo 有時間在弄吧