# lagou_3_1_rpc **Repository Path**: java-quickstart/lagou_3_1_rpc ## Basic Information - **Project Name**: lagou_3_1_rpc - **Description**: 阶段三模块一作业 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-05-26 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 李志勇的作业 #### 阶段三模块一作业 编程题 在基于 Netty 的自定义 RPC 的案例基础上,进行改造 案例版本 序列化方式为 String, 并根据自定义的 providerName 做为通信协议,服务端判断是否以”UserService“开头 完成的案例 要求完成改造版本 序列化协议修改为 JSON,使用 fastjson 作为 JSON 框架,并根据 RpcRequest 实体作为通信协议,服务端需根据客户端传递过来的 RpcRequest 对象通过反射,动态代理等技术,最终能够执行目标方法,返回字符串"success" 要点提示 1. 客户端代理的 invoke 方法中需封装 RpcRequest 对象,将其当做参数进行传递 2. 服务端的 UserServiceImpl 类上添加@Service 注解,在启动项目时,添加到容器中 3. 服务端要添加@SpringBootApplication 注解,main 方法中添加 SpringApplication.run(ServerBootstrap.class, args);,进行启动扫描(注意项目启动类位置:扫描路径) 4. 服务端在收到参数,可以借助反射及动态代理(如需用到 ApplicationContext 对象,可以借助实现 ApplicationContextAware 接口获取),来调用 UserServiceImpl 方法,最终向客户端返回”success“即可 5. 既然传递的是 RpcRequest 对象了,那么客户端的编码器与服务端的解码器需重新设置 作业思路 1. 封装 rpc 通信对象 RpcRequest(包含请求 id、接口类名、方法名、类型参数列表、参数列表) 2. 自定义序列化方式,使用 fastjson 实现序列化与反序列化 3. 自定义编码/解码实现,RpcEncoder 继承 MessageToByteEncoder 重写 encode 方法序列化 RpcRequest 对象,RpcDecoder 继承 ByteToMessageDecoder 重写 decode 方法反序列化 RpcRequest 对象 (需要注意的是 RpcEncoder.encode 方法往 ByteBuf 中写入了请求内容的长度,解码的时候需要先获取内容长度,然后再读取请求内容反序列化) RpcEncoder ```java @Override protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf byteBuf) throws Exception { if (clazz != null && clazz.isInstance(msg)) { byte[] bytes = serializer.serialize(msg); byteBuf.writeInt(bytes.length); byteBuf.writeBytes(bytes); } } ``` RpcDecoder ```java @Override protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List out) throws IOException { int byteLength = byteBuf.readInt(); byte[] bytes = new byte[byteLength]; byteBuf.readBytes(bytes); out.add(serializer.deserialize(clazz, bytes)); } ``` 4. 客户端创建代理对象时需要将请求信息封装成 RpcRequest 对象,设置到 UserClientHandler 中 ```java public Object createProxy(final Class serviceClass) { return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{serviceClass}, (proxy, method, args) -> { if (userClientHandler == null) { initClient(); } RpcRequest rpcRequest = new RpcRequest(); rpcRequest.setRequestId(UUID.randomUUID().toString()); rpcRequest.setClassName(serviceClass.getName()); rpcRequest.setMethodName(method.getName()); rpcRequest.setParameterTypes(method.getParameterTypes()); rpcRequest.setParameters(args); userClientHandler.setRpcRequest(rpcRequest); // 去服务端请求数据 return executor.submit(userClientHandler).get(); }); } ``` 5. 客户端初始化时需要使用自定义的编码方式 RpcEncoder,解码方式使用 StringDecoder(客户端提交的是 RpcRequest 对象,服务端返回的是 String) ```java bootstrap.group(group) .channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .handler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new RpcEncoder(RpcRequest.class, new JSONSerializer())); pipeline.addLast(new StringDecoder()); pipeline.addLast(userClientHandler); } }); ``` 6. 服务端初始化时使用自定义的解码方式 RpcDecoder,编码方式使用 StringEncoder(服务端接收到的是 RpcRequest 对象,服务端返回的是 String) ```java serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new StringEncoder()); pipeline.addLast(new RpcDecoder(RpcRequest.class, new JSONSerializer())); pipeline.addLast(userServerHandler); } }); ``` 7. 服务端 UserServerHandler 根据 RpcRequest.className 从 ApplicationContext 中获取实例对象,根据 Request.methodName 获取对应的方法,通过反射 invoke 调用 ```java private ApplicationContext applicationContext; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof RpcRequest) { Class clazz = Class.forName(((RpcRequest) msg).getClassName()); Object obj = applicationContext.getBean(clazz); Method method = clazz.getMethod(((RpcRequest) msg).getMethodName(), ((RpcRequest) msg).getParameterTypes()); Object result = method.invoke(obj, ((RpcRequest) msg).getParameters()); ctx.writeAndFlush(result); } } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } ``` 8. 服务端通过 SpringBoot 启动,实现 CommandLineRunner 接口重写 run 方法 ```java @SpringBootApplication(scanBasePackages = "com.lagou") public class ServerBootstrap implements CommandLineRunner { @Autowired private UserServerHandler userServerHandler; public static void main(String[] args) throws InterruptedException { SpringApplication.run(ServerBootstrap.class, args); } @Override public void run(String... args) throws Exception { NioEventLoopGroup bossGroup = new NioEventLoopGroup(); NioEventLoopGroup workerGroup = new NioEventLoopGroup(); io.netty.bootstrap.ServerBootstrap serverBootstrap = new io.netty.bootstrap.ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new StringEncoder()); pipeline.addLast(new RpcDecoder(RpcRequest.class, new JSONSerializer())); pipeline.addLast(userServerHandler); } }); serverBootstrap.bind("127.0.0.1", 8990).sync(); } } ```