├── .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