從懵逼到恍然大悟的Java中RMI的使用
你知道的越多,不知道的就越多,業(yè)余的像一棵小草!
你來(lái),我們一起精進(jìn)!你不來(lái),我和你的競(jìng)爭(zhēng)對(duì)手一起精進(jìn)!
編輯:業(yè)余草
blog.csdn.net/lmy86263
推薦:https://www.xttblog.com/?p=5329
今天上海大決戰(zhàn),我們小區(qū)早上 4 點(diǎn) 30 分都有人起來(lái)排隊(duì)做核酸了。最近醫(yī)護(hù)人員都很辛苦,向她們致敬!
文末有點(diǎn)贊送書活動(dòng),一共 3 本實(shí)體書,歡迎參與!

本文講的是Java中的RMI,而不是通用意義上的RMI。
一、Java RMI簡(jiǎn)介
Java RMI用于不同虛擬機(jī)之間的通信,這些虛擬機(jī)可以在不同的主機(jī)上、也可以在同一個(gè)主機(jī)上;一個(gè)虛擬機(jī)中的對(duì)象調(diào)用另一個(gè)虛擬上中的對(duì)象的方法,只不過(guò)是允許被遠(yuǎn)程調(diào)用的對(duì)象要通過(guò)一些標(biāo)志加以標(biāo)識(shí)。這樣做的特點(diǎn)如下:
優(yōu)點(diǎn):避免重復(fù)造輪子; 缺點(diǎn):調(diào)用過(guò)程很慢,而且該過(guò)程是不可靠的,容易發(fā)生不可預(yù)料的錯(cuò)誤,比如網(wǎng)絡(luò)錯(cuò)誤等;
在RMI中的核心是遠(yuǎn)程對(duì)象(remote object),除了對(duì)象本身所在的虛擬機(jī),其他虛擬機(jī)也可以調(diào)用此對(duì)象的方法,而且這些虛擬機(jī)可以不在同一個(gè)主機(jī)上。每個(gè)遠(yuǎn)程對(duì)象都要實(shí)現(xiàn)一個(gè)或者多個(gè)遠(yuǎn)程接口來(lái)標(biāo)識(shí)自己,聲明了可以被外部系統(tǒng)或者應(yīng)用調(diào)用的方法(當(dāng)然也有一些方法是不想讓人訪問(wèn)的)。
1.1 RMI的通信模型
從方法調(diào)用角度來(lái)看,RMI要解決的問(wèn)題,是讓客戶端對(duì)遠(yuǎn)程方法的調(diào)用可以相當(dāng)于對(duì)本地方法的調(diào)用而屏蔽其中關(guān)于遠(yuǎn)程通信的內(nèi)容,即使在遠(yuǎn)程上,也和在本地上是一樣的。
從客戶端-服務(wù)器模型來(lái)看,客戶端程序直接調(diào)用服務(wù)端,兩者之間是通過(guò)JRMP(Java Remote Method Protocolhttps://en.wikipedia.org/wiki/Java_Remote_Method_Protocol)協(xié)議通信,這個(gè)協(xié)議類似于HTTP協(xié)議,規(guī)定了客戶端和服務(wù)端通信要滿足的規(guī)范。
但是實(shí)際上,客戶端只與代表遠(yuǎn)程主機(jī)中對(duì)象的Stub對(duì)象進(jìn)行通信,絲毫不知道Server的存在。客戶端只是調(diào)用Stub對(duì)象中的本地方法,Stub對(duì)象是一個(gè)本地對(duì)象,它實(shí)現(xiàn)了遠(yuǎn)程對(duì)象向外暴露的接口,也就是說(shuō)它的方法和遠(yuǎn)程對(duì)象暴露的方法的簽名是相同的。客戶端認(rèn)為它是調(diào)用遠(yuǎn)程對(duì)象的方法,實(shí)際上是調(diào)用Stub對(duì)象中的方法。「可以理解為Stub對(duì)象是遠(yuǎn)程對(duì)象在本地的一個(gè)代理」,當(dāng)客戶端調(diào)用方法的時(shí)候,Stub對(duì)象會(huì)將調(diào)用通過(guò)網(wǎng)絡(luò)傳遞給遠(yuǎn)程對(duì)象。
在java 1.2之前,與Stub對(duì)象直接對(duì)話的是Skeleton對(duì)象,在Stub對(duì)象將調(diào)用傳遞給Skeleton的過(guò)程中,其實(shí)這個(gè)過(guò)程是通過(guò)JRMP協(xié)議實(shí)現(xiàn)轉(zhuǎn)化的,通過(guò)這個(gè)協(xié)議將調(diào)用從一個(gè)虛擬機(jī)轉(zhuǎn)到另一個(gè)虛擬機(jī)。在Java 1.2之后,與Stub對(duì)象直接對(duì)話的是Server程序,不再是Skeleton對(duì)象了。
所以從邏輯上來(lái)看,數(shù)據(jù)是在Client和Server之間橫向流動(dòng)的,但是實(shí)際上是從Client到Stub,然后從Skeleton到Server這樣縱向流動(dòng)的。

1.2 重要的問(wèn)題
1.2.1 數(shù)據(jù)的傳遞問(wèn)題
我們都知道在Java程序中引用類型(不包括基本類型)的參數(shù)傳遞是按引用傳遞的,對(duì)于在同一個(gè)虛擬機(jī)中的傳遞時(shí)是沒(méi)有問(wèn)題的,因?yàn)榈膮?shù)的引用對(duì)應(yīng)的是同一個(gè)內(nèi)存空間,但是對(duì)于分布式系統(tǒng)中,由于對(duì)象不再存在于同一個(gè)內(nèi)存空間,虛擬機(jī)A的對(duì)象引用對(duì)于虛擬機(jī)B沒(méi)有任何意義,那么怎么解決這個(gè)問(wèn)題呢?
第一種:將引用傳遞更改為值傳遞,也就是將對(duì)象序列化為字節(jié),然后使用該字節(jié)的副本在客戶端和服務(wù)器之間傳遞,而且一個(gè)虛擬機(jī)中對(duì)該值的修改不會(huì)影響到其他主機(jī)中的數(shù)據(jù);但是對(duì)象的序列化也有一個(gè)問(wèn)題,就是對(duì)象的嵌套引用就會(huì)造成序列化的嵌套,這必然會(huì)導(dǎo)致數(shù)據(jù)量的激增,因此我們需要有選擇進(jìn)行序列化,在
Java中一個(gè)對(duì)象如果能夠被序列化,需要滿足下面兩個(gè)條件之一:是 Java的基本類型;實(shí)現(xiàn) java.io.Serializable接口(String類即實(shí)現(xiàn)了該接口);對(duì)于容器類,如果其中的對(duì)象是可以序列化的,那么該容器也是可以序列化的; 可序列化的子類也是可以序列化的; 第二種:仍然使用引用傳遞,每當(dāng)遠(yuǎn)程主機(jī)調(diào)用本地主機(jī)方法時(shí),該調(diào)用還要通過(guò)本地主機(jī)查詢?cè)撘脤?duì)應(yīng)的對(duì)象,在任何一臺(tái)機(jī)器上的改變都會(huì)影響原始主機(jī)上的數(shù)據(jù),因?yàn)檫@個(gè)對(duì)象是共享的;
RMI中的參數(shù)傳遞和結(jié)果返回可以使用的三種機(jī)制(取決于數(shù)據(jù)類型):
簡(jiǎn)單類型:按值傳遞,直接傳遞數(shù)據(jù)拷貝; 遠(yuǎn)程對(duì)象引用(實(shí)現(xiàn)了 Remote接口):以遠(yuǎn)程對(duì)象的引用傳遞;遠(yuǎn)程對(duì)象引用(未實(shí)現(xiàn) Remote接口):按值傳遞,通過(guò)序列化對(duì)象傳遞副本,本身不允許序列化的對(duì)象不允許傳遞給遠(yuǎn)程方法;
1.2.2 遠(yuǎn)程對(duì)象的發(fā)現(xiàn)問(wèn)題
在調(diào)用遠(yuǎn)程對(duì)象的方法之前需要一個(gè)遠(yuǎn)程對(duì)象的引用,如何獲得這個(gè)遠(yuǎn)程對(duì)象的引用在RMI中是一個(gè)關(guān)鍵的問(wèn)題,如果將遠(yuǎn)程對(duì)象的發(fā)現(xiàn)類比于IP地址的發(fā)現(xiàn)可能比較好理解一些。
在我們?nèi)粘J褂镁W(wǎng)絡(luò)時(shí),基本上都是通過(guò)域名來(lái)定位一個(gè)網(wǎng)站,但是實(shí)際上網(wǎng)絡(luò)是通過(guò)IP地址來(lái)定位網(wǎng)站的,因此其中就需要一個(gè)映射的過(guò)程,域名系統(tǒng)(DNS)就是為了這個(gè)目的出現(xiàn)的,在域名系統(tǒng)中通過(guò)域名來(lái)查找對(duì)應(yīng)的IP地址來(lái)訪問(wèn)對(duì)應(yīng)的服務(wù)器。那么對(duì)應(yīng)的,IP地址在這里就相當(dāng)于遠(yuǎn)程對(duì)象的引用,而DNS則相當(dāng)于一個(gè)「注冊(cè)表」(Registry)。而域名在RMI中就相當(dāng)于遠(yuǎn)程對(duì)象的標(biāo)識(shí)符,客戶端通過(guò)提供遠(yuǎn)程對(duì)象的標(biāo)識(shí)符訪問(wèn)注冊(cè)表,來(lái)得到遠(yuǎn)程對(duì)象的引用。這個(gè)標(biāo)識(shí)符是類似URL地址格式的,它要滿足的規(guī)范如下:
該名稱是 URL形式的,類似于http的URL,schema是rmi;格式類似于 rmi://host:port/name,host指明注冊(cè)表運(yùn)行的注解,port表明接收調(diào)用的端口,name是一個(gè)標(biāo)識(shí)該對(duì)象的簡(jiǎn)單名稱。主機(jī)和端口都是可選的,如果省略主機(jī),則默認(rèn)運(yùn)行在本地;如果端口也省略,則默認(rèn)端口是「1099」;
二、編程實(shí)現(xiàn)
2.1 基本內(nèi)容
實(shí)現(xiàn)RMI所需的API幾乎都在:
java.rmi:提供「客戶端」需要的類、接口和異常;java.rmi.server:提供「服務(wù)端」需要的類、接口和異常;java.rmi.registry:提供注冊(cè)表的創(chuàng)建以及查找和命名遠(yuǎn)程對(duì)象的類、接口和異常;
其實(shí)在RMI中的客戶端和服務(wù)端并沒(méi)有絕對(duì)的界限,與Web應(yīng)用中的客戶端和服務(wù)器還是有區(qū)別的。這兩者其實(shí)是平等的,客戶端可以為服務(wù)端提供遠(yuǎn)程調(diào)用的方法,這時(shí)候,原來(lái)的客戶端就是服務(wù)器端。
2.2 基本實(shí)現(xiàn)之一(注冊(cè)表單獨(dú)運(yùn)行)
2.2.1 構(gòu)建服務(wù)器端
什么是遠(yuǎn)程對(duì)象?首先從名稱上來(lái)看,遠(yuǎn)程對(duì)象是存在于服務(wù)端以供客戶端調(diào)用。那么什么對(duì)象可以被客戶端進(jìn)行遠(yuǎn)程調(diào)用?這個(gè)問(wèn)題從編程的角度來(lái)看,「實(shí)現(xiàn)了java.rmi.Remote接口的類或者繼承了java.rmi.Remote接口的所有接口都是遠(yuǎn)程對(duì)象」。這些繼承或者實(shí)現(xiàn)了該接口的類或者接口中定義了客戶端可以訪問(wèn)的方法。這個(gè)遠(yuǎn)程對(duì)象中可能有很多個(gè)方法,但是「只有在遠(yuǎn)程接口中聲明的方法才能從遠(yuǎn)程調(diào)用」,其他的公共方法只能在本地虛擬機(jī)中使用。
實(shí)現(xiàn)過(guò)程中的注意事項(xiàng):
子接口的每個(gè)方法都「必須」聲明拋出 java.rmi.RemoteException異常,該異常是使用RMI時(shí)可能拋出的大多數(shù)異常的父類。子接口的實(shí)現(xiàn)類應(yīng)該直接或者間接繼承 java.rmi.server.UnicastRemoteObject類,該類提供了很多支持RMI的方法,具體來(lái)說(shuō),這些方法可以通過(guò)JRMP協(xié)議導(dǎo)出一個(gè)遠(yuǎn)程對(duì)象的引用,并通過(guò)動(dòng)態(tài)代理構(gòu)建一個(gè)可以和遠(yuǎn)程對(duì)象交互的Stub對(duì)象。具體的實(shí)現(xiàn)看如下的例子。
首先遠(yuǎn)程接口如下:
public interface UserHandler extends Remote {
String getUserName(int id) throws RemoteException;
int getUserCount() throws RemoteException;
User getUserByName(String name) throws RemoteException;
}
遠(yuǎn)程接口的實(shí)現(xiàn)類如下:
public class UserHandlerImpl extends UnicastRemoteObject implements UserHandler {
// 該構(gòu)造期必須存在,因?yàn)榧^承了UnicastRemoteObject類,其構(gòu)造器要拋出RemoteException
public UserHandlerImpl() throws RemoteException {
super();
}
@Override
public String getUserName(int id) throws RemoteException {
return "lmy86263";
}
@Override
public int getUserCount() throws RemoteException{
return 1;
}
@Override
public User getUserByName(String name) throws RemoteException{
return new User("lmy86263", 1);
}
}
為了測(cè)試在使用RMI的序列化的問(wèn)題,這里特別設(shè)置了一個(gè)引用類型User:
public class User implements Serializable {
// 該字段必須存在
private static final long serialVersionUID = 42L;
// setter和getter可以沒(méi)有
String name;
int id;
public User(String name, int id) {
this.name = name;
this.id = id;
}
}
在Java 1.4及 以前的版本中需要手動(dòng)建立Stub對(duì)象,通過(guò)運(yùn)行rmic命令來(lái)生成遠(yuǎn)程對(duì)象實(shí)現(xiàn)類的Stub對(duì)象,但是在Java 1.5之后可以通過(guò)動(dòng)態(tài)代理http://blog.csdn.net/lmy86263/article/details/50764643來(lái)完成,不再需要這個(gè)過(guò)程了。
運(yùn)行該遠(yuǎn)程對(duì)象的服務(wù)器代碼如下:
UserHandler userHandler = null;
try {
userHandler = new UserHandlerImpl();
Naming.rebind("user", userHandler);
System.out.println(" rmi server is ready ...");
} catch (Exception e) {
e.printStackTrace();
}
這里面的核心代碼為Naming.rebind("user", userHandler) ,通過(guò)一個(gè)名稱映射到該遠(yuǎn)程對(duì)象的引用,客戶端通過(guò)該名稱獲取該遠(yuǎn)程對(duì)象的引用。
在遠(yuǎn)程對(duì)象中有三個(gè)方法:getUserName(int id) 和getUserCount()的參數(shù)和返回結(jié)果都是基本類型,因此是默認(rèn)序列化的,但是對(duì)于getUserByName(String name)方法,返回的結(jié)果是一個(gè)引用類型,因此會(huì)涉及到序列化與反序列的問(wèn)題,對(duì)于User類,必須滿足以下條件:
必須實(shí)現(xiàn)
java.io.Serializable接口;其中必須有
serialVersionUID字段,格式如下:
private static final long serialVersionUID = 42L;
如果沒(méi)有該字段,則默認(rèn)該類會(huì)隨機(jī)生成一個(gè)整數(shù),且在客戶端和服務(wù)器生成的整數(shù)不相同,則會(huì)拋出異常如下:

