?? Java11實(shí)戰(zhàn):模塊化的 Netty RPC 服務(wù)項(xiàng)目

從 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è)具體的類稽鞭。
下面是 requiresuses 的 主要區(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é)果舵稠。采用 GsonProtobuf 對(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模塊挚赊,主要為 clientservice 共同依賴
  • 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é)果:


client.png

服務(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)幸 Java11Java 官方準(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)注。

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末找筝,一起剝皮案震驚了整個(gè)濱河市蹈垢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌袖裕,老刑警劉巖曹抬,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異急鳄,居然都是意外死亡谤民,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)疾宏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)张足,“玉大人,你說(shuō)我怎么就攤上這事坎藐∥梗” “怎么了哼绑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)碉咆。 經(jīng)常有香客問(wèn)我抖韩,道長(zhǎng),這世上最難降的妖魔是什么疫铜? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任茂浮,我火速辦了婚禮,結(jié)果婚禮上壳咕,老公的妹妹穿的比我還像新娘席揽。我一直安慰自己,他們只是感情好囱井,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布驹尼。 她就那樣靜靜地躺著,像睡著了一般庞呕。 火紅的嫁衣襯著肌膚如雪新翎。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,985評(píng)論 1 291
  • 那天住练,我揣著相機(jī)與錄音地啰,去河邊找鬼。 笑死讲逛,一個(gè)胖子當(dāng)著我的面吹牛亏吝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盏混,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蔚鸥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了许赃?” 一聲冷哼從身側(cè)響起止喷,我...
    開(kāi)封第一講書(shū)人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎混聊,沒(méi)想到半個(gè)月后弹谁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡句喜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年预愤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咳胃。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡植康,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出展懈,到底是詐尸還是另有隱情向图,我是刑警寧澤泳秀,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站榄攀,受9級(jí)特大地震影響嗜傅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜檩赢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一吕嘀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贞瞒,春花似錦偶房、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至乒融,卻和暖如春掰盘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赞季。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工愧捕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人申钩。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓次绘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親撒遣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子邮偎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)义黎,斷路器钢猛,智...
    卡卡羅2017閱讀 134,637評(píng)論 18 139
  • Netty的簡(jiǎn)單介紹 Netty 是一個(gè) NIO client-server(客戶端服務(wù)器)框架,使用 Netty...
    AI喬治閱讀 8,396評(píng)論 1 101
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,848評(píng)論 25 707
  • 優(yōu)雅的方式: 效果如下:
    LeeLeCoder閱讀 677評(píng)論 0 0
  • 易效能第一課是關(guān)于早起轩缤。易效能說(shuō),一天之計(jì)在于晨贩绕,一晨之計(jì)在于起火的。時(shí)間管理的第一習(xí)慣是就是早起。曾國(guó)藩認(rèn)為淑倾,早起...
    落梅亦如雪閱讀 400評(píng)論 0 0