Java11实战:模块化的 Netty RPC 服务项目

作者:枫叶lhz
链接:https://www.jianshu.com/p/19b81178d8c1
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

参考  [Java模块系统]:https://www.oracle.com/corporate/features/understanding-java-9-modules.html

从 Java9 就引入了模块化的新语法了。如果我们想在项目中使用 Java9 及以上的版本的话,模块化是无法忽视的。它不像 Java8 的 lambda 表达式,我们可以不使用 lambda 这个新特性,仍然用老旧的 API 进行替代。但是模块化就不同。我们甚至发现,新的版本里, rt.jar 已经不存在了。JDK 的结构和基础库率先模块化了。

模块化 API

模块化的背景和概念本篇文章就不概述了,读者可以查看官方文档或者通过网上不错的博客进行了解。我们主要讲解一下 module-info.java 里的一些配置含义。

自定义的 helo.service 模块,包含了不少指令,我们分别进行介绍。

  1. module helo.service {
  2. exports com.maple.netty.handler;
  3. exports com.maple.hello.service to hello.client;
  4. requires slf4j.api;
  5. requires io.netty.all;
  6. requires gson;
  7. requires hello.api;
  8. requires hello.common;
  9. uses com.google.gson.Gson;
  10. opens com.maple.hello;
  11. }

exports 和 exports to 指令

exports 指令用于指定一个模块中哪些包对外是可访问的。而 exports…to 指令则用来限定哪些模块可以访问当前模块导出的类,通过逗号分隔可以指定多个模块访问当前模块导出的类。这种方式称为限定导出(qualified export)。

require 指令

require 指令声明一个模块所依赖的其他模块,在 Java9 之后,我们除了引入 Jar 包依赖后,如果想要使用它们,还需要在 module-info.java中使用 require 声明使用。

uses 指令

uses 指令用于指定一个模块所使用的服务,使模块成为服务的消费者。其实就是指定一个模块下的某一个具体的类。
下面是 requiresuses 的 主要区别:

  1. module hello.client {
  2. requires gson;
  3. uses com.google.gson.Gson;
  4. }

provides…with 指令

该指令用于说明模块提供了某个服务的实现,因此模块也称为服务提供者。provides 后面跟接口名或抽象类名,与 uses 指令后的名称一致,with 后面跟实现类该接口或抽象类的类名。
例如java.base 模块里的其中一个声明,with后面为前者的一个实现类。

  1. provides java.nio.file.spi.FileSystemProvider with jdk.internal.jrtfs.JrtFileSystemProvider;

open, opens, opens…to 指令

Java9 之前,Java 类的属性即使定义为 private 也是能够被访问到的,我们可以通过反射等手段达到目的。
Java9 模块化推出的一个重要目的就是强封装,可以完全的将不想暴露的类和属性给保护起来。
默认情况下,除非显式地导出或声明某个类为 public 类型,那么模块中的类对外部都是不可见的,模块化要求我们对外部模块以最小范围进行暴露。
open 等相关的指令目的就是来限制哪些类或者属性能够通过反射技术访问。

opens 指令

opens package 指定模块某个包下的所有 public 类都能被其他模块通过反射进行访问。

opens ... to 指令

opens package to modules 指定某些特定的模块才能去对当前 package 进行反射访问。

open module 指令

外部模块对该模块下所有的类在运行时都可以进行反射操作。

  1. open module hello.common {
  2. requires io.netty.all;
  3. exports com.maple.hello.common;
  4. exports com.maple.hello.common.netty;
  5. }

实战:基于 Netty 的模块化 RPC 服务例子

实战部分将会项目将会基于最新的 Java11 版本,使用 Maven 进行项目管理。Netty 作为网络通讯框架。实现一个简单的RPC功能,hello-client 将会通过 netty客户端发送请求,netty服务端接收请求并返回处理结果。采用 GsonProtobuf 对请求对象进行序列化/反序列化处理。网络通讯采用 Reacter 模式,客户端异步非阻塞模式请求。Netty层进行了 TCP 粘包拆包的处理,心跳检测和channel空闲检测,channel断线重连等功能。