而且在「服務(wù)器和客戶端這個(gè)字段必須保持一致才能進(jìn)行反序列化」,如果兩端都有該字段,但是數(shù)據(jù)不一致,則會(huì)拋出異常如下:

這個(gè)類在服務(wù)器和客戶端都必須可用;
在序列化的時(shí)候,如果在字段前加入了
transient關(guān)鍵字,則該數(shù)據(jù)不會(huì)被序列化;
2.2.2 構(gòu)建注冊(cè)表
注冊(cè)表其實(shí)不用寫任何代碼,在你的JAVA_HOME下bin目錄下有一個(gè)rmiregistry.exe程序,需要在你的程序的classpath下運(yùn)行該程序。
在啟動(dòng)服務(wù)器的時(shí)候,實(shí)際上需要運(yùn)行兩個(gè)服務(wù)器:
一個(gè)是遠(yuǎn)程對(duì)象本身; 一個(gè)是允許客戶端下載遠(yuǎn)程對(duì)象引用的注冊(cè)表;
由于遠(yuǎn)程對(duì)象需要與注冊(cè)表對(duì)話,所以必須首先啟動(dòng)注冊(cè)表程序。當(dāng)注冊(cè)表程序沒(méi)有啟動(dòng)的時(shí)候,如果強(qiáng)行啟動(dòng)遠(yuǎn)程對(duì)象服務(wù)器時(shí),會(huì)拋出如下錯(cuò)誤:

確保遠(yuǎn)程對(duì)象類可以被注冊(cè)表程序發(fā)現(xiàn),當(dāng)遠(yuǎn)程對(duì)象類沒(méi)有被注冊(cè)表程序發(fā)現(xiàn)時(shí),則會(huì)發(fā)現(xiàn)如下錯(cuò)誤:

如果是使用maven管理工程,則在target/classes目錄中啟動(dòng)該程序。
這說(shuō)明注冊(cè)表程序時(shí)運(yùn)行在一個(gè)單獨(dú)的進(jìn)程中的,它作為一個(gè)第三方的組件,來(lái)協(xié)調(diào)客戶端和服務(wù)器之間的通信,但是與它們兩個(gè)之間是完全解決解耦的。
rmiregistry.exe默認(rèn)情況下是監(jiān)聽1099端口,如果已經(jīng)該端口已經(jīng)被使用了,可以通過(guò)命令
rmiregistry 1020
指定其他的端口來(lái)運(yùn)行。
?可以通過(guò)
?start rmiregistry命令在后臺(tái)運(yùn)行
運(yùn)行完注冊(cè)表程序后,就可以運(yùn)行遠(yuǎn)程對(duì)象所在的服務(wù)器,以便接受客戶端的連接。
2.2.3 構(gòu)建客戶端
客戶端的代碼如下:
try {
UserHandler handler = (UserHandler) Naming.lookup("user");
int count = handler.getUserCount();
String name = handler.getUserName(1);
System.out.println("name: " + name);
System.out.println("count: " + count);
System.out.println("user: " + handler.getUserByName("lmy86263"));
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
在上邊的代碼中通過(guò)Naming.lookup(...)獲取該遠(yuǎn)程對(duì)象的引用。這個(gè)方法通過(guò)一個(gè)指定的名稱來(lái)獲取,該名稱必須與遠(yuǎn)程對(duì)象服務(wù)器綁定的名稱一致。可以通過(guò)Naming.list(...)方法列出所有可用的遠(yuǎn)程對(duì)象。
在使用客戶端連接服務(wù)器調(diào)用遠(yuǎn)程方法的時(shí)候,需要注意的問(wèn)題如下:
UserHandler類在客戶端本地必須可用,不然無(wú)法指定要調(diào)用的方法,而且「其全限定名必須與服務(wù)器上的對(duì)象完全相同」,不然拋出如下異常:

「從注冊(cè)表中獲取的對(duì)象引用已經(jīng)失去類型信息」,需要強(qiáng)制轉(zhuǎn)化為遠(yuǎn)程對(duì)象類型。這樣運(yùn)行客戶端的時(shí)候才能獲得相應(yīng)的響應(yīng);
如果在方法中使用到了引用類型,比如這里的
User,那么「該類型的全限定名也必須與服務(wù)器的相同」,如果不相同則會(huì)拋出如下異常:

客戶端的引用類型的 serialVersionUID字段要與服務(wù)器端的對(duì)象保持一致;
在客戶端的User對(duì)象如下:
public class User implements Serializable {
// 與客戶端的serialVersionUID字段數(shù)據(jù)一致
private static final long serialVersionUID = 42L;
// setter和getter可以沒(méi)有
String name;
int id;
@Override
public String toString() {
return "User{" + "name='" + name + '\'' + ", id=" + id + '}';
}
}
2.3 基本實(shí)現(xiàn)之二(服務(wù)端運(yùn)行注冊(cè)表程序)
對(duì)于實(shí)現(xiàn)二,和實(shí)現(xiàn)一的主要區(qū)別在注冊(cè)表程序的運(yùn)行,不再是通過(guò)rmiregistry.exe單獨(dú)運(yùn)行,而是通過(guò)編程來(lái)實(shí)現(xiàn),而遠(yuǎn)程接口以及其實(shí)現(xiàn)類與實(shí)現(xiàn)一完全相同。
這里注冊(cè)表的實(shí)現(xiàn)是通過(guò)java.rmi.registry包中的Registry接口和以及其實(shí)現(xiàn)類LocateRegistry來(lái)完成的。如果你詳細(xì)查看JDK的源碼的話,就會(huì)發(fā)現(xiàn)其實(shí)我們之前使用的java.rmi.Naming類中的方法實(shí)際上都是間接通過(guò)Registry和LocateRegistry實(shí)現(xiàn)的。
其中獲取根據(jù)主機(jī)和端口獲取注冊(cè)表引用的源碼如下:
/**
* Returns a registry reference obtained from information in the URL.
*/
private static Registry getRegistry(ParsedNamingURL parsed) throws RemoteException {
return LocateRegistry.getRegistry(parsed.host, parsed.port);
}
而且Naming中的方法和Registry中是一一對(duì)應(yīng)的。
而如果要?jiǎng)?chuàng)建一個(gè)注冊(cè)表,這里要使用的是LocateRegistry,該類中只要兩類方法:
創(chuàng)建「本地注冊(cè)表」并且獲取該注冊(cè)表的引用;
createRegistry(int port)createRegistry(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf)直接獲取注冊(cè)表引用,「該注冊(cè)表可以是本地運(yùn)行的,也可以是遠(yuǎn)程運(yùn)行的」,這類方法是不能夠創(chuàng)建注冊(cè)表的,只能等注冊(cè)表程序運(yùn)行起來(lái)之后,和它進(jìn)行通信來(lái)獲取引用,否則拋出異常如下:

其中的方法如下:
+ `getRegistry()`
+ `getRegistry(int port)`
+ `getRegistry(String host)`
+ `getRegistry(String host, int port)`
+ `getRegistry(String host, int port, RMIClientSocketFactory csf)`
由于是可能從遠(yuǎn)程主機(jī)獲取注冊(cè)表引用,因此可能需要指定Socket套接字來(lái)和遠(yuǎn)程主機(jī)進(jìn)行溝通,在這個(gè)過(guò)程中也有可能因?yàn)楦鞣N原因造成調(diào)用過(guò)程失敗;
運(yùn)行遠(yuǎn)程對(duì)象的服務(wù)器代碼如下:
UserHandler userHandler = null;
Registry registry = null;
try {
registry = LocateRegistry.createRegistry(1099);
userHandler = new UserHandlerImpl();
registry.rebind("user", userHandler);
System.out.println(" rmi server is ready ...");
} catch (RemoteException e) {
e.printStackTrace();
}
除此之外,其他服務(wù)器端和客戶端的代碼與實(shí)現(xiàn)一完全相同。
關(guān)于RMI的實(shí)際使用,通過(guò)和ZooKeeper結(jié)合使用RMI。
贈(zèng)書
前兩天微信群里有一位網(wǎng)友問(wèn)到了 sentinel 相關(guān)的問(wèn)題。剛好我手里有 3 本相關(guān)的書籍《實(shí)戰(zhàn)Alibaba Sentinel》,今天贈(zèng)送給有緣的網(wǎng)友!


我看了,當(dāng)當(dāng)網(wǎng)上這本書需要 75,還是有些小貴。我今天吐血免費(fèi)送 3 本,感謝大家支持!
「贈(zèng)書規(guī)則」:為本文「點(diǎn)贊」+ 「在看」 +「留言」且與文章內(nèi)容相關(guān)的優(yōu)質(zhì)留言即可上墻并從所有留言中選出 3 位點(diǎn)贊最高的讀者留言將各獲得一本。
截止時(shí)間:2022年04月05日,晚 21:00
注意事項(xiàng):最終獲贈(zèng)者請(qǐng)?jiān)?24 小時(shí)以內(nèi)添加我的微信:xttblog2,備注:贈(zèng)書??發(fā)我收件人信息,我發(fā)快遞,郵費(fèi)我自付!
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)
