├── .gitignore ├── README.md ├── ch0-custom-rpc-protocol ├── README.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── club │ │ │ └── throwable │ │ │ ├── client │ │ │ ├── ClientApplication.java │ │ │ ├── ClientChannelHolder.java │ │ │ ├── ClientHandler.java │ │ │ ├── ContractProxyFactory.java │ │ │ ├── DefaultRequestArgumentExtractor.java │ │ │ ├── RequestArgumentExtractInput.java │ │ │ ├── RequestArgumentExtractOutput.java │ │ │ ├── RequestArgumentExtractor.java │ │ │ ├── ResponseFuture.java │ │ │ └── TestDynamicProxy.java │ │ │ ├── contract │ │ │ ├── HelloService.java │ │ │ └── dto │ │ │ │ ├── SayHelloRequestDTO.java │ │ │ │ └── SayHelloResponseDTO.java │ │ │ ├── exception │ │ │ ├── ArgumentConvertException.java │ │ │ ├── MethodMatchException.java │ │ │ └── SendRequestException.java │ │ │ ├── protocol │ │ │ ├── BaseMessagePacket.java │ │ │ ├── MessageType.java │ │ │ ├── ProtocolConstant.java │ │ │ ├── RequestMessagePacket.java │ │ │ ├── RequestMessagePacketDecoder.java │ │ │ ├── RequestMessagePacketEncoder.java │ │ │ ├── ResponseMessagePacket.java │ │ │ ├── ResponseMessagePacketDecoder.java │ │ │ ├── ResponseMessagePacketEncoder.java │ │ │ ├── TestProtocolClient.java │ │ │ ├── TestProtocolServer.java │ │ │ └── serialize │ │ │ │ ├── FastJsonSerializer.java │ │ │ │ └── Serializer.java │ │ │ ├── server │ │ │ ├── ArgumentConvertInput.java │ │ │ ├── ArgumentConvertOutput.java │ │ │ ├── BaseMethodMatcher.java │ │ │ ├── DefaultMethodArgumentConverter.java │ │ │ ├── HostClassMethodInfo.java │ │ │ ├── MethodArgumentConverter.java │ │ │ ├── MethodMatchInput.java │ │ │ ├── MethodMatchOutput.java │ │ │ ├── MethodMatcher.java │ │ │ ├── ServerApplication.java │ │ │ ├── ServerHandler.java │ │ │ ├── SpringMethodMatcher.java │ │ │ └── contract │ │ │ │ └── DefaultHelloService.java │ │ │ └── utils │ │ │ ├── ByteBufferUtils.java │ │ │ └── SerialNumberUtils.java │ └── resources │ │ └── logback.xml │ └── test │ └── java │ └── club │ └── throwable │ └── client │ └── NettyThreadSyncTest.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Example user template template 3 | ### Example user template 4 | 5 | # IntelliJ project files 6 | .idea 7 | *.iml 8 | out 9 | gen 10 | target 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## netty-tutorials 2 | 3 | ## ch0-custom-rpc-protocol 4 | 5 | **1. 基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇** 6 | 7 | - Github Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇》](http://throwable.club/2020/01/12/netty-custom-rpc-framework-protocol) 8 | - Coding Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇》](http://throwable.coding.me/2020/01/12/netty-custom-rpc-framework-protocol) 9 | 10 | **2. 基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇** 11 | 12 | - Github Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇》](http://www.throwable.club/2020/01/15/netty-custom-rpc-framework-server) 13 | - Coding Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇》](http://throwable.coding.me/2020/01/15/netty-custom-rpc-framework-server) 14 | 15 | **3. 基于Netty和SpringBoot实现一个轻量级RPC框架-Client篇** 16 | 17 | - Github Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-Client篇》](http://www.throwable.club/2020/01/16/netty-custom-rpc-framework-client) 18 | - Coding Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-Client篇》](http://throwable.coding.me/2020/01/16/netty-custom-rpc-framework-client) 19 | 20 | **4. 基于Netty和SpringBoot实现一个轻量级RPC框架-Client端请求响应同步化处理** 21 | 22 | - Github Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-Client端请求响应同步化处理》](http://www.throwable.club/2020/01/18/netty-custom-rpc-framework-client-sync) 23 | - Coding Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-Client端请求响应同步化处理》](http://throwable.coding.me/2020/01/18/netty-custom-rpc-framework-client-sync) -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/README.md: -------------------------------------------------------------------------------- 1 | ## netty-tutorials 2 | 3 | ## ch0-custom-rpc-protocol 4 | 5 | **1. 基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇** 6 | 7 | - Github Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇》](http://throwable.club/2020/01/12/netty-custom-rpc-framework-protocol) 8 | - Coding Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇》](http://throwable.coding.me/2020/01/12/netty-custom-rpc-framework-protocol) 9 | 10 | **2. 基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇** 11 | 12 | - Github Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇》](http://www.throwable.club/2020/01/15/netty-custom-rpc-framework-server) 13 | - Coding Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇》](http://throwable.coding.me/2020/01/15/netty-custom-rpc-framework-server) 14 | 15 | **3. 基于Netty和SpringBoot实现一个轻量级RPC框架-Client篇** 16 | 17 | - Github Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-Client篇》](http://www.throwable.club/2020/01/16/netty-custom-rpc-framework-client) 18 | - Coding Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-Client篇》](http://throwable.coding.me/2020/01/16/netty-custom-rpc-framework-client) 19 | 20 | **4. 基于Netty和SpringBoot实现一个轻量级RPC框架-Client端请求响应同步化处理** 21 | 22 | - Github Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-Client端请求响应同步化处理》](http://www.throwable.club/2020/01/18/netty-custom-rpc-framework-client-sync) 23 | - Coding Page:[《基于Netty和SpringBoot实现一个轻量级RPC框架-Client端请求响应同步化处理》](http://throwable.coding.me/2020/01/18/netty-custom-rpc-framework-client-sync) -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | netty-tutorials 6 | club.throwable 7 | 1.0-SNAPSHOT 8 | 9 | 4.0.0 10 | ch0-custom-rpc-protocol 11 | jar 12 | ch0-custom-rpc-protocol 13 | 14 | 15 | 16 | 17 | ch0-custom-rpc-protocol 18 | 19 | 20 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/client/ClientApplication.java: -------------------------------------------------------------------------------- 1 | package club.throwable.client; 2 | 3 | import club.throwable.contract.HelloService; 4 | import club.throwable.contract.dto.SayHelloRequestDTO; 5 | import club.throwable.contract.dto.SayHelloResponseDTO; 6 | import club.throwable.protocol.RequestMessagePacketEncoder; 7 | import club.throwable.protocol.ResponseMessagePacketDecoder; 8 | import club.throwable.protocol.serialize.FastJsonSerializer; 9 | import io.netty.bootstrap.Bootstrap; 10 | import io.netty.channel.ChannelFuture; 11 | import io.netty.channel.ChannelInitializer; 12 | import io.netty.channel.ChannelOption; 13 | import io.netty.channel.EventLoopGroup; 14 | import io.netty.channel.nio.NioEventLoopGroup; 15 | import io.netty.channel.socket.SocketChannel; 16 | import io.netty.channel.socket.nio.NioSocketChannel; 17 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 18 | import io.netty.handler.codec.LengthFieldPrepender; 19 | import io.netty.handler.logging.LogLevel; 20 | import io.netty.handler.logging.LoggingHandler; 21 | import lombok.extern.slf4j.Slf4j; 22 | 23 | /** 24 | * @author throwable 25 | * @version v1.0 26 | * @description 27 | * @since 2020/1/15 23:58 28 | */ 29 | @Slf4j 30 | public class ClientApplication { 31 | 32 | public static void main(String[] args) throws Exception { 33 | int port = 9092; 34 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 35 | Bootstrap bootstrap = new Bootstrap(); 36 | try { 37 | bootstrap.group(workerGroup); 38 | bootstrap.channel(NioSocketChannel.class); 39 | bootstrap.option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE); 40 | bootstrap.option(ChannelOption.TCP_NODELAY, Boolean.TRUE); 41 | bootstrap.handler(new ChannelInitializer() { 42 | 43 | @Override 44 | protected void initChannel(SocketChannel ch) throws Exception { 45 | ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4)); 46 | ch.pipeline().addLast(new LengthFieldPrepender(4)); 47 | ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG)); 48 | ch.pipeline().addLast(new RequestMessagePacketEncoder(FastJsonSerializer.X)); 49 | ch.pipeline().addLast(new ResponseMessagePacketDecoder()); 50 | ch.pipeline().addLast(new ClientHandler()); 51 | } 52 | }); 53 | ChannelFuture future = bootstrap.connect("localhost", port).sync(); 54 | // 保存Channel实例,暂时不考虑断连重连 55 | ClientChannelHolder.CHANNEL_REFERENCE.set(future.channel()); 56 | // 构造契约接口代理类实例 57 | HelloService helloService = ContractProxyFactory.ofProxy(HelloService.class); 58 | String name = "throwable"; 59 | String result = helloService.sayHello(name); 60 | log.info("HelloService[{}],调用参数:{}, 调用结果:{}", "sayHello(String name)", name, result); 61 | SayHelloRequestDTO request = new SayHelloRequestDTO(); 62 | request.setName("doge"); 63 | SayHelloResponseDTO responseDTO = helloService.sayHello(request); 64 | log.info("HelloService[{}],调用参数:{}, 调用结果:{}", "sayHello(SayHelloRequestDTO request)", 65 | request.toString(), responseDTO.toString()); 66 | future.channel().closeFuture().sync(); 67 | } finally { 68 | workerGroup.shutdownGracefully(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/client/ClientChannelHolder.java: -------------------------------------------------------------------------------- 1 | package club.throwable.client; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | import java.util.concurrent.atomic.AtomicReference; 6 | 7 | /** 8 | * @author throwable 9 | * @version v1.0 10 | * @description 11 | * @since 2020/1/15 23:45 12 | */ 13 | public class ClientChannelHolder { 14 | 15 | public static final AtomicReference CHANNEL_REFERENCE = new AtomicReference<>(); 16 | } 17 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/client/ClientHandler.java: -------------------------------------------------------------------------------- 1 | package club.throwable.client; 2 | 3 | import club.throwable.protocol.ResponseMessagePacket; 4 | import com.alibaba.fastjson.JSON; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.SimpleChannelInboundHandler; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | /** 10 | * @author throwable 11 | * @version v1.0 12 | * @description 13 | * @since 2020/1/18 14:16 14 | */ 15 | @Slf4j 16 | public class ClientHandler extends SimpleChannelInboundHandler { 17 | 18 | @Override 19 | protected void channelRead0(ChannelHandlerContext ctx, ResponseMessagePacket packet) throws Exception { 20 | log.info("接收到响应包,内容:{}", JSON.toJSONString(packet)); 21 | ResponseFuture responseFuture = ContractProxyFactory.RESPONSE_FUTURE_TABLE.get(packet.getSerialNumber()); 22 | if (null != responseFuture) { 23 | responseFuture.putResponse(packet); 24 | } else { 25 | log.warn("接收响应包查询ResponseFuture不存在,请求ID:{}", packet.getSerialNumber()); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/client/ContractProxyFactory.java: -------------------------------------------------------------------------------- 1 | package club.throwable.client; 2 | 3 | import club.throwable.contract.dto.SayHelloRequestDTO; 4 | import club.throwable.contract.dto.SayHelloResponseDTO; 5 | import club.throwable.exception.SendRequestException; 6 | import club.throwable.protocol.MessageType; 7 | import club.throwable.protocol.ProtocolConstant; 8 | import club.throwable.protocol.RequestMessagePacket; 9 | import club.throwable.protocol.ResponseMessagePacket; 10 | import club.throwable.protocol.serialize.FastJsonSerializer; 11 | import club.throwable.protocol.serialize.Serializer; 12 | import club.throwable.utils.ByteBufferUtils; 13 | import club.throwable.utils.SerialNumberUtils; 14 | import com.alibaba.fastjson.JSON; 15 | import com.google.common.collect.Maps; 16 | import io.netty.buffer.ByteBuf; 17 | import io.netty.channel.Channel; 18 | import io.netty.channel.ChannelFutureListener; 19 | import lombok.extern.slf4j.Slf4j; 20 | 21 | import java.lang.reflect.Proxy; 22 | import java.util.Iterator; 23 | import java.util.Map; 24 | import java.util.concurrent.*; 25 | import java.util.concurrent.atomic.AtomicInteger; 26 | 27 | /** 28 | * @author throwable 29 | * @version v1.0 30 | * @description 31 | * @since 2020/1/15 23:44 32 | */ 33 | @Slf4j 34 | public class ContractProxyFactory { 35 | 36 | private static final RequestArgumentExtractor EXTRACTOR = new DefaultRequestArgumentExtractor(); 37 | private static final ConcurrentMap, Object> CACHE = Maps.newConcurrentMap(); 38 | static final ConcurrentMap RESPONSE_FUTURE_TABLE = Maps.newConcurrentMap(); 39 | // 定义请求的最大超时时间为3秒 40 | private static final long REQUEST_TIMEOUT_MS = 3000; 41 | private static final ExecutorService EXECUTOR; 42 | private static final ScheduledExecutorService CLIENT_HOUSE_KEEPER; 43 | private static final Serializer SERIALIZER = FastJsonSerializer.X; 44 | 45 | /** 46 | * 线程数计数器 47 | */ 48 | private static final AtomicInteger COUNTER = new AtomicInteger(); 49 | 50 | 51 | @SuppressWarnings("unchecked") 52 | public static T ofProxy(Class interfaceKlass) { 53 | // 缓存契约接口的代理类实例 54 | return (T) CACHE.computeIfAbsent(interfaceKlass, x -> 55 | Proxy.newProxyInstance(interfaceKlass.getClassLoader(), new Class[]{interfaceKlass}, (target, method, args) -> { 56 | RequestArgumentExtractInput input = new RequestArgumentExtractInput(); 57 | input.setInterfaceKlass(interfaceKlass); 58 | input.setMethod(method); 59 | RequestArgumentExtractOutput output = EXTRACTOR.extract(input); 60 | // 封装请求参数 61 | RequestMessagePacket packet = new RequestMessagePacket(); 62 | packet.setMagicNumber(ProtocolConstant.MAGIC_NUMBER); 63 | packet.setVersion(ProtocolConstant.VERSION); 64 | packet.setSerialNumber(SerialNumberUtils.X.generateSerialNumber()); 65 | packet.setMessageType(MessageType.REQUEST); 66 | packet.setInterfaceName(output.getInterfaceName()); 67 | packet.setMethodName(output.getMethodName()); 68 | packet.setMethodArgumentSignatures(output.getMethodArgumentSignatures().toArray(new String[0])); 69 | packet.setMethodArguments(args); 70 | Channel channel = ClientChannelHolder.CHANNEL_REFERENCE.get(); 71 | return sendRequestSync(channel, packet, method.getReturnType()); 72 | })); 73 | } 74 | 75 | /** 76 | * 同步发送请求 77 | * 78 | * @param channel channel 79 | * @param packet packet 80 | * @return Object 81 | */ 82 | static Object sendRequestSync(Channel channel, RequestMessagePacket packet, Class returnType) { 83 | long beginTimestamp = System.currentTimeMillis(); 84 | ResponseFuture responseFuture = new ResponseFuture(packet.getSerialNumber(), REQUEST_TIMEOUT_MS); 85 | RESPONSE_FUTURE_TABLE.put(packet.getSerialNumber(), responseFuture); 86 | try { 87 | // 获取到承载响应Packet的Future 88 | Future packetFuture = EXECUTOR.submit(() -> { 89 | channel.writeAndFlush(packet).addListener((ChannelFutureListener) 90 | future -> responseFuture.setSendRequestSucceed(true)); 91 | return responseFuture.waitResponse(REQUEST_TIMEOUT_MS - (System.currentTimeMillis() - beginTimestamp)); 92 | }); 93 | ResponseMessagePacket responsePacket = packetFuture.get( 94 | REQUEST_TIMEOUT_MS - (System.currentTimeMillis() - beginTimestamp), TimeUnit.MILLISECONDS); 95 | if (null == responsePacket) { 96 | // 超时导致响应包获取失败 97 | throw new SendRequestException(String.format("ResponseMessagePacket获取超时,请求ID:%s", packet.getSerialNumber())); 98 | } else { 99 | ByteBuf payload = (ByteBuf) responsePacket.getPayload(); 100 | byte[] bytes = ByteBufferUtils.X.readBytes(payload); 101 | return SERIALIZER.decode(bytes, returnType); 102 | } 103 | } catch (Exception e) { 104 | log.error("同步发送请求异常,请求包:{}", JSON.toJSONString(packet), e); 105 | if (e instanceof RuntimeException) { 106 | throw (RuntimeException) e; 107 | } else { 108 | throw new SendRequestException(e); 109 | } 110 | } 111 | } 112 | 113 | public static void main(String[] args) throws Exception { 114 | SayHelloResponseDTO r = new SayHelloResponseDTO(); 115 | r.setContent("doge say hello!"); 116 | byte[] encode = SERIALIZER.encode(r); 117 | String x = "\"{\\\"content\\\":\\\"doge say hello!\\\"}\""; 118 | // String x = "{\"content\":\"doge say hello!\"}"; 119 | byte[] bytes = x.getBytes(); 120 | Object decode = SERIALIZER.decode(bytes, SayHelloResponseDTO.class); 121 | System.out.println(decode); 122 | } 123 | 124 | static void scanResponseFutureTable() { 125 | log.info("开始执行ResponseFutureTable清理任务......"); 126 | Iterator> iterator = RESPONSE_FUTURE_TABLE.entrySet().iterator(); 127 | while (iterator.hasNext()) { 128 | Map.Entry entry = iterator.next(); 129 | ResponseFuture responseFuture = entry.getValue(); 130 | if (responseFuture.timeout()) { 131 | iterator.remove(); 132 | log.warn("移除过期的请求ResponseFuture,请求ID:{}", entry.getKey()); 133 | } 134 | } 135 | log.info("执行ResponseFutureTable清理任务结束......"); 136 | } 137 | 138 | static { 139 | int n = Runtime.getRuntime().availableProcessors(); 140 | EXECUTOR = new ThreadPoolExecutor(n * 2, n * 2, 0, TimeUnit.SECONDS, 141 | new ArrayBlockingQueue<>(50), runnable -> { 142 | Thread thread = new Thread(runnable); 143 | thread.setDaemon(true); 144 | thread.setName("client-request-executor-" + COUNTER.getAndIncrement()); 145 | return thread; 146 | }); 147 | CLIENT_HOUSE_KEEPER = new ScheduledThreadPoolExecutor(1, runnable -> { 148 | Thread thread = new Thread(runnable); 149 | thread.setDaemon(true); 150 | thread.setName("client-house-keeper"); 151 | return thread; 152 | }); 153 | CLIENT_HOUSE_KEEPER.scheduleWithFixedDelay(ContractProxyFactory::scanResponseFutureTable, 5, 5, TimeUnit.SECONDS); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/client/DefaultRequestArgumentExtractor.java: -------------------------------------------------------------------------------- 1 | package club.throwable.client; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.common.collect.Maps; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | import java.lang.reflect.Method; 8 | import java.util.List; 9 | import java.util.Objects; 10 | import java.util.concurrent.ConcurrentMap; 11 | 12 | /** 13 | * @author throwable 14 | * @version v1.0 15 | * @description 16 | * @since 2020/1/15 23:36 17 | */ 18 | public class DefaultRequestArgumentExtractor implements RequestArgumentExtractor { 19 | 20 | private final ConcurrentMap cache = Maps.newConcurrentMap(); 21 | 22 | @Override 23 | 24 | public RequestArgumentExtractOutput extract(RequestArgumentExtractInput input) { 25 | Class interfaceKlass = input.getInterfaceKlass(); 26 | Method method = input.getMethod(); 27 | String methodName = method.getName(); 28 | Class[] parameterTypes = method.getParameterTypes(); 29 | return cache.computeIfAbsent(new CacheKey(interfaceKlass.getName(), methodName, 30 | Lists.newArrayList(parameterTypes)), x -> { 31 | RequestArgumentExtractOutput output = new RequestArgumentExtractOutput(); 32 | output.setInterfaceName(interfaceKlass.getName()); 33 | List methodArgumentSignatures = Lists.newArrayList(); 34 | for (Class klass : parameterTypes) { 35 | methodArgumentSignatures.add(klass.getName()); 36 | } 37 | output.setMethodArgumentSignatures(methodArgumentSignatures); 38 | output.setMethodName(methodName); 39 | output.setMethodArgumentTypes(x.parameterTypes); 40 | return output; 41 | }); 42 | } 43 | 44 | @RequiredArgsConstructor 45 | private static class CacheKey { 46 | 47 | private final String interfaceName; 48 | private final String methodName; 49 | private final List> parameterTypes; 50 | 51 | @Override 52 | public boolean equals(Object o) { 53 | if (this == o) return true; 54 | if (o == null || getClass() != o.getClass()) return false; 55 | CacheKey cacheKey = (CacheKey) o; 56 | return Objects.equals(interfaceName, cacheKey.interfaceName) && 57 | Objects.equals(methodName, cacheKey.methodName) && 58 | Objects.equals(parameterTypes, cacheKey.parameterTypes); 59 | } 60 | 61 | @Override 62 | public int hashCode() { 63 | return Objects.hash(interfaceName, methodName, parameterTypes); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/client/RequestArgumentExtractInput.java: -------------------------------------------------------------------------------- 1 | package club.throwable.client; 2 | 3 | import lombok.Data; 4 | 5 | import java.lang.reflect.Method; 6 | 7 | /** 8 | * @author throwable 9 | * @version v1.0 10 | * @description 11 | * @since 2020/1/15 23:32 12 | */ 13 | @Data 14 | public class RequestArgumentExtractInput { 15 | 16 | private Class interfaceKlass; 17 | 18 | private Method method; 19 | } 20 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/client/RequestArgumentExtractOutput.java: -------------------------------------------------------------------------------- 1 | package club.throwable.client; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author throwable 9 | * @version v1.0 10 | * @description 11 | * @since 2020/1/15 23:32 12 | */ 13 | @Data 14 | public class RequestArgumentExtractOutput { 15 | 16 | private String interfaceName; 17 | 18 | private String methodName; 19 | 20 | private List methodArgumentSignatures; 21 | 22 | private List> methodArgumentTypes; 23 | } 24 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/client/RequestArgumentExtractor.java: -------------------------------------------------------------------------------- 1 | package club.throwable.client; 2 | 3 | /** 4 | * @author throwable 5 | * @version v1.0 6 | * @description 7 | * @since 2020/1/15 23:31 8 | */ 9 | public interface RequestArgumentExtractor { 10 | 11 | RequestArgumentExtractOutput extract(RequestArgumentExtractInput input); 12 | } 13 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/client/ResponseFuture.java: -------------------------------------------------------------------------------- 1 | package club.throwable.client; 2 | 3 | import club.throwable.protocol.ResponseMessagePacket; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.util.concurrent.CountDownLatch; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | /** 12 | * @author throwable 13 | * @version v1.0 14 | * @description 15 | * @since 2020/1/18 13:30 16 | */ 17 | @ToString 18 | public class ResponseFuture { 19 | 20 | private final long beginTimestamp = System.currentTimeMillis(); 21 | @Getter 22 | private final long timeoutMilliseconds; 23 | @Getter 24 | private final String requestId; 25 | @Setter 26 | @Getter 27 | private volatile boolean sendRequestSucceed = false; 28 | @Setter 29 | @Getter 30 | private volatile Throwable cause; 31 | @Getter 32 | private volatile ResponseMessagePacket response; 33 | 34 | private final CountDownLatch latch = new CountDownLatch(1); 35 | 36 | public ResponseFuture(String requestId, long timeoutMilliseconds) { 37 | this.requestId = requestId; 38 | this.timeoutMilliseconds = timeoutMilliseconds; 39 | } 40 | 41 | public boolean timeout() { 42 | return System.currentTimeMillis() - beginTimestamp > timeoutMilliseconds; 43 | } 44 | 45 | public ResponseMessagePacket waitResponse(final long timeoutMilliseconds) throws InterruptedException { 46 | latch.await(timeoutMilliseconds, TimeUnit.MILLISECONDS); 47 | return response; 48 | } 49 | 50 | public void putResponse(ResponseMessagePacket response) throws InterruptedException { 51 | this.response = response; 52 | latch.countDown(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/client/TestDynamicProxy.java: -------------------------------------------------------------------------------- 1 | package club.throwable.client; 2 | 3 | import club.throwable.contract.HelloService; 4 | import com.alibaba.fastjson.JSON; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | import java.lang.reflect.InvocationHandler; 8 | import java.lang.reflect.Method; 9 | import java.lang.reflect.Proxy; 10 | 11 | /** 12 | * @author throwable 13 | * @version v1.0 14 | * @description 15 | * @since 2020/1/15 23:14 16 | */ 17 | public class TestDynamicProxy { 18 | 19 | public static void main(String[] args) throws Exception { 20 | Class interfaceKlass = HelloService.class; 21 | InvocationHandler handler = new HelloServiceImpl(interfaceKlass); 22 | HelloService helloService = (HelloService) 23 | Proxy.newProxyInstance(interfaceKlass.getClassLoader(), new Class[]{interfaceKlass}, handler); 24 | System.out.println(helloService.sayHello("throwable")); 25 | } 26 | 27 | @RequiredArgsConstructor 28 | private static class HelloServiceImpl implements InvocationHandler { 29 | 30 | private final Class interfaceKlass; 31 | 32 | @Override 33 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 34 | // 这里应该根据方法的返回值类型去决定返回结果 35 | return String.format("[%s#%s]方法被调用,参数列表:%s", interfaceKlass.getName(), method.getName(), 36 | JSON.toJSONString(args)); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/contract/HelloService.java: -------------------------------------------------------------------------------- 1 | package club.throwable.contract; 2 | 3 | import club.throwable.contract.dto.SayHelloRequestDTO; 4 | import club.throwable.contract.dto.SayHelloResponseDTO; 5 | 6 | /** 7 | * @author throwable 8 | * @version v1.0 9 | * @description 契约接口 10 | * @since 2020/1/3 20:32 11 | */ 12 | public interface HelloService { 13 | 14 | String sayHello(String name); 15 | 16 | SayHelloResponseDTO sayHello(SayHelloRequestDTO request); 17 | } 18 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/contract/dto/SayHelloRequestDTO.java: -------------------------------------------------------------------------------- 1 | package club.throwable.contract.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author throwable 7 | * @version v1 8 | * @description 9 | * @since 2020/10/20 0:39 10 | */ 11 | @Data 12 | public class SayHelloRequestDTO { 13 | 14 | private String name; 15 | } 16 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/contract/dto/SayHelloResponseDTO.java: -------------------------------------------------------------------------------- 1 | package club.throwable.contract.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author throwable 7 | * @version v1 8 | * @description 9 | * @since 2020/10/20 0:39 10 | */ 11 | @Data 12 | public class SayHelloResponseDTO { 13 | 14 | private String content; 15 | } 16 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/exception/ArgumentConvertException.java: -------------------------------------------------------------------------------- 1 | package club.throwable.exception; 2 | 3 | /** 4 | * @author throwable 5 | * @version v1.0 6 | * @description 7 | * @since 2020/1/14 23:26 8 | */ 9 | public class ArgumentConvertException extends RuntimeException { 10 | 11 | public ArgumentConvertException(String message) { 12 | super(message); 13 | } 14 | 15 | public ArgumentConvertException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public ArgumentConvertException(Throwable cause) { 20 | super(cause); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/exception/MethodMatchException.java: -------------------------------------------------------------------------------- 1 | package club.throwable.exception; 2 | 3 | /** 4 | * @author throwable 5 | * @version v1.0 6 | * @description 7 | * @since 2020/1/12 11:55 8 | */ 9 | public class MethodMatchException extends RuntimeException { 10 | 11 | public MethodMatchException(String message) { 12 | super(message); 13 | } 14 | 15 | public MethodMatchException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public MethodMatchException(Throwable cause) { 20 | super(cause); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/exception/SendRequestException.java: -------------------------------------------------------------------------------- 1 | package club.throwable.exception; 2 | 3 | /** 4 | * @author throwable 5 | * @version v1.0 6 | * @description 7 | * @since 2020/1/18 14:05 8 | */ 9 | public class SendRequestException extends RuntimeException { 10 | 11 | public SendRequestException(String message) { 12 | super(message); 13 | } 14 | 15 | public SendRequestException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public SendRequestException(Throwable cause) { 20 | super(cause); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/protocol/BaseMessagePacket.java: -------------------------------------------------------------------------------- 1 | package club.throwable.protocol; 2 | 3 | import com.google.common.collect.Maps; 4 | import io.netty.buffer.ByteBuf; 5 | import lombok.Data; 6 | 7 | import java.io.Serializable; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * @author throwable 13 | * @version v1.0 14 | * @description 基础消息数据包 15 | * @since 2020/1/2 23:37 16 | */ 17 | @Data 18 | public abstract class BaseMessagePacket implements Serializable { 19 | 20 | /** 21 | * 魔数 22 | */ 23 | private int magicNumber; 24 | 25 | /** 26 | * 版本号 27 | */ 28 | private int version; 29 | 30 | /** 31 | * 流水号 32 | */ 33 | private String serialNumber; 34 | 35 | /** 36 | * 消息类型 37 | */ 38 | private MessageType messageType; 39 | 40 | /** 41 | * 附件 - K-V形式 42 | */ 43 | private Map attachments = new HashMap<>(); 44 | 45 | /** 46 | * 添加附件 47 | */ 48 | public void addAttachment(String key, String value) { 49 | attachments.put(key, value); 50 | } 51 | 52 | /** 53 | * 基础包encode 54 | * 55 | * @param out out 56 | */ 57 | public void encode(ByteBuf out) { 58 | // 魔数 59 | out.writeInt(getMagicNumber()); 60 | // 版本 61 | out.writeInt(getVersion()); 62 | // 流水号 63 | out.writeInt(getSerialNumber().length()); 64 | out.writeCharSequence(getSerialNumber(), ProtocolConstant.UTF_8); 65 | // 消息类型 66 | out.writeByte(getMessageType().getType()); 67 | // 附件size 68 | Map attachments = getAttachments(); 69 | out.writeInt(attachments.size()); 70 | // 附件内容 71 | attachments.forEach((k, v) -> { 72 | out.writeInt(k.length()); 73 | out.writeCharSequence(k, ProtocolConstant.UTF_8); 74 | out.writeInt(v.length()); 75 | out.writeCharSequence(v, ProtocolConstant.UTF_8); 76 | }); 77 | } 78 | 79 | /** 80 | * 基础包decode 81 | * 82 | * @param in in 83 | */ 84 | public void decode(ByteBuf in) { 85 | // 魔数 86 | setMagicNumber(in.readInt()); 87 | // 版本 88 | setVersion(in.readInt()); 89 | // 流水号 90 | int serialNumberLength = in.readInt(); 91 | setSerialNumber(in.readCharSequence(serialNumberLength, ProtocolConstant.UTF_8).toString()); 92 | // 消息类型 93 | byte messageTypeByte = in.readByte(); 94 | setMessageType(MessageType.fromValue(messageTypeByte)); 95 | // 附件 96 | Map attachments = Maps.newHashMap(); 97 | setAttachments(attachments); 98 | int attachmentSize = in.readInt(); 99 | if (attachmentSize > 0) { 100 | for (int i = 0; i < attachmentSize; i++) { 101 | int keyLength = in.readInt(); 102 | String key = in.readCharSequence(keyLength, ProtocolConstant.UTF_8).toString(); 103 | int valueLength = in.readInt(); 104 | String value = in.readCharSequence(valueLength, ProtocolConstant.UTF_8).toString(); 105 | attachments.put(key, value); 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/protocol/MessageType.java: -------------------------------------------------------------------------------- 1 | package club.throwable.protocol; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | /** 7 | * @author throwable 8 | * @version v1.0 9 | * @description 10 | * @since 2019/9/29 11:09 11 | */ 12 | @RequiredArgsConstructor 13 | public enum MessageType { 14 | 15 | /** 16 | * 请求 17 | */ 18 | REQUEST((byte) 1), 19 | 20 | /** 21 | * 响应 22 | */ 23 | RESPONSE((byte) 2), 24 | 25 | /** 26 | * PING 27 | */ 28 | PING((byte) 3), 29 | 30 | /** 31 | * PONG 32 | */ 33 | PONG((byte) 4), 34 | 35 | /** 36 | * NULL 37 | */ 38 | NULL((byte) 5), 39 | 40 | ; 41 | 42 | @Getter 43 | private final Byte type; 44 | 45 | public static MessageType fromValue(byte value) { 46 | for (MessageType type : MessageType.values()) { 47 | if (type.getType() == value) { 48 | return type; 49 | } 50 | } 51 | throw new IllegalArgumentException(String.format("value = %s", value)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/protocol/ProtocolConstant.java: -------------------------------------------------------------------------------- 1 | package club.throwable.protocol; 2 | 3 | import lombok.Data; 4 | 5 | import java.nio.charset.Charset; 6 | import java.nio.charset.StandardCharsets; 7 | 8 | /** 9 | * @author throwable 10 | * @version v1.0 11 | * @description 12 | * @since 2020/1/3 9:17 13 | */ 14 | @Data 15 | public class ProtocolConstant { 16 | 17 | public static final int MAGIC_NUMBER = 10086; 18 | 19 | public static final int VERSION = 1; 20 | 21 | public static final Charset UTF_8 = StandardCharsets.UTF_8; 22 | } 23 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/protocol/RequestMessagePacket.java: -------------------------------------------------------------------------------- 1 | package club.throwable.protocol; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | /** 7 | * @author throwable 8 | * @version v1.0 9 | * @description 请求消息数据包 10 | * @since 2020/1/2 23:37 11 | */ 12 | @EqualsAndHashCode(callSuper = true) 13 | @Data 14 | public class RequestMessagePacket extends BaseMessagePacket { 15 | 16 | /** 17 | * 接口全类名 18 | */ 19 | private String interfaceName; 20 | 21 | /** 22 | * 方法名 23 | */ 24 | private String methodName; 25 | 26 | /** 27 | * 方法参数签名 28 | */ 29 | private String[] methodArgumentSignatures; 30 | 31 | /** 32 | * 方法参数 33 | */ 34 | private Object[] methodArguments; 35 | } 36 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/protocol/RequestMessagePacketDecoder.java: -------------------------------------------------------------------------------- 1 | package club.throwable.protocol; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ByteToMessageDecoder; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * @author throwable 12 | * @version v1.0 13 | * @description 14 | * @since 2020/1/3 0:08 15 | */ 16 | @RequiredArgsConstructor 17 | public class RequestMessagePacketDecoder extends ByteToMessageDecoder { 18 | 19 | @Override 20 | protected void decode(ChannelHandlerContext context, ByteBuf in, List list) throws Exception { 21 | RequestMessagePacket packet = new RequestMessagePacket(); 22 | // 基础包decode 23 | packet.decode(in); 24 | // 接口全类名 25 | int interfaceNameLength = in.readInt(); 26 | packet.setInterfaceName(in.readCharSequence(interfaceNameLength, ProtocolConstant.UTF_8).toString()); 27 | // 方法名 28 | int methodNameLength = in.readInt(); 29 | packet.setMethodName(in.readCharSequence(methodNameLength, ProtocolConstant.UTF_8).toString()); 30 | // 方法参数签名 31 | int methodArgumentSignatureArrayLength = in.readInt(); 32 | if (methodArgumentSignatureArrayLength > 0) { 33 | String[] methodArgumentSignatures = new String[methodArgumentSignatureArrayLength]; 34 | for (int i = 0; i < methodArgumentSignatureArrayLength; i++) { 35 | int methodArgumentSignatureLength = in.readInt(); 36 | methodArgumentSignatures[i] = in.readCharSequence(methodArgumentSignatureLength, ProtocolConstant.UTF_8).toString(); 37 | } 38 | packet.setMethodArgumentSignatures(methodArgumentSignatures); 39 | } 40 | // 方法参数 41 | int methodArgumentArrayLength = in.readInt(); 42 | if (methodArgumentArrayLength > 0) { 43 | // 这里的Object[]实际上是ByteBuf[] - 后面需要二次加工为对应类型的实例 44 | Object[] methodArguments = new Object[methodArgumentArrayLength]; 45 | for (int i = 0; i < methodArgumentArrayLength; i++) { 46 | int byteLength = in.readInt(); 47 | methodArguments[i] = in.readBytes(byteLength); 48 | } 49 | packet.setMethodArguments(methodArguments); 50 | } 51 | list.add(packet); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/protocol/RequestMessagePacketEncoder.java: -------------------------------------------------------------------------------- 1 | package club.throwable.protocol; 2 | 3 | import club.throwable.protocol.serialize.Serializer; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.MessageToByteEncoder; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | /** 10 | * @author throwable 11 | * @version v1.0 12 | * @description 13 | * @since 2020/1/3 0:05 14 | */ 15 | @RequiredArgsConstructor 16 | public class RequestMessagePacketEncoder extends MessageToByteEncoder { 17 | 18 | private final Serializer serializer; 19 | 20 | @Override 21 | protected void encode(ChannelHandlerContext context, RequestMessagePacket packet, ByteBuf out) throws Exception { 22 | // 基础包encode 23 | packet.encode(out); 24 | // 接口全类名 25 | out.writeInt(packet.getInterfaceName().length()); 26 | out.writeCharSequence(packet.getInterfaceName(), ProtocolConstant.UTF_8); 27 | // 方法名 28 | out.writeInt(packet.getMethodName().length()); 29 | out.writeCharSequence(packet.getMethodName(), ProtocolConstant.UTF_8); 30 | // 方法参数签名(String[]类型) - 非必须 31 | if (null != packet.getMethodArgumentSignatures()) { 32 | int len = packet.getMethodArgumentSignatures().length; 33 | // 方法参数签名数组长度 34 | out.writeInt(len); 35 | for (int i = 0; i < len; i++) { 36 | String methodArgumentSignature = packet.getMethodArgumentSignatures()[i]; 37 | out.writeInt(methodArgumentSignature.length()); 38 | out.writeCharSequence(methodArgumentSignature, ProtocolConstant.UTF_8); 39 | } 40 | } else { 41 | out.writeInt(0); 42 | } 43 | // 方法参数(Object[]类型) - 非必须 44 | if (null != packet.getMethodArguments()) { 45 | int len = packet.getMethodArguments().length; 46 | // 方法参数数组长度 47 | out.writeInt(len); 48 | for (int i = 0; i < len; i++) { 49 | byte[] bytes = serializer.encode(packet.getMethodArguments()[i]); 50 | out.writeInt(bytes.length); 51 | out.writeBytes(bytes); 52 | } 53 | } else { 54 | out.writeInt(0); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/protocol/ResponseMessagePacket.java: -------------------------------------------------------------------------------- 1 | package club.throwable.protocol; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | /** 7 | * @author throwable 8 | * @version v1.0 9 | * @description 响应消息数据包 10 | * @since 2020/1/2 23:37 11 | */ 12 | @EqualsAndHashCode(callSuper = true) 13 | @Data 14 | public class ResponseMessagePacket extends BaseMessagePacket { 15 | 16 | /** 17 | * error code 18 | */ 19 | private Long errorCode; 20 | 21 | /** 22 | * 消息描述 23 | */ 24 | private String message; 25 | 26 | /** 27 | * 消息载荷 - 暂定为字符串 28 | */ 29 | private Object payload; 30 | } 31 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/protocol/ResponseMessagePacketDecoder.java: -------------------------------------------------------------------------------- 1 | package club.throwable.protocol; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ByteToMessageDecoder; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author throwable 11 | * @version v1.0 12 | * @description 13 | * @since 2020/1/12 20:47 14 | */ 15 | public class ResponseMessagePacketDecoder extends ByteToMessageDecoder { 16 | 17 | @Override 18 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 19 | ResponseMessagePacket packet = new ResponseMessagePacket(); 20 | // 基础包decode 21 | packet.decode(in); 22 | // error code 23 | packet.setErrorCode(in.readLong()); 24 | // message 25 | int messageLength = in.readInt(); 26 | packet.setMessage(in.readCharSequence(messageLength, ProtocolConstant.UTF_8).toString()); 27 | // payload - ByteBuf实例 28 | int payloadLength = in.readInt(); 29 | packet.setPayload(in.readBytes(payloadLength)); 30 | out.add(packet); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/protocol/ResponseMessagePacketEncoder.java: -------------------------------------------------------------------------------- 1 | package club.throwable.protocol; 2 | 3 | import club.throwable.protocol.serialize.Serializer; 4 | import club.throwable.utils.ByteBufferUtils; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.MessageToByteEncoder; 8 | import lombok.RequiredArgsConstructor; 9 | 10 | /** 11 | * @author throwable 12 | * @version v1.0 13 | * @description 14 | * @since 2020/1/12 20:47 15 | */ 16 | @RequiredArgsConstructor 17 | public class ResponseMessagePacketEncoder extends MessageToByteEncoder { 18 | 19 | private final Serializer serializer; 20 | 21 | @Override 22 | protected void encode(ChannelHandlerContext ctx, ResponseMessagePacket packet, ByteBuf out) throws Exception { 23 | // 基础包encode 24 | packet.encode(out); 25 | // error code 26 | out.writeLong(packet.getErrorCode()); 27 | // message 28 | String message = packet.getMessage(); 29 | ByteBufferUtils.X.encodeUtf8CharSequence(out, message); 30 | // payload 31 | byte[] bytes = serializer.encode(packet.getPayload()); 32 | out.writeInt(bytes.length); 33 | out.writeBytes(bytes); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/protocol/TestProtocolClient.java: -------------------------------------------------------------------------------- 1 | package club.throwable.protocol; 2 | 3 | import club.throwable.protocol.serialize.FastJsonSerializer; 4 | import club.throwable.utils.SerialNumberUtils; 5 | import com.alibaba.fastjson.JSON; 6 | import io.netty.bootstrap.Bootstrap; 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.channel.*; 9 | import io.netty.channel.nio.NioEventLoopGroup; 10 | import io.netty.channel.socket.SocketChannel; 11 | import io.netty.channel.socket.nio.NioSocketChannel; 12 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 13 | import io.netty.handler.codec.LengthFieldPrepender; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | /** 17 | * @author throwable 18 | * @version v1.0 19 | * @description 20 | * @since 2020/1/12 22:10 21 | */ 22 | @Slf4j 23 | public class TestProtocolClient { 24 | 25 | public static void main(String[] args) throws Exception { 26 | int port = 9092; 27 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 28 | Bootstrap bootstrap = new Bootstrap(); 29 | try { 30 | bootstrap.group(workerGroup); 31 | bootstrap.channel(NioSocketChannel.class); 32 | bootstrap.option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE); 33 | bootstrap.option(ChannelOption.TCP_NODELAY, Boolean.TRUE); 34 | bootstrap.handler(new ChannelInitializer() { 35 | 36 | @Override 37 | protected void initChannel(SocketChannel ch) throws Exception { 38 | ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4)); 39 | ch.pipeline().addLast(new LengthFieldPrepender(4)); 40 | ch.pipeline().addLast(new RequestMessagePacketEncoder(FastJsonSerializer.X)); 41 | ch.pipeline().addLast(new ResponseMessagePacketDecoder()); 42 | ch.pipeline().addLast(new SimpleChannelInboundHandler() { 43 | @Override 44 | protected void channelRead0(ChannelHandlerContext ctx, ResponseMessagePacket packet) throws Exception { 45 | Object targetPayload = packet.getPayload(); 46 | if (targetPayload instanceof ByteBuf) { 47 | ByteBuf byteBuf = (ByteBuf) targetPayload; 48 | int readableByteLength = byteBuf.readableBytes(); 49 | byte[] bytes = new byte[readableByteLength]; 50 | byteBuf.readBytes(bytes); 51 | targetPayload = FastJsonSerializer.X.decode(bytes, String.class); 52 | byteBuf.release(); 53 | } 54 | packet.setPayload(targetPayload); 55 | log.info("接收到来自服务端的响应消息,消息内容:{}", JSON.toJSONString(packet)); 56 | } 57 | }); 58 | } 59 | }); 60 | ChannelFuture future = bootstrap.connect("localhost", port).sync(); 61 | log.info("启动NettyClient[{}]成功...", port); 62 | Channel channel = future.channel(); 63 | RequestMessagePacket packet = new RequestMessagePacket(); 64 | packet.setMagicNumber(ProtocolConstant.MAGIC_NUMBER); 65 | packet.setVersion(ProtocolConstant.VERSION); 66 | packet.setSerialNumber(SerialNumberUtils.X.generateSerialNumber()); 67 | packet.setMessageType(MessageType.REQUEST); 68 | packet.setInterfaceName("club.throwable.contract.HelloService"); 69 | packet.setMethodName("sayHello"); 70 | packet.setMethodArgumentSignatures(new String[]{"java.lang.String"}); 71 | packet.setMethodArguments(new Object[]{"doge"}); 72 | channel.writeAndFlush(packet); 73 | future.channel().closeFuture().sync(); 74 | } finally { 75 | workerGroup.shutdownGracefully(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/protocol/TestProtocolServer.java: -------------------------------------------------------------------------------- 1 | package club.throwable.protocol; 2 | 3 | import club.throwable.protocol.serialize.FastJsonSerializer; 4 | import club.throwable.server.ServerHandler; 5 | import com.alibaba.fastjson.JSON; 6 | import io.netty.bootstrap.ServerBootstrap; 7 | import io.netty.channel.*; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 12 | import io.netty.handler.codec.LengthFieldPrepender; 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | /** 16 | * @author throwable 17 | * @version v1.0 18 | * @description 19 | * @since 2020/1/12 22:10 20 | */ 21 | @Slf4j 22 | public class TestProtocolServer { 23 | 24 | public static void main(String[] args) throws Exception { 25 | int port = 9092; 26 | ServerBootstrap bootstrap = new ServerBootstrap(); 27 | EventLoopGroup bossGroup = new NioEventLoopGroup(); 28 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 29 | try { 30 | bootstrap.group(bossGroup, workerGroup) 31 | .channel(NioServerSocketChannel.class) 32 | .childHandler(new ChannelInitializer() { 33 | 34 | @Override 35 | protected void initChannel(SocketChannel ch) throws Exception { 36 | ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4)); 37 | ch.pipeline().addLast(new LengthFieldPrepender(4)); 38 | ch.pipeline().addLast(new RequestMessagePacketDecoder()); 39 | ch.pipeline().addLast(new ResponseMessagePacketEncoder(FastJsonSerializer.X)); 40 | ch.pipeline().addLast(new SimpleChannelInboundHandler() { 41 | 42 | @Override 43 | protected void channelRead0(ChannelHandlerContext ctx, RequestMessagePacket packet) throws Exception { 44 | log.info("接收到来自客户端的请求消息,消息内容:{}", JSON.toJSONString(packet)); 45 | ResponseMessagePacket response = ServerHandler.applyResponseMessagePacket(packet); 46 | response.setPayload("{\"name\":\"throwable\"}"); 47 | ctx.writeAndFlush(response); 48 | } 49 | }); 50 | } 51 | }); 52 | ChannelFuture future = bootstrap.bind(port).sync(); 53 | log.info("启动NettyServer[{}]成功...", port); 54 | future.channel().closeFuture().sync(); 55 | } finally { 56 | workerGroup.shutdownGracefully(); 57 | bossGroup.shutdownGracefully(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/protocol/serialize/FastJsonSerializer.java: -------------------------------------------------------------------------------- 1 | package club.throwable.protocol.serialize; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | /** 6 | * @author throwable 7 | * @version v1.0 8 | * @description 9 | * @since 2020/1/3 9:43 10 | */ 11 | public enum FastJsonSerializer implements Serializer { 12 | 13 | // 单例 14 | X; 15 | 16 | @Override 17 | public byte[] encode(Object target) { 18 | return JSON.toJSONBytes(target); 19 | } 20 | 21 | @Override 22 | public Object decode(byte[] bytes, Class targetClass) { 23 | return JSON.parseObject(bytes, targetClass); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/protocol/serialize/Serializer.java: -------------------------------------------------------------------------------- 1 | package club.throwable.protocol.serialize; 2 | 3 | /** 4 | * @author throwable 5 | * @version v1.0 6 | * @description 序列化器 7 | * @since 2020/1/3 9:40 8 | */ 9 | public interface Serializer { 10 | 11 | byte[] encode(Object target); 12 | 13 | Object decode(byte[] bytes, Class targetClass); 14 | } 15 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/server/ArgumentConvertInput.java: -------------------------------------------------------------------------------- 1 | package club.throwable.server; 2 | 3 | import lombok.Data; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.List; 7 | 8 | /** 9 | * @author throwable 10 | * @version v1.0 11 | * @description 12 | * @since 2020/1/14 23:22 13 | */ 14 | @Data 15 | public class ArgumentConvertInput { 16 | 17 | /** 18 | * 目标方法 19 | */ 20 | private Method method; 21 | 22 | /** 23 | * 方法参数类型列表 24 | */ 25 | private List> parameterTypes; 26 | 27 | /** 28 | * 方法参数列表 29 | */ 30 | private List arguments; 31 | } 32 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/server/ArgumentConvertOutput.java: -------------------------------------------------------------------------------- 1 | package club.throwable.server; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author throwable 7 | * @version v1.0 8 | * @description 9 | * @since 2020/1/14 23:24 10 | */ 11 | @Data 12 | public class ArgumentConvertOutput { 13 | 14 | 15 | private Object[] arguments; 16 | } 17 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/server/BaseMethodMatcher.java: -------------------------------------------------------------------------------- 1 | package club.throwable.server; 2 | 3 | import club.throwable.exception.MethodMatchException; 4 | import com.alibaba.fastjson.JSON; 5 | import com.google.common.collect.Lists; 6 | import com.google.common.collect.Maps; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.util.ClassUtils; 9 | import org.springframework.util.ReflectionUtils; 10 | 11 | import java.lang.reflect.Method; 12 | import java.util.List; 13 | import java.util.Objects; 14 | import java.util.Optional; 15 | import java.util.concurrent.ConcurrentMap; 16 | 17 | /** 18 | * @author throwable 19 | * @version v1.0 20 | * @description 21 | * @since 2020/1/12 11:38 22 | */ 23 | @Slf4j 24 | abstract class BaseMethodMatcher implements MethodMatcher { 25 | 26 | private final ConcurrentMap cache = Maps.newConcurrentMap(); 27 | 28 | @Override 29 | public MethodMatchOutput selectOneBestMatchMethod(MethodMatchInput input) { 30 | return cache.computeIfAbsent(input, in -> { 31 | try { 32 | MethodMatchOutput output = new MethodMatchOutput(); 33 | Class interfaceClass = Class.forName(in.getInterfaceName()); 34 | // 获取宿主类信息 35 | HostClassMethodInfo info = findHostClassMethodInfo(interfaceClass); 36 | List targetMethods = Lists.newArrayList(); 37 | ReflectionUtils.doWithMethods(info.getHostUserClass(), targetMethods::add, method -> { 38 | String methodName = method.getName(); 39 | Class declaringClass = method.getDeclaringClass(); 40 | List> inputParameterTypes = Optional.ofNullable(in.getMethodArgumentSignatures()) 41 | .map(mas -> { 42 | List> list = Lists.newArrayList(); 43 | mas.forEach(ma -> list.add(ClassUtils.resolveClassName(ma, null))); 44 | return list; 45 | }).orElse(Lists.newArrayList()); 46 | output.setParameterTypes(inputParameterTypes); 47 | // 如果传入了参数签名列表,优先使用参数签名列表类型进行匹配 48 | if (!inputParameterTypes.isEmpty()) { 49 | List> parameterTypes = Lists.newArrayList(method.getParameterTypes()); 50 | return Objects.equals(methodName, in.getMethodName()) && 51 | Objects.equals(info.getHostUserClass(), declaringClass) && 52 | Objects.equals(parameterTypes, inputParameterTypes); 53 | } 54 | // 如果没有传入参数签名列表,那么使用参数的数量进行匹配 55 | if (in.getMethodArgumentArraySize() > 0) { 56 | List> parameterTypes = Lists.newArrayList(method.getParameterTypes()); 57 | return Objects.equals(methodName, in.getMethodName()) && 58 | Objects.equals(info.getHostUserClass(), declaringClass) && 59 | in.getMethodArgumentArraySize() == parameterTypes.size(); 60 | 61 | } 62 | // 如果参数签名列表和参数列表都没有传入,那么只能通过方法名称和方法实例的宿主类型匹配 63 | return Objects.equals(methodName, in.getMethodName()) && 64 | Objects.equals(info.getHostUserClass(), declaringClass); 65 | }); 66 | if (targetMethods.size() != 1) { 67 | throw new MethodMatchException(String.format("查找到目标方法数量不等于1,interface:%s,method:%s", 68 | in.getInterfaceName(), in.getMethodName())); 69 | } 70 | Method targetMethod = targetMethods.get(0); 71 | output.setTargetClass(info.getHostClass()); 72 | output.setTargetMethod(targetMethod); 73 | output.setTargetUserClass(info.getHostUserClass()); 74 | output.setTarget(info.getHostTarget()); 75 | return output; 76 | } catch (Exception e) { 77 | log.error("查找匹配度最高的方法失败,输入参数:{}", JSON.toJSONString(in), e); 78 | if (e instanceof MethodMatchException) { 79 | throw (MethodMatchException) e; 80 | } else { 81 | throw new MethodMatchException(e); 82 | } 83 | } 84 | }); 85 | } 86 | 87 | /** 88 | * 获取宿主类的信息 89 | * 90 | * @param interfaceClass interfaceClass 91 | * @return HostClassMethodInfo 92 | */ 93 | abstract HostClassMethodInfo findHostClassMethodInfo(Class interfaceClass); 94 | } 95 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/server/DefaultMethodArgumentConverter.java: -------------------------------------------------------------------------------- 1 | package club.throwable.server; 2 | 3 | import club.throwable.exception.ArgumentConvertException; 4 | import club.throwable.protocol.serialize.FastJsonSerializer; 5 | import club.throwable.protocol.serialize.Serializer; 6 | import io.netty.buffer.ByteBuf; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * @author throwable 14 | * @version v1.0 15 | * @description 16 | * @since 2020/1/14 23:25 17 | */ 18 | @Slf4j 19 | @Component 20 | public class DefaultMethodArgumentConverter implements MethodArgumentConverter { 21 | 22 | private final Serializer serializer = FastJsonSerializer.X; 23 | 24 | @Override 25 | public ArgumentConvertOutput convert(ArgumentConvertInput input) { 26 | ArgumentConvertOutput output = new ArgumentConvertOutput(); 27 | try { 28 | if (null == input.getArguments() || input.getArguments().isEmpty()) { 29 | output.setArguments(new Object[0]); 30 | return output; 31 | } 32 | List> inputParameterTypes = input.getParameterTypes(); 33 | int size = inputParameterTypes.size(); 34 | if (size > 0) { 35 | Object[] arguments = new Object[size]; 36 | for (int i = 0; i < size; i++) { 37 | ByteBuf byteBuf = (ByteBuf) input.getArguments().get(i); 38 | int readableBytes = byteBuf.readableBytes(); 39 | byte[] bytes = new byte[readableBytes]; 40 | byteBuf.readBytes(bytes); 41 | arguments[i] = serializer.decode(bytes, inputParameterTypes.get(i)); 42 | byteBuf.release(); 43 | } 44 | output.setArguments(arguments); 45 | return output; 46 | } 47 | Class[] parameterTypes = input.getMethod().getParameterTypes(); 48 | int len = parameterTypes.length; 49 | Object[] arguments = new Object[len]; 50 | for (int i = 0; i < len; i++) { 51 | ByteBuf byteBuf = (ByteBuf) input.getArguments().get(i); 52 | int readableBytes = byteBuf.readableBytes(); 53 | byte[] bytes = new byte[readableBytes]; 54 | byteBuf.readBytes(bytes); 55 | arguments[i] = serializer.decode(bytes, parameterTypes[i]); 56 | byteBuf.release(); 57 | } 58 | output.setArguments(arguments); 59 | return output; 60 | } catch (Exception e) { 61 | throw new ArgumentConvertException(e); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/server/HostClassMethodInfo.java: -------------------------------------------------------------------------------- 1 | package club.throwable.server; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author throwable 7 | * @version v1.0 8 | * @description 9 | * @since 2020/1/12 11:59 10 | */ 11 | @Data 12 | public class HostClassMethodInfo { 13 | 14 | private Class hostClass; 15 | private Class hostUserClass; 16 | private Object hostTarget; 17 | } 18 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/server/MethodArgumentConverter.java: -------------------------------------------------------------------------------- 1 | package club.throwable.server; 2 | 3 | /** 4 | * @author throwable 5 | * @version v1.0 6 | * @description 7 | * @since 2020/1/14 23:21 8 | */ 9 | public interface MethodArgumentConverter { 10 | 11 | ArgumentConvertOutput convert(ArgumentConvertInput input); 12 | } 13 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/server/MethodMatchInput.java: -------------------------------------------------------------------------------- 1 | package club.throwable.server; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author throwable 10 | * @version v1.0 11 | * @description 12 | * @since 2020/1/12 11:39 13 | */ 14 | @EqualsAndHashCode 15 | @Data 16 | public class MethodMatchInput { 17 | 18 | private String interfaceName; 19 | 20 | private String methodName; 21 | 22 | private List methodArgumentSignatures; 23 | 24 | private int methodArgumentArraySize; 25 | } 26 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/server/MethodMatchOutput.java: -------------------------------------------------------------------------------- 1 | package club.throwable.server; 2 | 3 | import lombok.Data; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.List; 7 | 8 | /** 9 | * @author throwable 10 | * @version v1.0 11 | * @description 12 | * @since 2020/1/12 11:47 13 | */ 14 | @Data 15 | public class MethodMatchOutput { 16 | 17 | /** 18 | * 目标方法实例 19 | */ 20 | private Method targetMethod; 21 | 22 | /** 23 | * 目标实现类 - 这个有可能是被Cglib增强过的类型,是宿主类的子类,如果没有被Cglib增强过,那么它就是宿主类 24 | */ 25 | private Class targetClass; 26 | 27 | /** 28 | * 宿主类 29 | */ 30 | private Class targetUserClass; 31 | 32 | /** 33 | * 宿主类Bean实例 34 | */ 35 | private Object target; 36 | 37 | /** 38 | * 方法参数类型列表 39 | */ 40 | private List> parameterTypes; 41 | } 42 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/server/MethodMatcher.java: -------------------------------------------------------------------------------- 1 | package club.throwable.server; 2 | 3 | /** 4 | * @author throwable 5 | * @version v1.0 6 | * @description 方法选择器 7 | * @since 2020/1/12 11:37 8 | */ 9 | public interface MethodMatcher { 10 | 11 | /** 12 | * 查找一个匹配度最高的方法信息 13 | * 14 | * @param input input 15 | * @return output 16 | */ 17 | MethodMatchOutput selectOneBestMatchMethod(MethodMatchInput input); 18 | } 19 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/server/ServerApplication.java: -------------------------------------------------------------------------------- 1 | package club.throwable.server; 2 | 3 | import club.throwable.protocol.RequestMessagePacketDecoder; 4 | import club.throwable.protocol.ResponseMessagePacketEncoder; 5 | import club.throwable.protocol.serialize.FastJsonSerializer; 6 | import io.netty.bootstrap.ServerBootstrap; 7 | import io.netty.channel.ChannelFuture; 8 | import io.netty.channel.ChannelInitializer; 9 | import io.netty.channel.EventLoopGroup; 10 | import io.netty.channel.nio.NioEventLoopGroup; 11 | import io.netty.channel.socket.SocketChannel; 12 | import io.netty.channel.socket.nio.NioServerSocketChannel; 13 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 14 | import io.netty.handler.codec.LengthFieldPrepender; 15 | import io.netty.handler.logging.LogLevel; 16 | import io.netty.handler.logging.LoggingHandler; 17 | import lombok.extern.slf4j.Slf4j; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.beans.factory.annotation.Value; 20 | import org.springframework.boot.CommandLineRunner; 21 | import org.springframework.boot.SpringApplication; 22 | import org.springframework.boot.autoconfigure.SpringBootApplication; 23 | import org.springframework.context.ApplicationContext; 24 | 25 | /** 26 | * @author throwable 27 | * @version v1.0 28 | * @description 29 | * @since 2019/9/29 12:04 30 | */ 31 | @SpringBootApplication(scanBasePackages = "club.throwable.server") 32 | @Slf4j 33 | public class ServerApplication implements CommandLineRunner { 34 | 35 | @Value("${netty.port:9092}") 36 | private Integer nettyPort; 37 | 38 | @Autowired 39 | private ApplicationContext applicationContext; 40 | 41 | public static void main(String[] args) throws Exception { 42 | SpringApplication.run(ServerApplication.class, args); 43 | } 44 | 45 | @Override 46 | public void run(String... args) throws Exception { 47 | int port = nettyPort; 48 | ServerBootstrap bootstrap = new ServerBootstrap(); 49 | EventLoopGroup bossGroup = new NioEventLoopGroup(); 50 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 51 | try { 52 | bootstrap.group(bossGroup, workerGroup) 53 | .channel(NioServerSocketChannel.class) 54 | .childHandler(new ChannelInitializer() { 55 | 56 | @Override 57 | protected void initChannel(SocketChannel ch) throws Exception { 58 | ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4)); 59 | ch.pipeline().addLast(new LengthFieldPrepender(4)); 60 | ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG)); 61 | ch.pipeline().addLast(new RequestMessagePacketDecoder()); 62 | ch.pipeline().addLast(new ResponseMessagePacketEncoder(FastJsonSerializer.X)); 63 | ch.pipeline().addLast(applicationContext.getBean(ServerHandler.class)); 64 | } 65 | }); 66 | ChannelFuture future = bootstrap.bind(port).sync(); 67 | log.info("启动NettyServer[{}]成功...", port); 68 | future.channel().closeFuture().sync(); 69 | } finally { 70 | workerGroup.shutdownGracefully(); 71 | bossGroup.shutdownGracefully(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/server/ServerHandler.java: -------------------------------------------------------------------------------- 1 | package club.throwable.server; 2 | 3 | 4 | import club.throwable.protocol.MessageType; 5 | import club.throwable.protocol.RequestMessagePacket; 6 | import club.throwable.protocol.ResponseMessagePacket; 7 | import com.alibaba.fastjson.JSON; 8 | import com.google.common.collect.Lists; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import io.netty.channel.SimpleChannelInboundHandler; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.beans.factory.config.ConfigurableBeanFactory; 14 | import org.springframework.context.annotation.Scope; 15 | import org.springframework.stereotype.Component; 16 | import org.springframework.util.ReflectionUtils; 17 | 18 | import java.lang.reflect.Method; 19 | import java.util.Optional; 20 | 21 | /** 22 | * @author throwable 23 | * @version v1.0 24 | * @description 25 | * @since 2020/1/3 20:26 26 | */ 27 | @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE) 28 | @Component 29 | @Slf4j 30 | public class ServerHandler extends SimpleChannelInboundHandler { 31 | 32 | @Autowired 33 | private MethodMatcher methodMatcher; 34 | 35 | @Autowired 36 | private MethodArgumentConverter methodArgumentConverter; 37 | 38 | @Override 39 | protected void channelRead0(ChannelHandlerContext ctx, RequestMessagePacket packet) throws Exception { 40 | log.info("服务端接收到:{}", packet); 41 | MethodMatchInput input = new MethodMatchInput(); 42 | input.setInterfaceName(packet.getInterfaceName()); 43 | input.setMethodArgumentSignatures(Optional.ofNullable(packet.getMethodArgumentSignatures()) 44 | .map(Lists::newArrayList).orElse(Lists.newArrayList())); 45 | input.setMethodName(packet.getMethodName()); 46 | Object[] methodArguments = packet.getMethodArguments(); 47 | input.setMethodArgumentArraySize(null != methodArguments ? methodArguments.length : 0); 48 | MethodMatchOutput output = methodMatcher.selectOneBestMatchMethod(input); 49 | log.info("查找目标实现方法成功,目标类:{},宿主类:{},宿主方法:{}", 50 | output.getTargetClass().getCanonicalName(), 51 | output.getTargetUserClass().getCanonicalName(), 52 | output.getTargetMethod().getName() 53 | ); 54 | Method targetMethod = output.getTargetMethod(); 55 | ArgumentConvertInput convertInput = new ArgumentConvertInput(); 56 | convertInput.setArguments(input.getMethodArgumentArraySize() > 0 ? Lists.newArrayList(methodArguments) : Lists.newArrayList()); 57 | convertInput.setMethod(output.getTargetMethod()); 58 | convertInput.setParameterTypes(output.getParameterTypes()); 59 | ArgumentConvertOutput convertOutput = methodArgumentConverter.convert(convertInput); 60 | ReflectionUtils.makeAccessible(targetMethod); 61 | // 反射调用 62 | Object result = targetMethod.invoke(output.getTarget(), convertOutput.getArguments()); 63 | ResponseMessagePacket response = applyResponseMessagePacket(packet); 64 | response.setPayload(result); 65 | log.info("服务端输出:{}", JSON.toJSONString(response)); 66 | ctx.writeAndFlush(response); 67 | } 68 | 69 | public static ResponseMessagePacket applyResponseMessagePacket(RequestMessagePacket packet) { 70 | ResponseMessagePacket response = new ResponseMessagePacket(); 71 | response.setMagicNumber(packet.getMagicNumber()); 72 | response.setVersion(packet.getVersion()); 73 | response.setSerialNumber(packet.getSerialNumber()); 74 | response.setAttachments(packet.getAttachments()); 75 | response.setMessageType(MessageType.RESPONSE); 76 | response.setErrorCode(200L); 77 | response.setMessage("Success"); 78 | return response; 79 | } 80 | 81 | @Override 82 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 83 | log.error("服务端处理异常", cause); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/server/SpringMethodMatcher.java: -------------------------------------------------------------------------------- 1 | package club.throwable.server; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.beans.factory.BeanFactory; 5 | import org.springframework.beans.factory.BeanFactoryAware; 6 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 7 | import org.springframework.lang.NonNull; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.util.ClassUtils; 10 | 11 | /** 12 | * @author throwable 13 | * @version v1.0 14 | * @description 15 | * @since 2020/1/12 12:15 16 | */ 17 | @Component 18 | public class SpringMethodMatcher extends BaseMethodMatcher implements BeanFactoryAware { 19 | 20 | private DefaultListableBeanFactory beanFactory; 21 | 22 | @Override 23 | public void setBeanFactory(@NonNull BeanFactory beanFactory) throws BeansException { 24 | this.beanFactory = (DefaultListableBeanFactory) beanFactory; 25 | } 26 | 27 | @Override 28 | HostClassMethodInfo findHostClassMethodInfo(Class interfaceClass) { 29 | HostClassMethodInfo info = new HostClassMethodInfo(); 30 | // 从容器中通过接口类型获取对应的实现,实现必须只有一个 31 | Object bean = beanFactory.getBean(interfaceClass); 32 | info.setHostTarget(bean); 33 | info.setHostClass(bean.getClass()); 34 | info.setHostUserClass(ClassUtils.getUserClass(bean.getClass())); 35 | return info; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/server/contract/DefaultHelloService.java: -------------------------------------------------------------------------------- 1 | package club.throwable.server.contract; 2 | 3 | import club.throwable.contract.HelloService; 4 | import club.throwable.contract.dto.SayHelloRequestDTO; 5 | import club.throwable.contract.dto.SayHelloResponseDTO; 6 | import org.springframework.stereotype.Service; 7 | 8 | /** 9 | * @author throwable 10 | * @version v1.0 11 | * @description 12 | * @since 2020/1/14 23:55 13 | */ 14 | @Service 15 | public class DefaultHelloService implements HelloService { 16 | 17 | @Override 18 | public String sayHello(String name) { 19 | return String.format("%s say hello!", name); 20 | } 21 | 22 | @Override 23 | public SayHelloResponseDTO sayHello(SayHelloRequestDTO request) { 24 | String content = String.format("%s say hello!", request.getName()); 25 | SayHelloResponseDTO response = new SayHelloResponseDTO(); 26 | response.setContent(content); 27 | return response; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/utils/ByteBufferUtils.java: -------------------------------------------------------------------------------- 1 | package club.throwable.utils; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufUtil; 5 | 6 | /** 7 | * @author throwable 8 | * @version v1.0 9 | * @description 10 | * @since 2020/1/3 9:26 11 | */ 12 | public enum ByteBufferUtils { 13 | 14 | // 单例 15 | X; 16 | 17 | public void encodeUtf8CharSequence(ByteBuf byteBuf, CharSequence charSequence) { 18 | int writerIndex = byteBuf.writerIndex(); 19 | byteBuf.writeInt(0); 20 | int length = ByteBufUtil.writeUtf8(byteBuf, charSequence); 21 | byteBuf.setInt(writerIndex, length); 22 | } 23 | 24 | public byte[] readBytes(ByteBuf byteBuf) { 25 | int len = byteBuf.readableBytes(); 26 | byte[] bytes = new byte[len]; 27 | byteBuf.readBytes(bytes); 28 | byteBuf.release(); 29 | return bytes; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/java/club/throwable/utils/SerialNumberUtils.java: -------------------------------------------------------------------------------- 1 | package club.throwable.utils; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * @author throwable 7 | * @version v1.0 8 | * @description 9 | * @since 2020/1/3 9:27 10 | */ 11 | public enum SerialNumberUtils { 12 | 13 | // 单例; 14 | X; 15 | 16 | public String generateSerialNumber() { 17 | return UUID.randomUUID().toString().replace("-", ""); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |      4 | 5 | 6 | %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /ch0-custom-rpc-protocol/src/test/java/club/throwable/client/NettyThreadSyncTest.java: -------------------------------------------------------------------------------- 1 | package club.throwable.client; 2 | 3 | import com.google.common.collect.Maps; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.junit.BeforeClass; 9 | import org.junit.Test; 10 | 11 | import java.util.Map; 12 | import java.util.UUID; 13 | import java.util.concurrent.*; 14 | 15 | /** 16 | * @author throwable 17 | * @version v1.0 18 | * @description 19 | * @since 2020/1/18 12:44 20 | */ 21 | @Slf4j 22 | public class NettyThreadSyncTest { 23 | 24 | @ToString 25 | private static class ResponseFuture { 26 | 27 | private final long beginTimestamp = System.currentTimeMillis(); 28 | @Getter 29 | private final long timeoutMilliseconds; 30 | @Getter 31 | private final String requestId; 32 | @Setter 33 | @Getter 34 | private volatile boolean sendRequestSucceed = false; 35 | @Setter 36 | @Getter 37 | private volatile Throwable cause; 38 | @Getter 39 | private volatile Object response; 40 | 41 | private final CountDownLatch latch = new CountDownLatch(1); 42 | 43 | public ResponseFuture(String requestId, long timeoutMilliseconds) { 44 | this.requestId = requestId; 45 | this.timeoutMilliseconds = timeoutMilliseconds; 46 | } 47 | 48 | public boolean timeout() { 49 | return System.currentTimeMillis() - beginTimestamp > timeoutMilliseconds; 50 | } 51 | 52 | public Object waitResponse(final long timeoutMilliseconds) throws InterruptedException { 53 | latch.await(timeoutMilliseconds, TimeUnit.MILLISECONDS); 54 | return response; 55 | } 56 | 57 | public void putResponse(Object response) throws InterruptedException { 58 | this.response = response; 59 | latch.countDown(); 60 | } 61 | } 62 | 63 | static ExecutorService REQUEST_THREAD; 64 | static ExecutorService NETTY_IO_THREAD; 65 | static Callable REQUEST_TASK; 66 | static Runnable RESPONSE_TASK; 67 | 68 | static String processBusiness(String name) { 69 | return String.format("%s say hello!", name); 70 | } 71 | 72 | private static final Map RESPONSE_FUTURE_TABLE = Maps.newConcurrentMap(); 73 | 74 | @BeforeClass 75 | public static void beforeClass() throws Exception { 76 | String requestId = UUID.randomUUID().toString(); 77 | String requestContent = "throwable"; 78 | REQUEST_TASK = () -> { 79 | try { 80 | // 3秒没有得到响应认为超时 81 | ResponseFuture responseFuture = new ResponseFuture(requestId, 3000); 82 | RESPONSE_FUTURE_TABLE.put(requestId, responseFuture); 83 | // 这里忽略发送请求的操作,只打印日志和模拟耗时1秒 84 | Thread.sleep(1000); 85 | log.info("发送请求成功,请求ID:{},请求内容:{}", requestId, requestContent); 86 | // 更新标记属性 87 | responseFuture.setSendRequestSucceed(true); 88 | // 剩余2秒等待时间 - 这里只是粗略计算 89 | return responseFuture.waitResponse(3000 - 1000); 90 | } catch (Exception e) { 91 | log.info("发送请求失败,请求ID:{},请求内容:{}", requestId, requestContent); 92 | throw new RuntimeException(e); 93 | } 94 | }; 95 | RESPONSE_TASK = () -> { 96 | String responseContent = processBusiness(requestContent); 97 | try { 98 | ResponseFuture responseFuture = RESPONSE_FUTURE_TABLE.get(requestId); 99 | if (null != responseFuture) { 100 | log.warn("处理响应成功,请求ID:{},响应内容:{}", requestId, responseContent); 101 | responseFuture.putResponse(responseContent); 102 | } else { 103 | log.warn("请求ID[{}]对应的ResponseFuture不存在,忽略处理", requestId); 104 | } 105 | } catch (Exception e) { 106 | log.info("处理响应失败,请求ID:{},响应内容:{}", requestId, responseContent); 107 | throw new RuntimeException(e); 108 | } 109 | }; 110 | REQUEST_THREAD = Executors.newSingleThreadExecutor(runnable -> { 111 | Thread thread = new Thread(runnable, "REQUEST_THREAD"); 112 | thread.setDaemon(true); 113 | return thread; 114 | }); 115 | NETTY_IO_THREAD = Executors.newSingleThreadExecutor(runnable -> { 116 | Thread thread = new Thread(runnable, "NETTY_IO_THREAD"); 117 | thread.setDaemon(true); 118 | return thread; 119 | }); 120 | } 121 | 122 | @Test 123 | public void testProcessSync() throws Exception { 124 | log.info("异步提交请求处理任务......"); 125 | Future future = REQUEST_THREAD.submit(REQUEST_TASK); 126 | // 模拟请求耗时 127 | Thread.sleep(1500); 128 | log.info("异步提交响应处理任务......"); 129 | NETTY_IO_THREAD.execute(RESPONSE_TASK); 130 | // 这里可以设置超时 131 | log.info("同步获取请求结果:{}", future.get()); 132 | Thread.sleep(Long.MAX_VALUE); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | club.throwable 6 | netty-tutorials 7 | 1.0-SNAPSHOT 8 | 9 | ch0-custom-rpc-protocol 10 | 11 | pom 12 | netty-tutorials 13 | http://www.throwable.club 14 | 15 | UTF-8 16 | 1.8 17 | 1.8 18 | 4.1.44.Final 19 | 2.2.2.RELEASE 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-dependencies 26 | ${spring.boot.version} 27 | pom 28 | import 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter 36 | 37 | 38 | io.netty 39 | netty-all 40 | ${netty.version} 41 | 42 | 43 | org.projectlombok 44 | lombok 45 | 1.18.10 46 | provided 47 | 48 | 49 | com.alibaba 50 | fastjson 51 | 1.2.61 52 | 53 | 54 | com.google.guava 55 | guava 56 | 28.1-jre 57 | 58 | 59 | junit 60 | junit 61 | 4.13 62 | test 63 | 64 | 65 | 66 | netty-tutorials 67 | 68 | 69 | --------------------------------------------------------------------------------