本文实现的 RPC 例子,涵盖了目前现有的基于Netty的RPC网络通讯部分所有的技术点。

Maven 环境准备

编译插件

我们需要对 maven-compiler-plugin 插件进行升级,以支持最新的 Java11 的字节码版本(55),升级版本为最新版3.8.0

启用 Java 11 语言支持

  1. <properties>
  2. <maven.compiler.release>11</maven.compiler.release>
  3. <maven.compiler.source>11</maven.compiler.source>
  4. <maven.compiler.target>11</maven.compiler.target>
  5. </properties>

编译插件配置

  1. <plugin>
  2. <groupId>org.apache.maven.plugins</groupId>
  3. <artifactId>maven-compiler-plugin</artifactId>
  4. <version>${maven-compiler-plugin.version}</version>
  5. <!--
  6. Fix breaking change introduced by JDK-8178012: Finish removal of -Xmodule
  7. Reference: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8178012
  8. -->
  9. <executions>
  10. <execution>
  11. <id>default-testCompile</id>
  12. <phase>test-compile</phase>
  13. <goals>
  14. <goal>testCompile</goal>
  15. </goals>
  16. <configuration>
  17. <skip>true</skip>
  18. </configuration>
  19. </execution>
  20. </executions>
  21. <configuration>
  22. <showWarnings>true</showWarnings>
  23. <showDeprecation>true</showDeprecation>
  24. </configuration>
  25. </plugin>

工具链插件

这个插件主要是对Java11和Java8做兼容性选择。由于现在Java版本更新很快,但是大部分项目还是基于 Java8 甚至更低版本。不适宜更改项目所有的环境变量,并将其指向JDK11的主目录。使用 maven-toolchains-plugin 使您能够轻松地使用各种环境。

创建 $HOME/.m2/toolchains.xml

  1. <toolchains>
  2. <toolchain>
  3. <type>jdk</type>
  4. <provides>
  5. <version>11</version>
  6. <vendor>oracle</vendor>
  7. </provides>
  8. <configuration>
  9. <!-- Change path to JDK9 -->
  10. <jdkHome>/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home</jdkHome>
  11. </configuration>
  12. </toolchain>
  13. <toolchain>
  14. <type>jdk</type>
  15. <provides>
  16. <version>8</version>
  17. <vendor>oracle</vendor>
  18. </provides>
  19. <configuration>
  20. <jdkHome>/Library/Java/JavaVirtualMachines/jdk-8.jdk/Contents/Home</jdkHome>
  21. </configuration>
  22. </toolchain>
  23. </toolchains>

注意:将配置文件中 <jdkHome> 更改为实际的 JDK 安装 HOME

项目主POM 文件 添加 工具链插件

  1. <plugin>
  2. <groupId>org.apache.maven.plugins</groupId>
  3. <artifactId>maven-toolchains-plugin</artifactId>
  4. <version>1.1</version>
  5. <configuration>
  6. <toolchains>
  7. <jdk>
  8. <version>11</version>
  9. <vendor>oracle</vendor>
  10. </jdk>
  11. </toolchains>
  12. </configuration>
  13. <executions>
  14. <execution>
  15. <goals>
  16. <goal>toolchain</goal>
  17. </goals>
  18. </execution>
  19. </executions>
  20. </plugin>

构建项目 Java11-netty-demo

