從 Java9 就引入了模塊化的新語(yǔ)法了。如果我們想在項(xiàng)目中使用 Java9 及以上的版本的話,模塊化是無(wú)法忽視的。它不像 Java8 的 lambda 表達(dá)式逞怨,我們可以不使用 lambda 這個(gè)新特性,仍然用老舊的 API 進(jìn)行替代福澡。但是模塊化就不同叠赦。我們甚至發(fā)現(xiàn),新的版本里革砸,
rt.jar
已經(jīng)不存在了除秀。JDK
的結(jié)構(gòu)和基礎(chǔ)庫(kù)率先模塊化了。
模塊化 API
模塊化的背景和概念本篇文章就不概述了算利,讀者可以查看官方文檔或者通過(guò)網(wǎng)上不錯(cuò)的博客進(jìn)行了解册踩。我們主要講解一下
module-info.java
里的一些配置含義。
自定義的 helo.service
模塊效拭,包含了不少指令棍好,我們分別進(jìn)行介紹。
module helo.service {
exports com.maple.netty.handler;
exports com.maple.hello.service to hello.client;
requires slf4j.api;
requires io.netty.all;
requires gson;
requires hello.api;
requires hello.common;
uses com.google.gson.Gson;
opens com.maple.hello;
}
exports 和 exports to 指令
exports
指令用于指定一個(gè)模塊中哪些包對(duì)外是可訪問(wèn)的允耿。而 exports…to
指令則用來(lái)限定哪些模塊可以訪問(wèn)當(dāng)前模塊導(dǎo)出的類,通過(guò)逗號(hào)分隔可以指定多個(gè)模塊訪問(wèn)當(dāng)前模塊導(dǎo)出的類扒怖。這種方式稱為限定導(dǎo)出(qualified export
)较锡。
require 指令
require
指令聲明一個(gè)模塊所依賴的其他模塊,在 Java9
之后盗痒,我們除了引入 Jar
包依賴后蚂蕴,如果想要使用它們,還需要在 module-info.java
中使用 require
聲明使用俯邓。
uses
指令
uses
指令用于指定一個(gè)模塊所使用的服務(wù)骡楼,使模塊成為服務(wù)的消費(fèi)者。其實(shí)就是指定一個(gè)模塊下的某一個(gè)具體的類稽鞭。
下面是 requires
和 uses
的 主要區(qū)別:
module hello.client {
requires gson;
uses com.google.gson.Gson;
}
provides…with 指令
該指令用于說(shuō)明模塊提供了某個(gè)服務(wù)的實(shí)現(xiàn)鸟整,因此模塊也稱為服務(wù)提供者。provides
后面跟接口名或抽象類名朦蕴,與 uses
指令后的名稱一致篮条,with
后面跟實(shí)現(xiàn)類該接口或抽象類的類名。
例如java.base
模塊里的其中一個(gè)聲明吩抓,with后面為前者的一個(gè)實(shí)現(xiàn)類涉茧。
provides java.nio.file.spi.FileSystemProvider with jdk.internal.jrtfs.JrtFileSystemProvider;
open, opens, opens…to 指令
Java9
之前俭缓,Java
類的屬性即使定義為 private
也是能夠被訪問(wèn)到的迎膜,我們可以通過(guò)反射等手段達(dá)到目的。
Java9
模塊化推出的一個(gè)重要目的就是強(qiáng)封裝拳昌,可以完全的將不想暴露的類和屬性給保護(hù)起來(lái)。
默認(rèn)情況下钳垮,除非顯式地導(dǎo)出或聲明某個(gè)類為 public
類型惑淳,那么模塊中的類對(duì)外部都是不可見(jiàn)的,模塊化要求我們對(duì)外部模塊以最小范圍進(jìn)行暴露扔枫。
open
等相關(guān)的指令目的就是來(lái)限制哪些類或者屬性能夠通過(guò)反射技術(shù)訪問(wèn)汛聚。
opens
指令
opens package 指定模塊某個(gè)包下的所有 public
類都能被其他模塊通過(guò)反射進(jìn)行訪問(wèn)。
opens ... to
指令
opens package to modules
指定某些特定的模塊才能去對(duì)當(dāng)前 package
進(jìn)行反射訪問(wèn)短荐。
open module
指令
外部模塊對(duì)該模塊下所有的類在運(yùn)行時(shí)都可以進(jìn)行反射操作倚舀。
open module hello.common {
requires io.netty.all;
exports com.maple.hello.common;
exports com.maple.hello.common.netty;
}
實(shí)戰(zhàn):基于 Netty 的模塊化 RPC 服務(wù)例子
實(shí)戰(zhàn)部分將會(huì)項(xiàng)目將會(huì)基于最新的
Java11
版本,使用Maven
進(jìn)行項(xiàng)目管理忍宋。Netty
作為網(wǎng)絡(luò)通訊框架痕貌。實(shí)現(xiàn)一個(gè)簡(jiǎn)單的RPC功能,hello-client 將會(huì)通過(guò) netty客戶端發(fā)送請(qǐng)求糠排,netty服務(wù)端接收請(qǐng)求并返回處理結(jié)果舵稠。采用Gson
和Protobuf
對(duì)請(qǐng)求對(duì)象進(jìn)行序列化/反序列化處理。網(wǎng)絡(luò)通訊采用 Reacter 模式入宦,客戶端異步非阻塞模式請(qǐng)求哺徊。Netty層進(jìn)行了TCP
粘包拆包的處理,心跳檢測(cè)和channel空閑檢測(cè)乾闰,channel斷線重連等功能落追。
本文實(shí)現(xiàn)的 RPC
例子,涵蓋了目前現(xiàn)有的基于Netty的RPC網(wǎng)絡(luò)通訊部分所有的技術(shù)點(diǎn)涯肩。
Maven 環(huán)境準(zhǔn)備
編譯插件
我們需要對(duì) maven-compiler-plugin
插件進(jìn)行升級(jí)轿钠,以支持最新的 Java11
的字節(jié)碼版本(55),升級(jí)版本為最新版3.8.0
病苗。
啟用 Java 11
語(yǔ)言支持
<properties>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
編譯插件配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<!--
Fix breaking change introduced by JDK-8178012: Finish removal of -Xmodule
Reference: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8178012
-->
<executions>
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
<configuration>
<skip>true</skip>
</configuration>
</execution>
</executions>
<configuration>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
工具鏈插件
這個(gè)插件主要是對(duì)Java11和Java8做兼容性選擇疗垛。由于現(xiàn)在Java版本更新很快,但是大部分項(xiàng)目還是基于 Java8 甚至更低版本硫朦。不適宜更改項(xiàng)目所有的環(huán)境變量贷腕,并將其指向JDK11的主目錄。使用 maven-toolchains-plugin
使您能夠輕松地使用各種環(huán)境阵幸。
創(chuàng)建 $HOME/.m2/toolchains.xml
<toolchains>
<toolchain>
<type>jdk</type>
<provides>
<version>11</version>
<vendor>oracle</vendor>
</provides>
<configuration>
<!-- Change path to JDK9 -->
<jdkHome>/Library/Java/JavaVirtualMachines/jdk-11.jdk/Contents/Home</jdkHome>
</configuration>
</toolchain>
<toolchain>
<type>jdk</type>
<provides>
<version>8</version>
<vendor>oracle</vendor>
</provides>
<configuration>
<jdkHome>/Library/Java/JavaVirtualMachines/jdk-8.jdk/Contents/Home</jdkHome>
</configuration>
</toolchain>
</toolchains>
注意:將配置文件中 <jdkHome>
更改為實(shí)際的 JDK
安裝 HOME
花履。
項(xiàng)目主POM 文件 添加 工具鏈插件
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-toolchains-plugin</artifactId>
<version>1.1</version>
<configuration>
<toolchains>
<jdk>
<version>11</version>
<vendor>oracle</vendor>
</jdk>
</toolchains>
</configuration>
<executions>
<execution>
<goals>
<goal>toolchain</goal>
</goals>
</execution>
</executions>
</plugin>
構(gòu)建項(xiàng)目 Java11-netty-demo
構(gòu)建整個(gè)項(xiàng)目結(jié)構(gòu)如下
├── hello-api
│ ├── src
│ │ ├── main
│ │ │ ├── java
│ │ │ │ ├── com
│ │ │ │ │ └── maple
│ │ │ │ │ └── hello
│ │ │ │ │ ├── HelloRequest.java
│ │ │ │ │ └── HelloResponse.java
│ │ │ │ └── module-info.java
│ │ │ └── resources
│
├── hello-client
│ ├── src
│ │ ├── main
│ │ │ ├── java
│ │ │ │ ├── com
│ │ │ │ │ └── maple
│ │ │ │ │ └── hello
│ │ │ │ │ └── client
│ │ │ │ │ ├── AppClient.java
│ │ │ │ │ ├── Main.java
│ │ │ │ │ ├── netty
│ │ │ │ │ │ ├── NettyClient.java
│ │ │ │ │ │ └── handler
│ │ │ │ │ │ ├── RpcClientHandler.java
│ │ │ │ │ │ ├── RpcClientMsgDecoder.java
│ │ │ │ │ │ └── RpcClientMsgEncoder.java
│ │ │ │ │ └── service
│ │ │ │ │ └── HelloClient.java
│ │ │ │ └── module-info.java
│ │ │ └── resources
│ │ │ └── logback.xml
│ │ └── test
│ │ └── java
│
├── hello-common
│ ├── src
│ │ ├── main
│ │ │ ├── java
│ │ │ │ ├── com
│ │ │ │ │ └── maple
│ │ │ │ │ └── hello
│ │ │ │ │ └── common
│ │ │ │ │ ├── Constants.java
│ │ │ │ │ ├── DumpUtil.java
│ │ │ │ │ ├── RpcException.java
│ │ │ │ │ └── netty
│ │ │ │ │ └── RpcFrameDecoder.java
│ │ │ │ └── module-info.java
│ │ │ └── resources
│ └──
├── hello-service
│ ├── hello-service.iml
│ ├── pom.xml
│ ├── src
│ │ ├── main
│ │ │ ├── java
│ │ │ │ ├── com
│ │ │ │ │ └── maple
│ │ │ │ │ ├── AppServer.java
│ │ │ │ │ ├── hello
│ │ │ │ │ │ └── service
│ │ │ │ │ │ ├── HelloService.java
│ │ │ │ │ │ └── Person.java
│ │ │ │ │ └── netty
│ │ │ │ │ ├── NettySimpleServer.java
│ │ │ │ │ └── handler
│ │ │ │ │ ├── RpcLogHandler.java
│ │ │ │ │ ├── RpcMsgDecoder.java
│ │ │ │ │ ├── RpcMsgEncoder.java
│ │ │ │ │ └── ServerHandler.java
│ │ │ │ └── module-info.java
│ │ │ └── resources
│ │ │ └── logback.xml
從上面的 Tree
圖,我們可以看到項(xiàng)目分為四大模塊:
- hello-api ?? ?? API模塊挚赊,主要為
client
和service
共同依賴 - hello-common ?? 公用的類模塊
- hello-client ??? rpc客戶端模塊
- hello-service ??? rpc服務(wù)端模塊
每個(gè)模塊src根目錄下都有一個(gè) module-info.java
文件用來(lái)定義模塊
hello-api
module hello.api {
exports com.maple.hello;
}
hello-common
module hello.common {
requires io.netty.all;
exports com.maple.hello.common;
exports com.maple.hello.common.netty;
}
hello-client
module hello.client {
requires hello.api;
requires io.netty.all;
requires slf4j.api;
requires hello.common;
requires gson;
uses com.google.gson.Gson;
}
hello-service
module helo.service {
requires slf4j.api;
requires io.netty.all;
requires gson;
requires hello.api;
requires hello.common;
}
以上 module-info.java
主要定義模塊的依賴關(guān)系和導(dǎo)出關(guān)系诡壁。
運(yùn)行項(xiàng)目
通過(guò)上面幾步操作之后,我們便可以啟動(dòng)項(xiàng)目運(yùn)行荠割。
首先我們啟動(dòng)服務(wù)端妹卿,即 AppServer
旺矾,暴露 8000
端口
public class AppServer {
public static void main(String[] args) {
NettySimpleServer simpleServer = new NettySimpleServer(8000);
simpleServer.start();
}
}
然后我們啟動(dòng)客戶端程序Main
,該程序簡(jiǎn)單模擬控制臺(tái)輸入作為請(qǐng)求
public static void main(String[] args) throws IOException {
AppClient client = new AppClient(SERVER_URL, SERVER_PORT);
logger.info("------ 歡迎進(jìn)入JDK11的世界: 請(qǐng)輸入你的昵稱 --------- \n");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String name = in.readLine();
while (true) {
try {
logger.info("------ 請(qǐng)輸入任何你想輸入的內(nèi)容: --------- \n");
Scanner scanner = new Scanner(System.in);
if (scanner.hasNext()) {
String msg = scanner.next();
int seq = SEQ_ID_ATOMIC.incrementAndGet();
CompletableFuture<HelloResponse> response = client.sendMessage(new HelloRequest(seq, name, msg));
response.whenComplete((result, ex) -> {
if (ex != null) {
logger.info(ex.getMessage(), ex);
}
logger.info("seq為 {} 的請(qǐng)求,服務(wù)端返回結(jié)果為:{}", seq, result.toString());
});
} else {
int seq = SEQ_ID_ATOMIC.incrementAndGet();
CompletableFuture<HelloResponse> response = client.sendMessage(new HelloRequest(seq, name, "異常準(zhǔn)備關(guān)閉"));
response.whenComplete((result, ex) -> {
if (ex != null) {
logger.info(ex.getMessage(), ex);
}
logger.info("seq為 {} 的請(qǐng)求,服務(wù)端返回結(jié)果為:{}", seq, result.toString());
});
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
我們輸入內(nèi)容后夺克,馬上獲取到返回結(jié)果:
服務(wù)端處理日志:
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
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
一個(gè)簡(jiǎn)單但功能齊全的基于 Netty
的例子演示成功箕宙。如果讀者對(duì)本例子感興趣,可以訪問(wèn)如下地址獲取本項(xiàng)目源碼:
Java11-Netty-Demo: https://github.com/leihuazhe/Java11-Netty-Demo
遷移 Java11 注意事項(xiàng)
1. JavaEE 模塊被移除
Java11
移除了 JavaEE
模塊,所以很多諸如 javax JAXB 等已經(jīng)被移除铺纽。
如果舊版本的項(xiàng)目有依賴 Javaee的組件柬帕,需要單獨(dú)加入 javaee-api
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0</version>
</dependency>
2.使用最新版本 Netty
由于在Java11中,JDK部分底層的類例如 Unsafe
等被移到了 jdk.internal
模塊狡门。以及Java11 對(duì)模塊內(nèi)類的保護(hù)陷寝,導(dǎo)致暴力反射訪問(wèn)失效等問(wèn)題。因此在最新的 Netty 版本中對(duì)這些做了優(yōu)化了改變其馏。
總結(jié)
本文首先從模塊化 API
及其功能說(shuō)起凤跑,然后以實(shí)踐為目的介紹如何搭建基于Java11 的工程。通過(guò)一個(gè)基于 Netty
的案例工程來(lái)具體學(xué)習(xí)和深入模塊化的使用叛复。
新版本的 Java11
對(duì)比 Java8
的改動(dòng)個(gè)人感覺(jué)是有一點(diǎn)大的仔引。如果我們要從一個(gè)以 Java8
甚至更低版本的項(xiàng)目遷移過(guò)來(lái)時(shí),首先需要改變的就是一些依賴庫(kù)的變更褐奥,其次就是 模塊化的轉(zhuǎn)變咖耘,因此整個(gè)遷移還是需要考慮一定的兼容性。萬(wàn)幸 Java11
是 Java
官方準(zhǔn)備長(zhǎng)期維護(hù)的一個(gè)版本撬码,未來(lái)遷移到這個(gè)版本也是大勢(shì)所趨鲤看,后續(xù)博主將繼續(xù)跟進(jìn) Java11
的更多新特性。
本文例子源碼: Java11-Netty-Demo: https://github.com/leihuazhe/Java11-Netty-Demo
推薦
最后推薦一下本人微信公眾號(hào)耍群,歡迎大家關(guān)注。