├── .gitignore ├── LICENSE ├── README.md ├── pom.xml ├── rpc-client ├── .DS_Store ├── pom.xml └── src │ └── main │ └── java │ └── cn │ └── xpleaf │ └── rpc │ └── client │ ├── discovery │ └── ServiceDiscovery.java │ ├── netty │ └── RPCClient.java │ └── proxy │ └── RPCProxy.java ├── rpc-common ├── pom.xml └── src │ └── main │ └── java │ └── cn │ └── xpleaf │ └── rpc │ └── common │ ├── pojo │ ├── RPCRequest.java │ └── RPCResponse.java │ └── utils │ ├── RPCDecoder.java │ ├── RPCEncoder.java │ └── SerializationUtil.java └── rpc-server ├── pom.xml └── src └── main └── java └── cn └── xpleaf └── rpc └── server ├── annotation └── RPCService.java ├── netty ├── RPCServer.java └── RPCServerHandler.java └── registry └── ServiceRegistry.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # 过滤不必要的目录和文件 26 | *.iml 27 | .idea 28 | rpc-common/target/maven-archiver/pom.properties 29 | rpc-server/target/maven-archiver/pom.properties 30 | rpc-client/target/maven-archiver/pom.properties -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minidubbo 2 | A Full RPC Framework Based on Netty. 3 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | cn.xpleaf.rpc 8 | minidubbo 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | rpc-common 13 | rpc-server 14 | rpc-client 15 | 16 | 17 | minidubbo 18 | 19 | http://www.example.com 20 | 21 | 22 | 23 | UTF-8 24 | 4.12 25 | 4.2.4.RELEASE 26 | 4.1.21.Final 27 | 1.1.3 28 | 3.4.7 29 | 1.7.10 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework 38 | spring-context 39 | ${spring.version} 40 | 41 | 42 | org.springframework 43 | spring-beans 44 | ${spring.version} 45 | 46 | 47 | org.springframework 48 | spring-webmvc 49 | ${spring.version} 50 | 51 | 52 | org.springframework 53 | spring-jdbc 54 | ${spring.version} 55 | 56 | 57 | org.springframework 58 | spring-aspects 59 | ${spring.version} 60 | 61 | 62 | org.springframework 63 | spring-jms 64 | ${spring.version} 65 | 66 | 67 | org.springframework 68 | spring-context-support 69 | ${spring.version} 70 | 71 | 72 | 73 | io.netty 74 | netty-all 75 | ${netty.version} 76 | 77 | 78 | 79 | com.dyuproject.protostuff 80 | protostuff-core 81 | ${protostuff.version} 82 | 83 | 84 | com.dyuproject.protostuff 85 | protostuff-runtime 86 | ${protostuff.version} 87 | 88 | 89 | 90 | org.apache.zookeeper 91 | zookeeper 92 | ${zookeeper.version} 93 | 94 | 95 | 96 | org.slf4j 97 | slf4j-api 98 | ${log4j.version} 99 | 100 | 101 | org.slf4j 102 | slf4j-log4j12 103 | ${log4j.version} 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | junit 113 | junit 114 | 4.12 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | org.apache.maven.plugins 123 | maven-compiler-plugin 124 | 2.3.2 125 | 126 | UTF-8 127 | 1.8 128 | 1.8 129 | true 130 | 131 | 132 | 133 | 134 | maven-assembly-plugin 135 | 136 | 137 | 138 | jar-with-dependencies 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | make-assembly 152 | package 153 | 154 | single 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /rpc-client/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xpleaf/minidubbo/1ab502dd623a8bcfe8de786ecd8db09ebc1ce2c6/rpc-client/.DS_Store -------------------------------------------------------------------------------- /rpc-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | minidubbo 7 | cn.xpleaf.rpc 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | rpc-client 13 | 14 | rpc-client 15 | 16 | http://www.example.com 17 | 18 | 19 | UTF-8 20 | 21 | 22 | 23 | 24 | cn.xpleaf.rpc 25 | rpc-common 26 | 1.0-SNAPSHOT 27 | 28 | 29 | 30 | org.apache.zookeeper 31 | zookeeper 32 | 33 | 34 | 35 | org.slf4j 36 | slf4j-api 37 | 38 | 39 | org.slf4j 40 | slf4j-log4j12 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /rpc-client/src/main/java/cn/xpleaf/rpc/client/discovery/ServiceDiscovery.java: -------------------------------------------------------------------------------- 1 | package cn.xpleaf.rpc.client.discovery; 2 | 3 | import java.util.List; 4 | import java.util.Random; 5 | import java.util.concurrent.CountDownLatch; 6 | 7 | import org.apache.zookeeper.KeeperException; 8 | import org.apache.zookeeper.WatchedEvent; 9 | import org.apache.zookeeper.Watcher; 10 | import org.apache.zookeeper.ZooKeeper; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * 服务发现类,用于向zookeeper中查询服务提供者的地址(host:port) 暂时的设计思路是,每执行一次调用都会进行查询,而不是像dubbo那样 16 | * 会将interfaceName和服务地址缓存起来,后面会实现这一点 17 | * 18 | * 目前已经实现了负载均衡服务的功能,算法为随机负载均衡,即如果服务提供者有3个,会随机返回其中一个服务提供者的地址信息 19 | * 20 | * 另外,显然我这里都是使用zookeeper较为原生的API,原因很简单,当初入手zookeeper API时就是先从原生的学起,之后就直接应用在minidubbo上, 21 | * 在我的另外一个项目中[分布式爬虫系统],使用的是较为高层次的API,即curator,如果有兴趣,可以参考一下使用方式:https://github.com/xpleaf/ispider 22 | * 23 | * @author yeyonghao 24 | */ 25 | public class ServiceDiscovery { 26 | 27 | // zookeeper中保存服务信息的父节点 28 | private final String parentNode = "/minidubbo"; 29 | // zookeeper的地址,由spring构造ServiceDiscovery对象时传入 30 | private String registryAddress; 31 | // 连接zookeeper的超时时间 32 | private int sessionTimeout = 2000; 33 | // 连接zookeeper的客户端 34 | private ZooKeeper zkClient = null; 35 | // 用来确保zookeeper连接成功后才进行后续的操作 36 | private CountDownLatch latch = new CountDownLatch(1); 37 | // log4j日志记录 38 | private Logger logger = LoggerFactory.getLogger(ServiceDiscovery.class); 39 | 40 | /** 41 | * 构造方法 42 | * 43 | * @param registryAddress zookeeper的地址,格式为 host:port 44 | */ 45 | public ServiceDiscovery(String registryAddress) { 46 | this.registryAddress = registryAddress; 47 | } 48 | 49 | /** 50 | * 发现服务方法 根据接口名称向zookeeper查询服务提供者的地址 51 | * 52 | * @param interfaceName 接口名称 53 | * @return serverAddress服务提供者的地址,格式为 host:port 如果不存在,则返回null 54 | */ 55 | public String discoverService(String interfaceName) { 56 | // 如果zkClient为null,则连接未建立,先建立连接 57 | if (this.zkClient == null) { 58 | logger.info("未连接zookeeper,准备建立连接..."); 59 | connectServer(); 60 | } 61 | // 构建需要查询的节点的完整名称 62 | String node = parentNode + "/" + interfaceName; 63 | // 获取该节点所对应的服务提供者地址 64 | logger.info("zookeeper连接建立完毕,准备获取服务提供者地址[{}]...", node); 65 | String serverAddress = getServerAddress(node); 66 | logger.info("服务提供者地址获取完毕[{}]...", serverAddress); 67 | // 返回结果 68 | return serverAddress; 69 | } 70 | 71 | /** 72 | * 建立连接 73 | */ 74 | private void connectServer() { 75 | try { 76 | zkClient = new ZooKeeper(registryAddress, sessionTimeout, new Watcher() { 77 | 78 | // 注册监听事件,连接成功后会调用process方法 79 | // 此时再调用latch的countDown方法使CountDownLatch计数器减1 80 | // 因为构造CountDownLatch对象时设置的值为1,减1后变为0,所以执行该方法后latch.await()将会中断 81 | // 从而确保连接成功后才会执行后续zookeeper的相关操作 82 | @Override 83 | public void process(WatchedEvent event) { 84 | // 如果状态为已连接,则使用CountDownLatch计数器减1 85 | if (event.getState() == Event.KeeperState.SyncConnected) { 86 | latch.countDown(); 87 | } 88 | } 89 | }); 90 | latch.await(); 91 | } catch (Exception e) { 92 | e.printStackTrace(); 93 | } 94 | } 95 | 96 | /** 97 | * 获取对应接口名的服务地址 98 | * 99 | * @param node 接口名对应的完整节点名称 100 | * @return serverAddress,如果为null,说明不存在该节点的服务提供者 101 | */ 102 | private String getServerAddress(String node) { 103 | String serverAddress = null; 104 | try { 105 | // 先获取接口名节点的子节点,子节点下是服务器的列表 106 | // 需要注意的是,如果不存在该节点,会有异常,此时下面的代码就不会执行 107 | List children = zkClient.getChildren(node, false); 108 | // 随机负载均衡,会随机返回注册服务列表中的其中一个服务地址 109 | String firstChildren = children.get(getRandomNum(children.size())); 110 | // 构建该服务提供者的完整节点名称 111 | String firstChildrenNode = node + "/" + firstChildren; 112 | // 获取服务提供者节点的数据,得到serverAddress的byte数组 113 | byte[] serverAddressByte = zkClient.getData(firstChildrenNode, false, null); 114 | // 将byte数组转换为字符串,同时赋值给serverAddress 115 | serverAddress = new String(serverAddressByte); 116 | } catch (Exception e) { 117 | logger.error("节点[{}]不存在,无法获取服务提供者地址...", node); 118 | logger.error(e.getMessage()); 119 | } 120 | 121 | return serverAddress; 122 | } 123 | 124 | /** 125 | * 返回一个给定范围内的随机数值,用于实现 126 | * @param size 127 | * @return 128 | */ 129 | private Integer getRandomNum(int size) { 130 | Random random = new Random(); 131 | 132 | return random.nextInt(size); 133 | } 134 | 135 | /* 136 | public static void main(String[] args) throws Exception { 137 | ServiceDiscovery serviceDiscovery = new ServiceDiscovery("192.168.43.132:2181"); 138 | serviceDiscovery.connectServer(); 139 | String serverAddress = serviceDiscovery.getServerAddress("/minidubbo/cn.xpleaf.app.service.ItemService"); 140 | System.out.println("serverAddress: " + serverAddress); 141 | } 142 | */ 143 | 144 | } 145 | -------------------------------------------------------------------------------- /rpc-client/src/main/java/cn/xpleaf/rpc/client/netty/RPCClient.java: -------------------------------------------------------------------------------- 1 | package cn.xpleaf.rpc.client.netty; 2 | 3 | import cn.xpleaf.rpc.common.pojo.RPCRequest; 4 | import cn.xpleaf.rpc.common.pojo.RPCResponse; 5 | import cn.xpleaf.rpc.common.utils.RPCDecoder; 6 | import cn.xpleaf.rpc.common.utils.RPCEncoder; 7 | import io.netty.bootstrap.Bootstrap; 8 | import io.netty.channel.ChannelFuture; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.channel.ChannelInitializer; 11 | import io.netty.channel.ChannelOption; 12 | import io.netty.channel.EventLoopGroup; 13 | import io.netty.channel.SimpleChannelInboundHandler; 14 | import io.netty.channel.nio.NioEventLoopGroup; 15 | import io.netty.channel.socket.SocketChannel; 16 | import io.netty.channel.socket.nio.NioSocketChannel; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | /** 21 | * RPC客户端,用于连接RPC服务端,向服务端发送请求 22 | * 主要是netty的模板代码 23 | * 24 | * @author yeyonghao 25 | */ 26 | public class RPCClient extends SimpleChannelInboundHandler { 27 | 28 | // RPC服务端的地址 29 | private String host; 30 | // RPC服务端的端口号 31 | private int port; 32 | // RPCResponse响应对象 33 | private RPCResponse response; 34 | // log4j日志记录 35 | private Logger logger = LoggerFactory.getLogger(RPCClient.class); 36 | 37 | /** 38 | * 构造方法 39 | * 40 | * @param host RPC服务端的地址 41 | * @param port RPC服务端的端口号 42 | */ 43 | public RPCClient(String host, int port) { 44 | this.host = host; 45 | this.port = port; 46 | } 47 | 48 | /** 49 | * 向RPC服务端发送请求方法 50 | * 51 | * @param request RPC客户端向RPC服务端发送的request对象 52 | * @return 53 | */ 54 | public RPCResponse sendRequest(RPCRequest request) throws Exception { 55 | 56 | // 配置客户端NIO线程组 57 | EventLoopGroup group = new NioEventLoopGroup(); 58 | try { 59 | Bootstrap b = new Bootstrap(); 60 | b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true) 61 | // 设置TCP连接超时时间 62 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000) 63 | .handler(new ChannelInitializer() { 64 | 65 | @Override 66 | protected void initChannel(SocketChannel ch) throws Exception { 67 | // 添加解码器,RPC客户端需要解码的是RPCResponse对象,因为需要接收服务端发送过来的响应 68 | ch.pipeline().addLast(new RPCDecoder(RPCResponse.class)); 69 | // 添加编码器 70 | ch.pipeline().addLast(new RPCEncoder()); 71 | // 添加业务处理handler,本类继承了SimpleChannelInboundHandler 72 | // RPCClient继承了SimpleChannelInboundHandler,所以可以直接传入本类对象 73 | ch.pipeline().addLast(RPCClient.this); 74 | } 75 | }); 76 | // 发起异步连接操作(注意服务端是bind,客户端则需要connect) 77 | logger.info("准备发起异步连接操作[{}:{}]", host, port); 78 | ChannelFuture f = b.connect(host, port).sync(); 79 | 80 | // 判断连接是否成功的代码 81 | // System.out.println(f.isSuccess()); 82 | 83 | // 向RPC服务端发起请求 84 | logger.info("准备向RPC服务端发起请求..."); 85 | f.channel().writeAndFlush(request); 86 | 87 | 88 | // 需要注意的是,如果没有接收到服务端返回数据,那么会一直停在这里等待 89 | // 等待客户端链路关闭 90 | logger.info("准备等待客户端链路关闭..."); 91 | f.channel().closeFuture().sync(); 92 | } finally { 93 | // 优雅退出,释放NIO线程组 94 | logger.info("优雅退出,释放NIO线程组..."); 95 | group.shutdownGracefully(); 96 | } 97 | 98 | return response; 99 | } 100 | 101 | /** 102 | * 读取RPC服务端的响应结果,并赋值给response对象 103 | */ 104 | @Override 105 | protected void channelRead0(ChannelHandlerContext ctx, RPCResponse msg) throws Exception { 106 | logger.info("从RPC服务端接收到响应..."); 107 | this.response = msg; 108 | // 关闭与服务端的连接,这样就可以执行f.channel().closeFuture().sync();之后的代码,即优雅退出 109 | // 相当于是主动关闭连接 110 | ctx.close(); 111 | } 112 | 113 | /* 114 | public static void main(String[] args) throws Exception { 115 | int port = 21881; 116 | if (args != null && args.length > 0) { 117 | try { 118 | port = Integer.valueOf(port); 119 | } catch (NumberFormatException e) { 120 | // 采用默认值 121 | } 122 | } 123 | new RPCClient("localhost", port).sendRequest(new RPCRequest()); 124 | } 125 | */ 126 | } 127 | -------------------------------------------------------------------------------- /rpc-client/src/main/java/cn/xpleaf/rpc/client/proxy/RPCProxy.java: -------------------------------------------------------------------------------- 1 | package cn.xpleaf.rpc.client.proxy; 2 | 3 | import cn.xpleaf.rpc.client.discovery.ServiceDiscovery; 4 | import cn.xpleaf.rpc.client.netty.RPCClient; 5 | import cn.xpleaf.rpc.common.pojo.RPCRequest; 6 | import cn.xpleaf.rpc.common.pojo.RPCResponse; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.lang.reflect.InvocationHandler; 11 | import java.lang.reflect.Method; 12 | import java.lang.reflect.Proxy; 13 | import java.util.UUID; 14 | 15 | /** 16 | * 动态代理对象类,用于根据接口创建动态代理对象 17 | * 18 | * @author yeyonghao 19 | */ 20 | public class RPCProxy { 21 | 22 | // 用于发现服务的对象 23 | private ServiceDiscovery serviceDiscovery; 24 | 25 | // log4j日志记录 26 | private Logger logger = LoggerFactory.getLogger(RPCProxy.class); 27 | 28 | /** 29 | * 构造函数,传入ServiceDiscovery对象 30 | */ 31 | public RPCProxy(ServiceDiscovery serviceDiscovery) { 32 | this.serviceDiscovery = serviceDiscovery; 33 | } 34 | 35 | /** 36 | * 获得动态代理对象的通用方法,实现思路是,该方法中,并不需要具体的实现类对象 因为在invoke方法中,并不会调用Method 37 | * method这个方法,只是获得其方法的名字 然后将其封装在Netty请求中,发送到Netty服务端中请求远程调用的结果 38 | * 39 | * @param interfaceClass 需要被代理的接口的类型对象 40 | * @return proxy 对应接口的代理对象 41 | */ 42 | @SuppressWarnings("unchecked") 43 | public T getProxy(Class interfaceClass) { 44 | 45 | T proxy = (T) Proxy.newProxyInstance(RPCProxy.class.getClassLoader(), new Class[]{interfaceClass}, 46 | new InvocationHandler() { 47 | 48 | @Override 49 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 50 | 51 | logger.info("准备构建RPCRequest对象..."); 52 | 53 | // 构建RPCRequest对象 54 | RPCRequest request = new RPCRequest(); 55 | // 设置requestId 56 | request.setRequestId(UUID.randomUUID().toString()); 57 | // 设置接口名interfaceName 58 | String interfaceName = method.getDeclaringClass().getName(); 59 | request.setInterfaceName(interfaceName); 60 | /* 61 | 当时在将其整合到Spring-mvc失败时的调试信息 62 | 前后调试了近两个月才搞定,所以这部分调试信息就不删除了 63 | String interfaceName1 = method.getDeclaringClass().getName(); 64 | String interfaceName2 = interfaceClass.getName(); 65 | System.out.println("接口名称是:" + interfaceClass.getName()); 66 | System.out.println("接口名称是1:" + method.getDeclaringClass().getName()); 67 | String proxyName = proxy.getClass().getName(); 68 | */ 69 | // 设置方法名methodName 70 | request.setMethodName(method.getName()); 71 | // 设置参数类型parameterTypes 72 | request.setParameterTypes(method.getParameterTypes()); 73 | // 设置参数列表parameters 74 | request.setParameters(args); 75 | 76 | logger.info("RPCRequest对象构建完毕,准备发现服务[{}]...", interfaceName); 77 | 78 | // 发现服务,得到服务地址,格式为 host:port 79 | String serverAddress = serviceDiscovery.discoverService(interfaceName); 80 | // 如果服务不存在,null,否则就构建RPC客户端进行远程调用 81 | if (serverAddress == null) { 82 | logger.error("服务[{}]的提供者不存在,发现服务失败...", interfaceName); 83 | return null; 84 | } else { 85 | 86 | logger.info("发现服务完毕,准备解析服务地址[{}]...", serverAddress); 87 | 88 | // 解析服务地址 89 | String[] array = serverAddress.split(":"); 90 | String host = array[0]; 91 | int port = Integer.valueOf(array[1]); 92 | 93 | logger.info("服务地址解析完毕,准备构建RPC客户端..."); 94 | 95 | // 构建RPC客户端 96 | RPCClient client = new RPCClient(host, port); 97 | 98 | logger.info("RPC客户端构建完毕,准备向RPC服务端发送请求..."); 99 | 100 | // 向RPC服务端发送请求 101 | RPCResponse response = client.sendRequest(request); 102 | 103 | // 返回信息 104 | if (response.isError()) { 105 | // 如果进行远程调用时出现异常,[则抛出异常信息]--->直接返回null 106 | // throw response.getError(); 107 | logger.error("[{}]远程过程调用出现异常,远程过程调用失败...", interfaceName); 108 | return null; 109 | } else { 110 | // 如果没有异常,则返回调用的结果 111 | logger.info("[{}]远程过程调用完毕,远程过程调用成功...", interfaceName); 112 | return response.getResult(); 113 | } 114 | } 115 | } 116 | }); 117 | 118 | return proxy; 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /rpc-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | minidubbo 7 | cn.xpleaf.rpc 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | rpc-common 13 | 14 | rpc-common 15 | 16 | http://www.example.com 17 | 18 | 19 | UTF-8 20 | 21 | 22 | 23 | 24 | 25 | io.netty 26 | netty-all 27 | 28 | 29 | 30 | com.dyuproject.protostuff 31 | protostuff-core 32 | 33 | 34 | com.dyuproject.protostuff 35 | protostuff-runtime 36 | 37 | 38 | 39 | org.slf4j 40 | slf4j-api 41 | 42 | 43 | org.slf4j 44 | slf4j-log4j12 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/cn/xpleaf/rpc/common/pojo/RPCRequest.java: -------------------------------------------------------------------------------- 1 | package cn.xpleaf.rpc.common.pojo; 2 | 3 | /** 4 | * RPCRequest是client向server端发送数据的传输载体,将需要进行传输的pojo对象统一封装到RPCRequest对象中, 5 | * 这样会为编解码工作带来很大的方便性和统一性,同时也可以携带其它信息, 对于后面对程序进行扩展会有非常大的帮助 6 | * 7 | * @author yeyonghao 8 | */ 9 | public class RPCRequest { 10 | 11 | // 请求的ID,为UUID 12 | private String requestId; 13 | // 接口名称 14 | private String interfaceName; 15 | // 调用的方法名称 16 | private String methodName; 17 | // 方法的参数类型 18 | private Class[] parameterTypes; 19 | // 方法的参数值 20 | private Object[] parameters; 21 | 22 | /** 23 | * 上面几个数据就可以唯一地确定某一个类(接口)中的某一个具体的方法 24 | */ 25 | public String getRequestId() { 26 | return requestId; 27 | } 28 | 29 | public void setRequestId(String requestId) { 30 | this.requestId = requestId; 31 | } 32 | 33 | public String getInterfaceName() { 34 | return interfaceName; 35 | } 36 | 37 | public void setInterfaceName(String interfaceName) { 38 | this.interfaceName = interfaceName; 39 | } 40 | 41 | public String getMethodName() { 42 | return methodName; 43 | } 44 | 45 | public void setMethodName(String methodName) { 46 | this.methodName = methodName; 47 | } 48 | 49 | public Class[] getParameterTypes() { 50 | return parameterTypes; 51 | } 52 | 53 | public void setParameterTypes(Class[] parameterTypes) { 54 | this.parameterTypes = parameterTypes; 55 | } 56 | 57 | public Object[] getParameters() { 58 | return parameters; 59 | } 60 | 61 | public void setParameters(Object[] parameters) { 62 | this.parameters = parameters; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/cn/xpleaf/rpc/common/pojo/RPCResponse.java: -------------------------------------------------------------------------------- 1 | package cn.xpleaf.rpc.common.pojo; 2 | 3 | /** 4 | * RPCResponse是server向client端发送数据的传输载体,将需要进行传输的pojo对象统一封装到RPCResponse对象中, 5 | * 这样会为编解码工作带来很大的方便性和统一性,同时也可以携带其它信息, 对于后面对程序进行扩展会有非常大的帮助 6 | * 7 | * @author yeyonghao 8 | */ 9 | public class RPCResponse { 10 | 11 | private String requestId; 12 | private Throwable error; 13 | private Object result; 14 | 15 | public boolean isError() { 16 | return error != null; 17 | } 18 | 19 | public String getRequestId() { 20 | return requestId; 21 | } 22 | 23 | public void setRequestId(String requestId) { 24 | this.requestId = requestId; 25 | } 26 | 27 | public Throwable getError() { 28 | return error; 29 | } 30 | 31 | public void setError(Throwable error) { 32 | this.error = error; 33 | } 34 | 35 | public Object getResult() { 36 | return result; 37 | } 38 | 39 | public void setResult(Object result) { 40 | this.result = result; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/cn/xpleaf/rpc/common/utils/RPCDecoder.java: -------------------------------------------------------------------------------- 1 | package cn.xpleaf.rpc.common.utils; 2 | 3 | import java.util.List; 4 | 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.MessageToMessageDecoder; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | /** 12 | * RPCDecoder继承自Netty中的MessageToMessageDecoder类, 13 | * 并重写抽象方法decode(ChannelHandlerContext ctx, ByteBuf msg, List out) 14 | * 首先从数据报msg(数据类型取决于继承MessageToMessageDecoder时填写的泛型类型)中获取需要解码的byte数组 15 | * 然后调用使用序列化工具类将其反序列化(解码)为Object对象 将解码后的对象加入到解码列表out中,这样就完成了解码操作 16 | * 17 | * @author yeyonghao 18 | */ 19 | public class RPCDecoder extends MessageToMessageDecoder { 20 | 21 | // 需要反序列对象所属的类型 22 | private Class genericClass; 23 | // log4j日志记录 24 | private Logger logger = LoggerFactory.getLogger(RPCDecoder.class); 25 | 26 | // 构造方法,传入需要反序列化对象的类型 27 | public RPCDecoder(Class genericClass) { 28 | this.genericClass = genericClass; 29 | } 30 | 31 | @Override 32 | protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { 33 | // ByteBuf的长度 34 | int length = msg.readableBytes(); 35 | // 构建length长度的字节数组 36 | byte[] array = new byte[length]; 37 | // 将ByteBuf数据复制到字节数组中 38 | msg.readBytes(array); 39 | // 反序列化对象 40 | logger.info("准备反序列化对象..."); 41 | Object obj = SerializationUtil.deserialize(array, this.genericClass); 42 | // 添加到反序列化对象结果列表 43 | logger.info("反序列化对象完毕,准备将其添加到反序列化对象结果列表..."); 44 | out.add(obj); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/cn/xpleaf/rpc/common/utils/RPCEncoder.java: -------------------------------------------------------------------------------- 1 | package cn.xpleaf.rpc.common.utils; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToByteEncoder; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * RPCEncoder继承自Netty中的MessageToByteEncoder类, 11 | * 并重写抽象方法encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) 12 | * 它负责将Object类型的POJO对象编码为byte数组,然后写入到ByteBuf中 13 | * 14 | * @author yeyonghao 15 | */ 16 | public class RPCEncoder extends MessageToByteEncoder { 17 | 18 | // log4j日志记录 19 | private Logger logger = LoggerFactory.getLogger(RPCEncoder.class); 20 | 21 | @Override 22 | protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception { 23 | // 直接生成序列化对象 24 | // 需要注意的是,使用protostuff序列化时,不需要知道pojo对象的具体类型也可以进行序列化的 25 | // 在反序列化时,只要提供序列化后的字节数组和原来pojo对象的类型即可完成反序列化 26 | logger.info("准备序列化对象..."); 27 | byte[] array = SerializationUtil.serialize(msg); 28 | logger.info("序列化对象完毕,准备将其写入到ByteBuf中..."); 29 | out.writeBytes(array); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /rpc-common/src/main/java/cn/xpleaf/rpc/common/utils/SerializationUtil.java: -------------------------------------------------------------------------------- 1 | package cn.xpleaf.rpc.common.utils; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | 6 | import com.dyuproject.protostuff.LinkedBuffer; 7 | import com.dyuproject.protostuff.ProtostuffIOUtil; 8 | import com.dyuproject.protostuff.runtime.RuntimeSchema; 9 | 10 | /** 11 | * 具备缓存功能的序列化工具类,基于Protostuff实现(其基于Google Protobuf实现) 12 | * 13 | * 所以如果希望写好这个序列化工具类,下面几个方面的知识需要准备好: 14 | * 1.理解并能使用Google Protobuf 15 | * 2.在1的基础上,使用Protostuff 16 | * 3.Java泛型的充分使用 17 | * 并且需要注意的是,其实1、2点,在理解了之后,更多的是在写模板代码,当初我在学习的一个过程也是如此的 18 | * 可以参考我的相关博客文章,以Protostuff为例,这里给出一篇文章地址:http://blog.51cto.com/xpleaf/2071752 19 | * 20 | * @author yeyonghao 21 | */ 22 | public class SerializationUtil { 23 | 24 | // 缓存schema对象的map 25 | private static Map, RuntimeSchema> cachedSchema = new ConcurrentHashMap, RuntimeSchema>(); 26 | 27 | /** 28 | * 根据获取相应类型的schema方法 29 | * 30 | * @param clazz 31 | * @return 32 | */ 33 | @SuppressWarnings({"unchecked", "unused"}) 34 | private RuntimeSchema getSchema(Class clazz) { 35 | // 先尝试从缓存schema map中获取相应类型的schema 36 | RuntimeSchema schema = (RuntimeSchema) cachedSchema.get(clazz); 37 | // 如果没有获取到对应的schema,则创建一个该类型的schema 38 | // 同时将其添加到schema map中 39 | if (schema == null) { 40 | schema = RuntimeSchema.createFrom(clazz); 41 | if (schema != null) { 42 | cachedSchema.put(clazz, schema); 43 | } 44 | } 45 | // 返回schema对象 46 | return schema; 47 | } 48 | 49 | /** 50 | * 序列化方法,将对象序列化为字节数组(对象 ---> 字节数组) 51 | * 52 | * @param obj 53 | * @return 54 | */ 55 | @SuppressWarnings("unchecked") 56 | public static byte[] serialize(T obj) { 57 | // 获取泛型对象的类型 58 | Class clazz = (Class) obj.getClass(); 59 | // 创建泛型对象的schema对象 60 | RuntimeSchema schema = RuntimeSchema.createFrom(clazz); 61 | // 创建LinkedBuffer对象 62 | LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); 63 | // 序列化 64 | byte[] array = ProtostuffIOUtil.toByteArray(obj, schema, buffer); 65 | // 返回序列化对象 66 | return array; 67 | } 68 | 69 | /** 70 | * 反序列化方法,将字节数组反序列化为对象(字节数组 ---> 对象) 71 | * 72 | * @param data 73 | * @param clazz 74 | * @return 75 | */ 76 | public static T deserialize(byte[] data, Class clazz) { 77 | // 创建泛型对象的schema对象 78 | RuntimeSchema schema = RuntimeSchema.createFrom(clazz); 79 | // 根据schema实例化对象 80 | T message = schema.newMessage(); 81 | // 将字节数组中的数据反序列化到message对象 82 | ProtostuffIOUtil.mergeFrom(data, message, schema); 83 | // 返回反序列化对象 84 | return message; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /rpc-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | minidubbo 7 | cn.xpleaf.rpc 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | rpc-server 13 | 14 | rpc-server 15 | 16 | http://www.example.com 17 | 18 | 19 | UTF-8 20 | 21 | 22 | 23 | 24 | 25 | cn.xpleaf.rpc 26 | rpc-common 27 | 1.0-SNAPSHOT 28 | 29 | 30 | 31 | org.springframework 32 | spring-context 33 | 34 | 35 | org.springframework 36 | spring-beans 37 | 38 | 39 | org.springframework 40 | spring-webmvc 41 | 42 | 43 | org.springframework 44 | spring-jdbc 45 | 46 | 47 | org.springframework 48 | spring-aspects 49 | 50 | 51 | org.springframework 52 | spring-jms 53 | 54 | 55 | org.springframework 56 | spring-context-support 57 | 58 | 59 | 60 | io.netty 61 | netty-all 62 | 63 | 64 | 65 | org.apache.zookeeper 66 | zookeeper 67 | 68 | 69 | 70 | org.slf4j 71 | slf4j-api 72 | 73 | 74 | org.slf4j 75 | slf4j-log4j12 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/cn/xpleaf/rpc/server/annotation/RPCService.java: -------------------------------------------------------------------------------- 1 | package cn.xpleaf.rpc.server.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * RPC服务注解,标注在服务实现类在即可通过minidubbo发布服务 12 | * 13 | * @author yeyonghao 14 | */ 15 | @Target(value = ElementType.TYPE) // 自定义注解的使用范围,ElementType.TYPE表示自定义的注解可以用在类或接口上 16 | @Retention(RetentionPolicy.RUNTIME) // 注解的可见范围,RetentionPolicy.RUNTIME表示自定义注解在虚拟机运行期间也可见 17 | @Component // 让spring可以扫描到此注解 18 | public @interface RPCService { 19 | 20 | // 自定义注解的参数类型为类型对象,使用该注解时,需要传入实现类的接口对象 21 | Class value(); 22 | } 23 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/cn/xpleaf/rpc/server/netty/RPCServer.java: -------------------------------------------------------------------------------- 1 | package cn.xpleaf.rpc.server.netty; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.BeansException; 9 | import org.springframework.beans.factory.InitializingBean; 10 | import org.springframework.context.ApplicationContext; 11 | import org.springframework.context.ApplicationContextAware; 12 | 13 | import cn.xpleaf.rpc.common.pojo.RPCRequest; 14 | import cn.xpleaf.rpc.common.utils.RPCDecoder; 15 | import cn.xpleaf.rpc.common.utils.RPCEncoder; 16 | import cn.xpleaf.rpc.server.annotation.RPCService; 17 | import cn.xpleaf.rpc.server.registry.ServiceRegistry; 18 | import io.netty.bootstrap.ServerBootstrap; 19 | import io.netty.channel.ChannelFuture; 20 | import io.netty.channel.ChannelInitializer; 21 | import io.netty.channel.ChannelOption; 22 | import io.netty.channel.EventLoopGroup; 23 | import io.netty.channel.nio.NioEventLoopGroup; 24 | import io.netty.channel.socket.SocketChannel; 25 | import io.netty.channel.socket.nio.NioServerSocketChannel; 26 | 27 | /** 28 | * RPCServer主要完成下面几个功能: 29 | * 1.将需要发布的服务保存到一个map中 30 | * 2.启动netty服务端程序 31 | * 3.向zookeeper注册需要发布的服务 32 | * 33 | * @author yeyonghao 34 | */ 35 | public class RPCServer implements ApplicationContextAware, InitializingBean { 36 | 37 | // 用来保存用户服务实现类对象,key为实现类的接口名称,value为实现类对象 38 | private Map serviceBeanMap = new HashMap<>(); 39 | // 运行netty程序的服务端地址,用于绑定到netty服务端程序中 40 | private String serverAddress; 41 | // 向zookeeper注册的注册类对象 42 | private ServiceRegistry serviceRegistry; 43 | // log4j日志记录 44 | private Logger logger = LoggerFactory.getLogger(RPCServer.class); 45 | 46 | /** 47 | * RPCServer的构造方法 48 | * 49 | * @param serverAddress 服务提供者的地址信息,格式为 host:port 50 | * @param serviceRegistry zookeeper服务注册类对象 51 | */ 52 | public RPCServer(String serverAddress, ServiceRegistry serviceRegistry) { 53 | this.serverAddress = serverAddress; 54 | this.serviceRegistry = serviceRegistry; 55 | } 56 | 57 | /** 58 | * 由于本类实现了ApplicationContextAware接口,spring在构造本类对象时会调用setApplicationContext方法 59 | * 在该方法中,通过注解获取标注了RPCService的用户服务实现类,然后将其接口和实现类对象保存到serviceBeanMap中 60 | */ 61 | @Override 62 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 63 | // 通过spring获取到标注了RPCService注解的map,map的key为bean的名称,map的value为bean的实例对象 64 | // a Map of bean names with corresponding bean instances 65 | // 所以,如果你的服务实现类标注了RPCService注解,但其不在spring的扫描范围内,也是没有办法获取到的 66 | logger.info("准备扫描获取标注了RPCService的用户服务实现类..."); 67 | Map beansWithAnnotation = applicationContext.getBeansWithAnnotation(RPCService.class); 68 | // 将beansWithAnnotation中的values值,即bean的实现对象保存到serviceBeanMap中 69 | logger.info("标注了RPCService的用户服务实现类扫描完毕,准备将其接口和实现类对象保存到容器中..."); 70 | for (Object serviceBean : beansWithAnnotation.values()) { 71 | // 获取实现类对象的接口名称,思路是,实现类中标注了RPCService注解,同时其参数为实现类的接口类型 72 | // 说明:实际上可以通过serviceBean.getClass().getInterfaces()的方式来获取其接口名称的 73 | // 只是如果是实现多个接口的情况下需要进行判断,这点后面再做具体的实现 74 | String interfaceName = serviceBean.getClass().getAnnotation(RPCService.class).value().getName(); 75 | 76 | // 保存到serviceBeanMap中 77 | serviceBeanMap.put(interfaceName, serviceBean); 78 | } 79 | logger.info("用户服务接口和实现类对象保存完毕..."); 80 | 81 | } 82 | 83 | /** 84 | * 由于本类实现了InitializingBean接口,spring在构造完所有对象之后会调用afterPropertiesSet方法 85 | * 在该方法中,将服务注册到zookeeper,同时启动netty服务端程序,该方法中主要是netty框架的代码 86 | * 87 | * @throws Exception 88 | */ 89 | @Override 90 | public void afterPropertiesSet() throws Exception { 91 | 92 | logger.info("准备构建RPC服务端,监听来自RPC客户端的请求..."); 93 | 94 | // 配置服务端NIO线程组 95 | EventLoopGroup bossGroup = new NioEventLoopGroup(); 96 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 97 | 98 | try { 99 | ServerBootstrap b = new ServerBootstrap(); 100 | b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024) 101 | .childHandler(new ChannelInitializer() { 102 | 103 | @Override 104 | protected void initChannel(SocketChannel ch) throws Exception { 105 | // 添加编码器,RPC服务端需要解码的是RPCRequest对象,因为需要接收客户端发送过来的请求 106 | ch.pipeline().addLast(new RPCDecoder(RPCRequest.class)); 107 | // 添加解码器 108 | ch.pipeline().addLast(new RPCEncoder()); 109 | // 添加业务处理handler 110 | ch.pipeline().addLast(new RPCServerHandler(serviceBeanMap)); 111 | } 112 | }); 113 | 114 | // 解析serverAddress中的host和port 115 | String[] array = serverAddress.split(":"); 116 | String host = array[0]; 117 | int port = Integer.valueOf(array[1]); 118 | 119 | // 绑定端口,同步等待成功,该方法是同步阻塞的,绑定成功后返回一个ChannelFuture 120 | logger.info("准备绑定服务提供者地址和端口[{}:{}]", host, port); 121 | ChannelFuture f = b.bind(host, port).sync(); 122 | 123 | // 向zookeeper注册 124 | logger.info("绑定服务提供者地址和端口成功,准备向zookeeper注册服务..."); 125 | for (String interfaceName : serviceBeanMap.keySet()) { 126 | serviceRegistry.registerService(serverAddress, interfaceName); 127 | } 128 | 129 | // 等待服务端监听端口关闭,阻塞,等待服务端链路关闭之后main函数才退出 130 | logger.info("向zookeeper注册服务成功,正在监听来自RPC客户端的请求连接..."); 131 | f.channel().closeFuture().sync(); 132 | } finally { 133 | // 优雅退出,释放线程池资源 134 | bossGroup.shutdownGracefully(); 135 | workerGroup.shutdownGracefully(); 136 | } 137 | 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/cn/xpleaf/rpc/server/netty/RPCServerHandler.java: -------------------------------------------------------------------------------- 1 | package cn.xpleaf.rpc.server.netty; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Map; 5 | 6 | import cn.xpleaf.rpc.common.pojo.RPCRequest; 7 | import cn.xpleaf.rpc.common.pojo.RPCResponse; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.ChannelInboundHandlerAdapter; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * RPCServerHandler主要用于处理用户的请求,并返回响应结果 15 | * 主要是在Netty的模板代码(ChannelInboundHandlerAdapter)中嵌入反射调用方法的代码,并封装结果 16 | * 17 | * @author yeyonghao 18 | */ 19 | public class RPCServerHandler extends ChannelInboundHandlerAdapter { 20 | 21 | // 用来保存用户服务实现类对象,key为实现类的接口名称,value为实现类对象 22 | Map serviceBeanMap = null; 23 | // log4j日志记录 24 | Logger logger = LoggerFactory.getLogger(RPCServerHandler.class); 25 | 26 | /** 27 | * 构造方法传入保存了key-value为interfaceName-bean的map 28 | * 29 | * @param serviceBeanMap 30 | */ 31 | public RPCServerHandler(Map serviceBeanMap) { 32 | this.serviceBeanMap = serviceBeanMap; 33 | } 34 | 35 | /** 36 | * 接收消息,处理消息,返回结果 37 | */ 38 | @Override 39 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 40 | 41 | logger.info("接收到来自RPC客户端的连接请求..."); 42 | 43 | // 接收到的对象的类型为RPCRequest 44 | RPCRequest request = (RPCRequest) msg; 45 | RPCResponse response = new RPCResponse(); 46 | // 设置requestId 47 | response.setRequestId(response.getRequestId()); 48 | try { 49 | logger.info("准备调用handle方法处理request请求对象..."); 50 | // 调用handle方法处理request 51 | Object result = handleRequest(request); 52 | // 设置返回结果 53 | response.setResult(result); 54 | } catch (Throwable e) { 55 | // 如果有异常,则设置异常信息 56 | response.setError(e); 57 | } 58 | 59 | logger.info("请求处理完毕,准备回写response对象..."); 60 | ctx.writeAndFlush(response); 61 | } 62 | 63 | /** 64 | * 对request进行处理,其实就是通过反射进行调用的过程 65 | * 66 | * @param request 67 | * @return 68 | * @throws Throwable 69 | */ 70 | public Object handleRequest(RPCRequest request) throws Throwable { 71 | // 拿到类名 72 | String interfaceName = request.getInterfaceName(); 73 | 74 | // 根据接口名拿到其实现类对象 75 | Object serviceBean = serviceBeanMap.get(interfaceName); 76 | 77 | // 拿到要调用的方法名、参数类型、参数值 78 | String methodName = request.getMethodName(); 79 | Class[] parameterTypes = request.getParameterTypes(); 80 | Object[] parameters = request.getParameters(); 81 | 82 | // 拿到接口类对象 83 | Class clazz = Class.forName(interfaceName); 84 | 85 | // 拿到实现类对象的指定方法 86 | Method method = clazz.getMethod(methodName, parameterTypes); 87 | 88 | // 通过反射调用方法 89 | logger.info("准备通过反射调用方法[{}]...", interfaceName); 90 | Object result = method.invoke(serviceBean, parameters); 91 | 92 | logger.info("通过反射调用方法完毕..."); 93 | // 返回结果 94 | return result; 95 | } 96 | 97 | @Override 98 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 99 | ctx.flush(); 100 | } 101 | 102 | @Override 103 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 104 | ctx.close(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /rpc-server/src/main/java/cn/xpleaf/rpc/server/registry/ServiceRegistry.java: -------------------------------------------------------------------------------- 1 | package cn.xpleaf.rpc.server.registry; 2 | 3 | import java.util.concurrent.CountDownLatch; 4 | 5 | import org.apache.zookeeper.CreateMode; 6 | import org.apache.zookeeper.WatchedEvent; 7 | import org.apache.zookeeper.Watcher; 8 | import org.apache.zookeeper.ZooKeeper; 9 | import org.apache.zookeeper.ZooDefs.Ids; 10 | import org.apache.zookeeper.data.Stat; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * 服务注册类,用于将服务提供者的服务注册到zookeeper上 16 | * 17 | * @author yeyonghao 18 | */ 19 | public class ServiceRegistry { 20 | 21 | // zookeeper中保存服务信息的父节点 22 | private final String parentNode = "/minidubbo"; 23 | // zookeeper中服务提供者的序列化名称 24 | private final String serverName = "server"; 25 | // zookeeper的地址,由spring构造ServiceRegistry对象时传入 26 | private String registryAddress; 27 | // 连接zookeeper的超时时间 28 | private int sessionTimeout = 2000; 29 | // 连接zookeeper的客户端 30 | private ZooKeeper zkClient = null; 31 | // 用来确保zookeeper连接成功后才进行后续的操作 32 | private CountDownLatch latch = new CountDownLatch(1); 33 | // log4j日志记录 34 | private Logger logger = LoggerFactory.getLogger(ServiceRegistry.class); 35 | 36 | /** 37 | * 构造方法 38 | * 39 | * @param registryAddress zookeeper的地址,格式为 host:port 40 | */ 41 | public ServiceRegistry(String registryAddress) { 42 | this.registryAddress = registryAddress; 43 | } 44 | 45 | /** 46 | * 向zookeeper注册服务 47 | * 48 | * @param serverAddress 服务提供者的地址,格式为 host:port 49 | * @param interfaceName 注册的服务,完整接口名称,如cn.xpleaf.service.UserService 50 | */ 51 | public void registerService(String serverAddress, String interfaceName) { 52 | // 如果zkClient为null,则连接未建立,先建立连接 53 | if (this.zkClient == null) { 54 | logger.info("未连接zookeeper,准备建立连接..."); 55 | connectServer(); 56 | } 57 | logger.info("zookeeper连接建立成功,准备在zookeeper上创建相关节点..."); 58 | // 先判断父节点是否存在,如果不存在,则先创建父节点 59 | if (!isExist(parentNode)) { 60 | logger.info("正在创建节点[{}]", parentNode); 61 | createPNode(parentNode, ""); 62 | } 63 | // 先判断接口节点是否存在(即/minidubbo/interfacename),如果不存在,则先创建接口节点 64 | if (!isExist(parentNode + "/" + interfaceName)) { 65 | logger.info("正在创建节点[{}]", parentNode + "/" + interfaceName); 66 | createPNode(parentNode + "/" + interfaceName, ""); 67 | } 68 | // 创建接口节点下的服务提供者节点(即/minidubbo/interfacename/provider00001) 69 | logger.info("正在创建节点[{}]", parentNode + "/" + interfaceName + "/" + serverName + "+序列号"); 70 | createESNode(parentNode + "/" + interfaceName + "/" + serverName, serverAddress); 71 | logger.info("zookeeper上相关节点已经创建成功..."); 72 | } 73 | 74 | /** 75 | * 建立连接 76 | */ 77 | private void connectServer() { 78 | try { 79 | zkClient = new ZooKeeper(registryAddress, sessionTimeout, new Watcher() { 80 | 81 | // 注册监听事件,连接成功后会调用process方法 82 | // 此时再调用latch的countDown方法使CountDownLatch计数器减1 83 | // 因为构造CountDownLatch对象时设置的值为1,减1后变为0,所以执行该方法后latch.await()将会中断 84 | // 从而确保连接成功后才会执行后续zookeeper的相关操作 85 | @Override 86 | public void process(WatchedEvent event) { 87 | // 如果状态为已连接,则使用CountDownLatch计数器减1 88 | if (event.getState() == Event.KeeperState.SyncConnected) { 89 | latch.countDown(); 90 | } 91 | } 92 | }); 93 | latch.await(); 94 | } catch (Exception e) { 95 | e.printStackTrace(); 96 | } 97 | } 98 | 99 | /** 100 | * 判断节点是否存在 101 | * 102 | * @return 存在返回true,不存在返回false 103 | * @throws Exception 104 | */ 105 | private boolean isExist(String node) { 106 | Stat stat = null; 107 | try { 108 | stat = zkClient.exists(node, false); 109 | } catch (Exception e) { 110 | e.printStackTrace(); 111 | } 112 | return stat == null ? false : true; 113 | } 114 | 115 | /** 116 | * 创建永久节点(父节点/minidubbo和其子节点即接口节点需要创建为此种类型) 117 | * 118 | * @param node 节点的名称,父节点为/minidubbo,接口接点则为/minidubbo/interfacename 119 | * @param data 节点的数据,可为空 120 | */ 121 | private void createPNode(String node, String data) { 122 | try { 123 | zkClient.create(node, data.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 124 | } catch (Exception e) { 125 | e.printStackTrace(); 126 | } 127 | } 128 | 129 | /** 130 | * 创建短暂序列化节点(服务提供者节点需要创建为此种类型) 131 | * 132 | * @param node 节点的名称,如/minidubbo/interfacename/server00001 133 | * @param data 节点的数据,为服务提供者的IP地址和端口号的格式化数据,如192.168.100.101:21881 134 | */ 135 | private void createESNode(String node, String data) { 136 | try { 137 | zkClient.create(node, data.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); 138 | } catch (Exception e) { 139 | e.printStackTrace(); 140 | } 141 | } 142 | 143 | /* 144 | public static void main(String[] args) throws IOException { 145 | // ServiceRegistry serviceRegistry = new ServiceRegistry("localhost:2181"); 146 | // serviceRegistry.connectServer(); 147 | // serviceRegistry.registerService("192.168.1.102:21881", "cn.xpleaf.UserService"); 148 | // System.in.read(); 149 | Map map = new HashMap<>(); 150 | map.put("name", "xpleaf"); 151 | System.out.println(map.entrySet()); 152 | } 153 | */ 154 | } 155 | --------------------------------------------------------------------------------