构建整个项目结构如下

  1. ├── hello-api
  2. ├── src
  3. ├── main
  4. ├── java
  5. ├── com
  6. └── maple
  7. └── hello
  8. ├── HelloRequest.java
  9. └── HelloResponse.java
  10. └── module-info.java
  11. └── resources

  12. ├── hello-client
  13. ├── src
  14. ├── main
  15. ├── java
  16. ├── com
  17. └── maple
  18. └── hello
  19. └── client
  20. ├── AppClient.java
  21. ├── Main.java
  22. ├── netty
  23. ├── NettyClient.java
  24. └── handler
  25. ├── RpcClientHandler.java
  26. ├── RpcClientMsgDecoder.java
  27. └── RpcClientMsgEncoder.java
  28. └── service
  29. └── HelloClient.java
  30. └── module-info.java
  31. └── resources
  32. └── logback.xml
  33. └── test
  34. └── java

  35. ├── hello-common
  36. ├── src
  37. ├── main
  38. ├── java
  39. ├── com
  40. └── maple
  41. └── hello
  42. └── common
  43. ├── Constants.java
  44. ├── DumpUtil.java
  45. ├── RpcException.java
  46. └── netty
  47. └── RpcFrameDecoder.java
  48. └── module-info.java
  49. └── resources
  50. └──
  51. ├── hello-service
  52. ├── hello-service.iml
  53. ├── pom.xml
  54. ├── src
  55. ├── main
  56. ├── java
  57. ├── com
  58. └── maple
  59. ├── AppServer.java
  60. ├── hello
  61. └── service
  62. ├── HelloService.java
  63. └── Person.java
  64. └── netty
  65. ├── NettySimpleServer.java
  66. └── handler
  67. ├── RpcLogHandler.java
  68. ├── RpcMsgDecoder.java
  69. ├── RpcMsgEncoder.java
  70. └── ServerHandler.java
  71. └── module-info.java
  72. └── resources
  73. └── logback.xml

从上面的 Tree 图,我们可以看到项目分为四大模块:

  • hello-api       API模块,主要为 clientservice 共同依赖
  • hello-common    公用的类模块
  • hello-client     rpc客户端模块
  • hello-service     rpc服务端模块

每个模块src根目录下都有一个 module-info.java 文件用来定义模块

hello-api
  1. module hello.api {
  2. exports com.maple.hello;
  3. }
hello-common
  1. module hello.common {
  2. requires io.netty.all;
  3. exports com.maple.hello.common;
  4. exports com.maple.hello.common.netty;
  5. }
hello-client
  1. module hello.client {
  2. requires hello.api;
  3. requires io.netty.all;
  4. requires slf4j.api;
  5. requires hello.common;
  6. requires gson;
  7. uses com.google.gson.Gson;
  8. }
hello-service
  1. module helo.service {
  2. requires slf4j.api;
  3. requires io.netty.all;
  4. requires gson;
  5. requires hello.api;
  6. requires hello.common;
  7. }

以上 module-info.java 主要定义模块的依赖关系和导出关系。

运行项目

通过上面几步操作之后,我们便可以启动项目运行。

首先我们启动服务端,即 AppServer,暴露 8000 端口

  1. public class AppServer {
  2. public static void main(String[] args) {
  3. NettySimpleServer simpleServer = new NettySimpleServer(8000);
  4. simpleServer.start();
  5. }
  6. }

然后我们启动客户端程序Main,该程序简单模拟控制台输入作为请求

  1. public static void main(String[] args) throws IOException {
  2. AppClient client = new AppClient(SERVER_URL, SERVER_PORT);
  3. logger.info("------ 欢迎进入JDK11的世界: 请输入你的昵称 --------- \n");
  4. BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
  5. String name = in.readLine();
  6. while (true) {
  7. try {
  8. logger.info("------ 请输入任何你想输入的内容: --------- \n");
  9. Scanner scanner = new Scanner(System.in);
  10. if (scanner.hasNext()) {
  11. String msg = scanner.next();
  12. int seq = SEQ_ID_ATOMIC.incrementAndGet();
  13. CompletableFuture<HelloResponse> response = client.sendMessage(new HelloRequest(seq, name, msg));
  14. response.whenComplete((result, ex) -> {
  15. if (ex != null) {
  16. logger.info(ex.getMessage(), ex);
  17. }
  18. logger.info("seq为 {} 的请求,服务端返回结果为:{}", seq, result.toString());
  19. });
  20. } else {
  21. int seq = SEQ_ID_ATOMIC.incrementAndGet();
  22. CompletableFuture<HelloResponse> response = client.sendMessage(new HelloRequest(seq, name, "异常准备关闭"));
  23. response.whenComplete((result, ex) -> {
  24. if (ex != null) {
  25. logger.info(ex.getMessage(), ex);
  26. }
  27. logger.info("seq为 {} 的请求,服务端返回结果为:{}", seq, result.toString());
  28. });
  29. }
  30. } catch (Exception e) {
  31. logger.error(e.getMessage(), e);
  32. }
  33. }
  34. }

