Java 手寫一個RPC框架
RPC框架稱為遠端呼叫框架,其實現的核心原理就是消費者端使用動態代理來代理一個介面的方法(基於JDK的動態代理,當然如果使用CGLib可以直接使用無介面類別的方法),透過加入網路傳輸編程,傳輸呼叫介面方法名稱,方法參數來給提供者獲取,再透過反射,來執行該介面的方法,再將反射執行的結果透過網路程式傳回消費者端。
現在我們來依序實作這些概念。這裡我們做最簡單的實作,網路程式設計使用的是BIO,大家可以使用Reactor模式的Netty來改寫效能更好的方式。而網路傳輸所使用的序列化和反序列化也是Java自帶的,當然這樣的傳輸位元組比較大,可以用google的protoBuffer或是kryo來處理。 這裡只為了方便說明原理。
pom
<?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.guanjian</groupId> <artifactId>rpc-framework</artifactId> <version>1.0-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
首先當然是我們要進行遠端呼叫的介面以及介面的方法。
public interface HelloService { String sayHello(String content);}
介面實作類別
public class HelloServiceImpl implements HelloService { public String sayHello(String content) { return "hello," + content; } }
消費者端的動態代理,如果你是把提供者和消費者寫在兩個工程中,則提供者端需要上面的接口和實作類,而消費者端只需要上面的介面。
public class ConsumerProxy { /** * 消费者端的动态代理 * @param interfaceClass 代理的接口类 * @param host 远程主机IP * @param port 远程主机端口 * @param <T> * @return */ @SuppressWarnings("unchecked") public static <T> T consume(final Class<T> interfaceClass,final String host,final int port) { return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, (proxy,method,args) -> { //创建一个客户端套接字 Socket socket = new Socket(host, port); try { //创建一个对外传输的对象流,绑定套接字 ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); try { //将动态代理的方法名写入对外传输的对象流中 output.writeUTF(method.getName()); //将动态代理的方法的参数写入对外传输的对象流中 output.writeObject(args); //创建一个对内传输的对象流,绑定套接字 //这里是为了获取提供者端传回的结果 ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); try { //从对内传输的对象流中获取结果 Object result = input.readObject(); if (result instanceof Throwable) { throw (Throwable) result; } return result; } finally { input.close(); } } finally { output.close(); } } finally { socket.close(); } } ); } }
有關JDK動態代理的內容可以參考AOP原理與自實現,BIO的部分可以參考傳統IO與NIO比較
提供者端的網路傳輸與遠端方式呼叫服務
public class ProviderReflect { private static final ExecutorService executorService = Executors.newCachedThreadPool(); /** * RPC监听和远程方法调用 * @param service RPC远程方法调用的接口实例 * @param port 监听的端口 * @throws Exception */ public static void provider(final Object service,int port) throws Exception { //创建服务端的套接字,绑定端口port ServerSocket serverSocket = new ServerSocket(port); while (true) { //开始接收客户端的消息,并以此创建套接字 final Socket socket = serverSocket.accept(); //多线程执行,这里的问题是连接数过大,线程池的线程数会耗尽 executorService.execute(() -> { try { //创建呢一个对内传输的对象流,并绑定套接字 ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); try { try { //从对象流中读取接口方法的方法名 String methodName = input.readUTF(); //从对象流中读取接口方法的所有参数 Object[] args = (Object[]) input.readObject(); Class[] argsTypes = new Class[args.length]; for (int i = 0;i < args.length;i++) { argsTypes[i] = args[i].getClass(); } //创建一个对外传输的对象流,并绑定套接字 //这里是为了将反射执行结果传递回消费者端 ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); try { Class<?>[] interfaces = service.getClass().getInterfaces(); Method method = null; for (int i = 0;i < interfaces.length;i++) { method = interfaces[i].getDeclaredMethod(methodName,argsTypes); if (method != null) { break; } } Object result = method.invoke(service, args); //将反射执行结果写入对外传输的对象流中 output.writeObject(result); } catch (Throwable t) { output.writeObject(t); } finally { output.close(); } } catch (Exception e) { e.printStackTrace(); } finally { input.close(); } } finally { socket.close(); } } catch (Exception e) { e.printStackTrace(); } }); } } }
啟動提供者端的網路偵聽和遠端呼叫
public class RPCProviderMain { public static void main(String[] args) throws Exception { HelloService service = new HelloServiceImpl(); ProviderReflect.provider(service,8083); } }
啟動消費者的動態代理呼叫
public class RPCConsumerMain { public static void main(String[] args) throws InterruptedException { HelloService service = ConsumerProxy.consume(HelloService.class,"127.0.0.1",8083); for (int i = 0;i < 1000;i++) { String hello = service.sayHello("你好_" + i); System.out.println(hello); Thread.sleep(1000); } } }
運行結果
hello,你好_0
hello,你好_1
hello,你好_2
hello,你好_3
hello,你好_4
hello,你好_5
.....
如果你要擴充成一個Netty ProtoBuffer的高效能RPC框架可以參考Netty整合Protobuffer 的相關寫法。
推薦教學:《PHP》
以上是Java 手寫一個RPC框架的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undress AI Tool
免費脫衣圖片

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Stock Market GPT
人工智慧支援投資研究,做出更明智的決策

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

首先啟用UC瀏覽器內置縮放功能,進入設置→瀏覽設置→字體與排版或頁面縮放,選擇預設比例或自定義百分比;其次可通過雙指張開或捏合手勢強制調整頁面顯示大小;對於限制縮放的網頁,可請求桌面版網站以解除限制;高級用戶還可通過在地址欄執行JavaScript代碼修改viewport屬性,實現更靈活的強制縮放效果。

答案是使用Thread.currentThread().getStackTrace()獲取調用方法名,通過索引2得到調用anotherMethod的someMethod名稱,因索引0為getStackTrace、1為當前方法、2為調用者,示例輸出“Calledbymethod:someMethod”,也可用Throwable實現,但需注意性能、混淆、安全及內聯影響。

Optional類用於安全地處理可能為null的值,避免空指針異常。 1.使用Optional.ofNullable創建實例,可處理null值。 2.通過isPresent或ifPresent安全檢查和訪問值,避免直接調用get導致異常。 3.利用orElse、orElseGet提供默認值,或使用orElseThrow拋出自定義異常。 4.通過map和filter鍊式操作轉換或過濾值,提升代碼可讀性和健壯性。

使用getClass()方法可獲取對象的運行時類,如str.getClass()返回Class對象;對於類型可直接使用String.class語法。 Class類提供getName()、getSimpleName()等方法獲取類信息,例如num.getClass().getSimpleName()輸出Integer。

ThereplacemethodinJavareturnsanewstringwithalloccurrencesofspecifiedcharactersorsequencesreplaced.Ithastwoforms:oneforreplacingsinglecharactersandanotherforsubstrings.Sincestringsareimmutable,theoriginalremainsunchanged.Forexample,"helloworld&qu

atwo-dimensionalarayinjavaisanarrayofarrays,宣布Withtwobrackets,例如[] [] [] [] m atrix,and canbeinitializedwithvaluesorusisionnew; forexample,int [] [] [] [] [] [] matrix = {{1,2},{1,2},{3,4}}}}; createSa3x2matrix。

volatile確保多線程環境下變量的可見性,適用於簡單標誌位如boolean開關,不適用複合操作;其通過禁止指令重排序並建立happens-before關係,保證寫操作對其他線程立即可見,但不提供原子性,需配合Atomic類或鎖處理複雜並發。

thecurrentworkingdirectoryinjavacanbeobtainedusystem.getProperty(“ user.dir”),whoturnsthearsthearstheasthearstheabsolutepathwherethetheretheprogramwaslaunched; or of paths.gets.gets.get(“”)。 toabsolutepath(“)