我们输入内容后,马上获取到返回结果:

 

服务端处理日志:

  1. 09-28 00:51:57 271 netty-server-work-group-3-3 INFO - remote server /127.0.0.1:56546, channelRead, msg:com.maple.hello.HelloRequest@6400575a
  2. 09-28 00:51:57 271 netty-server-work-group-3-3 INFO - com.maple.hello.service.HelloService: 收到消息 seqId:2, request: com.maple.hello.HelloRequest@6400575a

一个简单但功能齐全的基于 Netty 的例子演示成功。如果读者对本例子感兴趣,可以访问如下地址获取本项目源码:

Java11-Netty-Demo: https://github.com/leihuazhe/Java11-Netty-Demo

迁移 Java11 注意事项

1. JavaEE 模块被移除

Java11 移除了 JavaEE 模块,所以很多诸如 javax JAXB 等已经被移除。
如果旧版本的项目有依赖 Javaee的组件,需要单独加入 javaee-api

  1. <dependency>
  2. <groupId>javax</groupId>
  3. <artifactId>javaee-api</artifactId>
  4. <version>8.0</version>
  5. </dependency>

2.使用最新版本 Netty

由于在Java11中,JDK部分底层的类例如 Unsafe 等被移到了 jdk.internal 模块。以及Java11 对模块内类的保护,导致暴力反射访问失效等问题。因此在最新的 Netty 版本中对这些做了优化了改变。

总结

本文首先从模块化 API 及其功能说起,然后以实践为目的介绍如何搭建基于Java11 的工程。通过一个基于 Netty 的案例工程来具体学习和深入模块化的使用。

新版本的 Java11 对比 Java8 的改动个人感觉是有一点大的。如果我们要从一个以 Java8 甚至更低版本的项目迁移过来时,首先需要改变的就是一些依赖库的变更,其次就是 模块化的转变,因此整个迁移还是需要考虑一定的兼容性。万幸 Java11Java 官方准备长期维护的一个版本,未来迁移到这个版本也是大势所趋,后续博主将继续跟进 Java11 的更多新特性。


本文例子源码: Java11-Netty-Demo: https://github.com/leihuazhe/Java11-Netty-Demo

================== End

 

Java11实战:模块化的 Netty RPC 服务项目的更多相关文章

  1. 【.NET Core项目实战-统一认证平台】第十六章 网关篇-Ocelot集成RPC服务

    [.NET Core项目实战-统一认证平台]开篇及目录索引 一.什么是RPC RPC是"远程调用(Remote Procedure Call)"的一个名称的缩写,并不是任何规范化的 ...

  2. 通过 Netty、ZooKeeper 手撸一个 RPC 服务

    说明 项目链接 微服务框架都包括什么? 如何实现 RPC 远程调用? 开源 RPC 框架 限定语言 跨语言 RPC 框架 本地 Docker 搭建 ZooKeeper 下载镜像 启动容器 查看容器日志 ...

  3. 基于netty轻量的高性能分布式RPC服务框架forest<上篇>

    工作几年,用过不不少RPC框架,也算是读过一些RPC源码.之前也撸过几次RPC框架,但是不断的被自己否定,最近终于又撸了一个,希望能够不断迭代出自己喜欢的样子. 顺便也记录一下撸RPC的过程,一来作为 ...

  4. 基于netty轻量的高性能分布式RPC服务框架forest<下篇>

    基于netty轻量的高性能分布式RPC服务框架forest<上篇> 文章已经简单介绍了forest的快速入门,本文旨在介绍forest用户指南. 基本介绍 Forest是一套基于java开 ...

  5. Thrift 个人实战--Thrift RPC服务框架日志的优化

    前言: Thrift作为Facebook开源的RPC框架, 通过IDL中间语言, 并借助代码生成引擎生成各种主流语言的rpc框架服务端/客户端代码. 不过Thrift的实现, 简单使用离实际生产环境还 ...

  6. CODING DevOps 微服务项目实战系列第一课,明天等你

    CODING DevOps 微服务项目实战系列第一课<DevOps 微服务项目实战:DevOps 初体验>将由 CODING DevOps 开发工程师 王宽老师 向大家介绍 DevOps ...

  7. CODING DevOps 微服务项目实战系列第二课来啦!

    近年来,工程项目的结构越来越复杂,需要接入合适的持续集成流水线形式,才能满足更多变的需求,那么如何优雅地使用 CI 能力提升生产效率呢?CODING DevOps 微服务项目实战系列第二课 <D ...

  8. CODING DevOps 微服务项目实战系列最后一课,周四开讲!

    随着软件工程越来越复杂化,如何在 Kubernetes 集群进行灰度发布成为了生产部署的"必修课",而如何实现安全可控.自动化的灰度发布也成为了持续部署重点关注的问题.CODING ...

  9. SpringCloud微服务项目实战 - API网关Gateway详解实现

    前面讲过zuul的网关实现,那为什么今天又要讲Spring Cloud Gateway呢?原因很简单.就是Spring Cloud已经放弃Netflix Zuul了.现在Spring Cloud中引用 ...

随机推荐

  1. LVDS时序分析

    LVDS时序分析 2012年05月07日 11:48:08 Walle 阅读数:3355 标签: 平台工作   最近在调试基于telechip平台的LVDS驱动,一开始对该平台的LVDS时序不是很了解 ...

  2. C#的delegate简单练习

    delegate中文的意思为委托. 在很久之前,Insus.NET有写过一篇<用一个简单的例子来演绎事件委托>http://www.cnblogs.com/insus/p/3732075. ...

  3. Luogu P2002 消息扩散&&P1262 间谍网络

    怕自己太久没写Tarjan了就会把这种神仙算法忘掉. 其实这种类型的图论题的套路还是比较简单且显然的. P2002 消息扩散 很显然的题目,因为在一个环(其实就是强连通分量)中的城市都只需要让其中一个 ...

  4. POJ Remmarguts' Date

    题目链接-> 题解: 次短路模板. 代码: #include<cstdio> #include<iostream> using namespace std; #defin ...

  5. .Net版本依赖之坑引发的搜查

    前言 今天上午,一个客户反馈XX消息没有推送到第三方链接.于是我查看了推送日志列表,并没有今天的.接着登录服务器查询文件日志,看到了记录.我们的代码步骤是消息先推送到消息队列,消费消息队列时,记录文件 ...

  6. Python基础(中)

    前言 print(" _ooOoo_ ") print(" o8888888o ") print(" 88 . 88 ") print(&q ...

  7. 破解Zip加密文件常用的几种方法

    前言 在互联网的浪潮中,大家也许碰到过这种情况: 从网络上下载了一个zip文件,最后却发现它是用密码保护的,或者自己用密码加密了一个很重要zip文件,但是一段时间后忘记了密码,无法打开.这个时候,我们 ...

  8. webpack笔记

    打包 img src src 必须以 点(.) 开始,才能被打包. 如: ./img/logo.png ../img/logo.png 使用 process a.js window.Base_Url ...

  9. MySQL针对Swap分区的运维注意点

    Linux有很多很好的内存.IO调度机制,但是并不会适用于所有场景.对于运维人员来说,Linux比较让人头疼的一个地方是:它不会因为MySQL很重要就避免将分配给MySQL的地址空间映射到swap上. ...

  10. Linux期末总结

    Linux内核学习总结 1.计算机是如何工作的? 存储程序计算机工作模型 X86汇编基础 汇编一个简单的C程序分析其汇编指令执行过程 2.操作系统是如何工作的? 三个法宝——存储程序计算机.函数调用堆 ...