作者 | 張凱
審校 | 重樓
動態(tài)代理技術與RPC(Remote Procedure Call)架構是現(xiàn)代分布式系統(tǒng)中常用的兩項關鍵技術,二者結合可以極大地提高系統(tǒng)的靈活性和可擴展性。將動態(tài)代理技術與RPC架構結合使用,可以實現(xiàn)自動化的服務調用和增強功能。開發(fā)者可以專注于業(yè)務邏輯的實現(xiàn),而不必擔心底層細節(jié)。這種結合不僅提高了代碼的復用性和可維護性,也增強了系統(tǒng)的監(jiān)控和管理能力。本文將詳細解析這兩項技術的實現(xiàn)方式以及其背后的設計邏輯。

一、動態(tài)代理
1. 代理模式
代理模式為其他對象提供了一種代理以控制對這個對象的訪問,根據(jù)代理類的創(chuàng)建時機和創(chuàng)建方式的不同,可以將其分為靜態(tài)代理和動態(tài)代理兩種形式:在程序運行前就已經存在的編譯好的代理類稱為靜態(tài)代理,在程序運行期間根據(jù)需要動態(tài)創(chuàng)建代理類及其實例來完成具體的功能稱為動態(tài)代理。
代理模式的目的是為真實業(yè)務對象提供一個代理對象以控制對真實業(yè)務對象的訪問,代理對象的作用有:
- 代理對象存在的價值主要用于攔截對真實業(yè)務對象的訪問;
- 代理對象可以和目標對象(真實業(yè)務對象)實現(xiàn)共同的接口或繼承于同一個類;
- 代理對象是對目標對象的增強,以便對消息進行預處理和后處理。
2. 反射
動態(tài)代理的實現(xiàn)核心是反射,一切動態(tài)代理的代理操作都是反射實現(xiàn)的。所以要先對反射知識有一定的了解。

2.1 反射實現(xiàn)步驟
2.1.1通過反射獲取對象的.class文件,也就是class對象(有三種方式)。
//方式一:使用Class.forName方法進行包名+類名的定位
Class c1=Class.forName("zk.reflect.Person");
//方式二:采用類名.class方法獲取反射
Class c2=Person.class;
//方式三:采用對象名.class方法獲取反射(運行過程中)
Person ps=new Person();
Class c3 = ps.getClass(); 2.1.2通過反射獲取的對象,獲取對象類中的方法,使用invoke([實例化對象],[方法對應的參數(shù)])方法,進行方法使用。
//獲取類的方法
Method[] m =c.getMethods(); //獲得本類和其父類的全部public方法
Method[] m2=c.getDeclaredMethods(); //僅獲取本類的全部方法(包括私有方法)
//獲取指定方法,并使用
//通過實例對象獲取反射
//Person p =new Person();
//獲取Person類對象
//Class c=p.getClass();
Class c=Person.class;
Person p= (Person) c.newInstance();
//獲取該類實例對象中的具體方法--第一個參數(shù)要寫Person類的方法名稱才能匹配上;后面參數(shù)是方法參數(shù)類型
Method m=c.getDeclaredMethod("eat",String.class);
//使用invoke方法對反射獲取的方法進行激活---第一個參數(shù)是實例化對象,后面參數(shù)是方法對應參數(shù)值
String s= (String) m.invoke(p,"zhangkai");
System.out.println(s);
//獲取指定方法,必須加入參數(shù),沒有加null;因為存在重載,只有方法名和參數(shù)個數(shù)兩個才能精確定位方法
Method getid=c.getMethod("getid",null);
Method setid=c.setMethod("setid",,int.class); 3. 動態(tài)代理原理
對代理模式而言,具體主題類與其代理類一般是一一對應的,這也是靜態(tài)代理的特點。但是,也存在這樣的情況:有N個主題類,但是代理類中的“預處理、后處理”都是相同的,僅僅是調用主題不同。那么,若采用靜態(tài)代理,那么必然需要手動創(chuàng)建N個代理類,這顯然讓人相當不爽。動態(tài)代理則可以簡單地為各個主題類分別生成代理類,共享“預處理,后處理”功能,這樣可以大大減小程序規(guī)模,這也是動態(tài)代理的一大亮點。(通俗上來說,動態(tài)代理不再是一個代理類代理一個委托類,而是像個大管家,指定那個委托對象,就代理誰的方法,只不過代理類中通用的邏輯會適用于每個委托類)
在動態(tài)代理中,代理類是在運行時期生成的。因此,相比靜態(tài)代理,動態(tài)代理可以很方便地對委托類的相關方法進行統(tǒng)一增強處理,如添加方法調用次數(shù)、添加日志功能等等。動態(tài)代理主要分為JDK動態(tài)代理和CGLIB動態(tài)代理兩大類。
下面以一個模擬動態(tài)代理案例來實現(xiàn)動態(tài)代理的實現(xiàn)思路:
設計兩個Service類:User和Order,使用ProxyUtils類動態(tài)代理這兩類的抽象接口。程序在調用過程中,通過接口直接到代理類中,由代理類實現(xiàn)接口實現(xiàn)類的功能以及代理類自身的一些增強功能和通用功能。

3.1 接口類
//User的一些方法
public interface UserService {
String login(String s,String p) throws InterruptedException;
void selectById(int id);
void delect();
}
//Order的一些方法
public interface OrderService {
String zhuyi();
void select();
String delectById(int id);
} 3.2 實現(xiàn)類
//UserServiceImpl實現(xiàn)類
public class UserServiceImpl implements UserService {
@Override
public String login(String name,String password) {
String resword="用戶名或密碼錯誤!";
if(name.equals("admin") && password.equals("123456")){
resword="登錄成功";
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return resword;
}
@Override
public void selectById(int id) {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(id+"的信息如下:----");
}
@Override
public void delect() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("信息正在刪除。。。。。");
}
}
//OrderServiceImpl實現(xiàn)類
public class OrderServiceImpl implements OrderService{
@Override
public String zhuyi() {
return "訂單功能區(qū):---------";
}
@Override
public void select() {
System.out.println("全部訂單如下所示--------");
}
@Override
public String delectById(int id) {
String s=id+"的信息正在刪除。。。。。";
return s;
}
} 3.3動態(tài)代理類ProxyUtils
- 該類中的proxyService方法使用泛型可以讓proxyService方法代理多種不同類的Service接口;
- 返回一個Proxy通過newProxyInstance()提供的實例對象;
- newProxyInstance()需要三部分的參數(shù)。第一部分是創(chuàng)建方法形參對象obj的類加載器對象;第二部分是創(chuàng)建方法形參對象obj的類接口對象(都為固定方法);第三部分是創(chuàng)建一個InvocationHandler()方法,意味調用類對象方法的方法;
- 代理類中的增強功能就可以寫在InvocationHandler()方法中,本例是實現(xiàn)了一個方法運行時間的檢測功能。
public class ProxyTest {
public static void main(String[] args) throws Exception {
//創(chuàng)建一個User類的代理對象,代理對象中也傳入了一個新建的User實現(xiàn)類對象
UserService userService=ProxyUtils.proxyService(new UserServiceImpl());
String rs = userService.login("admin", "123456");
System.out.println(rs);
userService.selectById(2);
userService.delect();
//創(chuàng)建一個Order類的代理對象
OrderService orderService=ProxyUtils.proxyService(new OrderServiceImpl());
String ss = orderService.delectById(9);
System.out.println(ss);
}
} 以上就是動態(tài)代理案例的全部實現(xiàn),核心的調用邏輯就是流程圖中的調用邏輯,使用動態(tài)代理類不僅可以大幅提高代碼的復用程度,而且還可以在被代理類的基礎上實現(xiàn)一些公共的增強功能,這其實就是Spring中的AOP的核心實現(xiàn)原理。例如本例就實現(xiàn)了所有被代理Service的方法運行時間檢測的功能,對于多個Service都實現(xiàn)檢測功能,其實就是一種橫向編程的思路。
二、RPC架構
1.RPC的出現(xiàn)原因
不同的業(yè)務拆分到多個應用中,讓不同的應用分別承擔不同的功能是解決這些問題的必殺技。將不同業(yè)務分拆到不同的應用后,不但可以大幅度提升系統(tǒng)的穩(wěn)定性還有助于豐富技術選型,進一步保證系統(tǒng)的性能??偟膩碚f,從單體應用到分布式多體應用是系統(tǒng)升級必經之路。
當一個單體應用演化成多體應用后,遠程調用就粉墨登場了。在一個應用時,相互通信直接通過本地調用就可完成,而變?yōu)槎囿w應用時,相互通信就得依賴遠程調用了,這時一個高效穩(wěn)定的RPC框架就顯得非常必要了。簡單的HTTP調用雖然也可以實現(xiàn)遠程通信,但效果不夠理想,原因有二:
- RPC遠程調用像本地調用一樣干凈簡潔,但其他方式對代碼的侵入性就比較強;
- 一般使用RPC框架實現(xiàn)遠程通信效率比其他方式效率要高一些。
2. RPC框架介紹
對于多體應用,由于各服務部署在不同機器,服務間的調用免不了網絡通信過程,服務消費方每調用一個服務都要寫較多網絡通信相關的代碼,不僅復雜而且極易出錯。若能實現(xiàn)一種機制,使得開發(fā)者能夠像調用本地服務一般便捷地調用遠程服務,同時對網絡通信等底層細節(jié)保持透明,那么這將極大地釋放程序員的開發(fā)負擔,顯著提升開發(fā)效率與生產力。比如,服務消費方在執(zhí)行helloService.hi(“Panda”)時,實質上調用的是遠端的服務。這種方式其實就是RPC(Remote Procedure Call),在各大互聯(lián)網公司中被廣泛使用,如阿里巴巴的HSF、Dubbo(開源)、Facebook的Thrift(開源)、Google GRPC(開源)、Twitter的Finagle(開源)等。
RPC的主要功能目標是讓構建分布式計算(應用)更容易,在提供強大的遠程調用能力時不損失本地調用的語義簡潔性。為實現(xiàn)該目標,RPC框架需提供一種透明調用機制讓使用者不必顯式的區(qū)分本地調用和遠程調用。要讓網絡通信細節(jié)對使用者透明,我們需要對通信細節(jié)進行封裝,下面是一個RPC的經典調用的流程,并且反映了所涉及到的一些通信細節(jié):

(1)服務消費方(client)以本地調用方式調用服務;
(2).client stub接收到調用后負責將方法、參數(shù)等組裝成能夠進行網絡傳輸?shù)南Ⅲw;
(3)client stub找到服務地址,并將消息發(fā)送到服務端;
(4)server stub收到消息后進行解碼;
(5)server stub根據(jù)解碼結果反射調用本地的服務;
(6)本地服務執(zhí)行并將結果返回給server stub;
(7)server stub將返回結果打包成消息并發(fā)送至消費方;
(8)client stub接收到消息,并進行解碼;
(9)服務消費方得到最終結果。
RPC框架就是要將2~8這些步驟封裝起來,讓用戶對這些細節(jié)透明,使得遠程方法調用看起來像調用本地方法一樣。
下面給出一個實現(xiàn)簡易RPC架構的代碼。
從該RPC框架的簡易實現(xiàn)來看,RPC客戶端邏輯是:1.首先創(chuàng)建Socket客戶端并與服務端建立鏈接;2.然后使用Java原生的序列化/反序列化機制將調用請求發(fā)送給客戶端,包括所調用方法的名稱、參數(shù)列表將服務端的響應返回給用戶即可。至此,一次簡單PRC調用的客戶端流程執(zhí)行完畢。特別地,從代碼實現(xiàn)來看,實現(xiàn)透明的PRC調用的關鍵就是動態(tài)代理,這是RPC框架實現(xiàn)的靈魂所在。
2.1.1服務端
服務端提供客戶端所期待的服務,一般包括三個部分:服務接口,服務實現(xiàn)以及服務的注冊暴露三部分。
//服務端接口
public interface HelloService {
String hello(String name);
String hi(String msg);
}
//服務實現(xiàn)類
public class HelloServiceImpl implements HelloService{
@Override
public String hello(String name) {
return "Hello " + name;
}
@Override
public String hi(String msg) {
return "Hi, " + msg;
}
}
//(重要)服務暴露
public class RpcProvider {
public static void main(String[] args) throws Exception {
HelloService service = new HelloServiceImpl();
// 自實現(xiàn)的RpcFramework,RPC框架使用export()方法將服務暴露出來,供客戶端消費
RpcFramework.export(service, 1234);
}
} 2.1.2客戶端
客戶端消費服務端所提供的服務,一般包括兩個部分:服務引用和服務接口兩個部分。
//(服務引用)消費端通過RPC框架進行遠程調用,這也是RPC框架功能之一
public class RpcConsumer {
public static void main(String[] args) throws Exception {
// 由RpcFramework類中refer()方法生成的HelloService接口的代理
HelloService service = RpcFramework.refer(HelloService.class, "127.0.0.1", 1234);
//使用代理對象進行實現(xiàn)類中hello()方法的調用
String hello = service.hello("World");
System.out.println("客戶端收到遠程調用的結果 : " + hello);
}
}
//與服務端共享同一個服務接口
public interface HelloService {
String hello(String name);
String hi(String msg);
}2.1.3 RPC框架類RpcFramework
public class RpcFramework {
/**
* 暴露服務
* @param service 服務實現(xiàn)
* @param port 服務端口
* @throws Exception
*/
public static void export(final Object service, int port) throws Exception {
//如果服務和端口存在問題,拋出異常
if (service == null) {
throw new IllegalArgumentException("service instance == null");
}
if (port <= 0 || port > 65535) {
throw new IllegalArgumentException("Invalid port " + port);
}
System.out.println("Export service " + service.getClass().getName() + " on port " + port);
// 建立Socket服務端
ServerSocket server = new ServerSocket(port);
for (; ; ) {
try {
// 監(jiān)聽Socket請求
final Socket socket = server.accept();
new Thread(new Runnable() {
@Override
public void run() {
try {
try {
/* 獲取請求流,Server解析并獲取請求*/
// 構建對象輸入流,從源中讀取對象到程序中
ObjectInputStream input = new ObjectInputStream(
socket.getInputStream());
try {
System.out.println("\nServer解析請求 : ");
String methodName = input.readUTF();
System.out.println("methodName : " + methodName);
// 泛型與數(shù)組是不兼容的,除了通配符作泛型參數(shù)以外
Class<?>[] parameterTypes = (Class<?>[])input.readObject();
System.out.println(
"parameterTypes : " + Arrays.toString(parameterTypes));
Object[] arguments = (Object[])input.readObject();
System.out.println("arguments : " + Arrays.toString(arguments));
/* Server 處理請求,進行響應*/
ObjectOutputStream output = new ObjectOutputStream(
socket.getOutputStream());
try {
// service類型為Object的(可以發(fā)布任何服務),故只能通過反射調用處理請求
// 反射調用,處理請求
Method method = service.getClass().getMethod(methodName,
parameterTypes);
//invoke方法調用對應方法,得到處理返回值保存在result中
Object result = method.invoke(service, arguments);
System.out.println("\nServer 處理并生成響應 :");
System.out.println("result : " + result);
//將請求處理結果寫回socket信息
output.writeObject(result);
} catch (Throwable t) {
output.writeObject(t);
} finally {
output.close();
}
} finally {
input.close();
}
} finally {
socket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 引用服務
*
* @param <T> 接口泛型
* @param interfaceClass 接口類型
* @param host 服務器主機名
* @param port 服務器端口
* @return 遠程服務,返回代理對象
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static <T> T refer(final Class<T> interfaceClass, final String host, final int port) throws Exception {
if (interfaceClass == null) {
throw new IllegalArgumentException("Interface class == null");
}
// JDK 動態(tài)代理的約束,只能實現(xiàn)對接口的代理
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
"The " + interfaceClass.getName() + " must be interface class!");
}
if (host == null || host.length() == 0) {
throw new IllegalArgumentException("Host == null!");
}
if (port <= 0 || port > 65535) {
throw new IllegalArgumentException("Invalid port " + port);
}
System.out.println(
"Get remote service " + interfaceClass.getName() + " from server " + host + ":" + port);
// JDK 動態(tài)代理,使用泛型實現(xiàn)廣泛代理
T proxy = (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class<?>[] {interfaceClass}, new InvocationHandler() {
// invoke方法本意是對目標方法的增強,在這里用于發(fā)送RPC請求和接收響應
@Override
public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
// 創(chuàng)建Socket客戶端,并與服務端建立鏈接
Socket socket = new Socket(host, port);
try {
/* 客戶端向服務端進行請求,并將請求參數(shù)寫入流中*/
// 將對象寫入到對象輸出流,并將其發(fā)送到Socket流中去
ObjectOutputStream output = new ObjectOutputStream(
socket.getOutputStream());
try {
// 發(fā)送請求
System.out.println("\nClient發(fā)送請求 : ");
output.writeUTF(method.getName());
System.out.println("methodName : " + method.getName());
output.writeObject(method.getParameterTypes());
System.out.println("parameterTypes : " + Arrays.toString(method
.getParameterTypes()));
output.writeObject(arguments);
System.out.println("arguments : " + Arrays.toString(arguments));
/* 客戶端讀取并返回服務端的響應*/
ObjectInputStream input = new ObjectInputStream(
socket.getInputStream());
try {
Object result = input.readObject();
//如果result是一個異常說明服務端返回沒成功,客戶端只能拋出異常
if (result instanceof Throwable) {
throw (Throwable)result;
}
System.out.println("\nClient收到響應 : ");
System.out.println("result : " + result);
return result;
} finally {
input.close();
}
} finally {
output.close();
}
} finally {
socket.close();
}
}
});
//給消費端返回代理對象,供使用
return proxy;
} 2.2關于RPC框架的解析與說明
(1)RPC框架如何做到透明化遠程服務調用
在封裝通信細節(jié)以實現(xiàn)用戶能夠像本地調用服務一樣便捷地訪問遠程服務的方案中,Java的動態(tài)代理技術無疑是一個有效的解決方案。具體而言,Java支持兩種動態(tài)代理的實現(xiàn)方式:JDK動態(tài)代理和CGLIB動態(tài)代理。盡管基于字節(jié)碼生成的CGLIB代理方法在功能上更為強大和高效,但由于其代碼維護的復雜性,許多RPC框架的實現(xiàn)仍然優(yōu)先選擇使用JDK動態(tài)代理。
在此背景下,RPCFramework的invoke方法有效地封裝了與遠端服務之間的通信細節(jié)。當消費者通過RPCFramework獲取服務提供者的接口后,在調用helloService.hi("Panda")方法時,將直接觸發(fā)該invoke方法,進而執(zhí)行與遠程服務的交互。此種設計極大地簡化了遠程服務調用的復雜性,為開發(fā)者提供了便捷的使用體驗。
(2)如何發(fā)布自己的服務
如何讓用戶高效地調用我們的服務?是否僅僅通過在代碼中硬編碼服務的IP地址和端口即可實現(xiàn)?實際上,在生產環(huán)境中,這種做法并不可行,因為服務的部署節(jié)點可能會頻繁地上線和下線。舉例而言,若發(fā)現(xiàn)現(xiàn)有的服務實例不夠應付需求,需要增加新的服務節(jié)點時,調用者必須手動更新其配置以包含新的IP地址,甚至可能需要在負載均衡的上下文中實現(xiàn)輪詢調用。與此同時,當某一臺機器出現(xiàn)故障,導致服務不可用時,調用者又必須手動刪除失效機器的IP。此過程顯然是相當繁瑣和低效的。
為了解決這一問題,應當采用一種能夠實現(xiàn)自動通知的機制,使得服務節(jié)點的變更對調用方保持透明,從而免去硬編碼服務提供者地址的需要。在實際生產中,許多RPC框架已經實現(xiàn)了此類自動告知功能。例如,阿里巴巴內部使用的RPC框架HSF通過ConfigServer來管理服務的注冊與發(fā)現(xiàn)。此外,Zookeeper也廣泛應用于實現(xiàn)服務的自動注冊與發(fā)現(xiàn)。無論采用何種具體技術,這些解決方案通?;诎l(fā)布/訂閱模式,以有效管理服務的可用性與負載均衡。
(3)序列化與反序列化
在Java中,對象無法直接在網絡中進行傳輸,因此在進行RPC請求時,客戶端如何將請求發(fā)送給服務端,并如何接收服務端的響應,成為一個需要解決的問題。解決方案在于對Java對象進行序列化,以便將其轉換為可傳輸?shù)淖止?jié)流,并在接收端通過反序列化過程將字節(jié)流還原為原始對象,從而便于后續(xù)的處理。
實際上,序列化和反序列化的技術存在多種實現(xiàn)方式,包括Java的原生序列化、JSON格式、阿里巴巴的Hessian序列化以及ProtoBuf序列化等。這些不同的序列化方式在傳輸效率上各有差異,同時也具備各自的特點和優(yōu)勢,適用于不同場景的需求。因此,選擇適當?shù)男蛄谢桨笇τ谔嵘齊PC框架的性能和可用性至關重要。
三、總結
動態(tài)代理允許在運行時創(chuàng)建對象的代理,從而對方法調用進行攔截和處理。在Java等編程語言中,利用反射機制,可以動態(tài)創(chuàng)建代理類。這種方式使得開發(fā)人員可以在不修改已有代碼的情況下,增強或改變方法的行為。本文通過構建實例的方式詳細解釋了其工作原理。RPC(遠程過程調用)是一種允許程序在不同的地址空間(通常是不同機器上的進程)中調用子程序或服務的協(xié)議。通過RPC,開發(fā)者可以像調用本地方法一樣,方便地調用遠程服務,隱藏了網絡通信的復雜性。
通過動態(tài)代理技術與RPC架構的結合,可以實現(xiàn)以下優(yōu)勢:
- 透明的遠程調用:使用動態(tài)代理,開發(fā)者在調用遠程服務時無需關心具體的網絡請求和響應處理,可以像調用本地方法一樣使用。
- 增強的功能:通過動態(tài)代理,能夠在服務調用時加入額外的處理邏輯,如日志記錄、方法監(jiān)控、錯誤處理等。
- 解耦合:RPC客戶端和服務端的實現(xiàn)可通過接口進行解耦,更新和維護服務時不會影響到調用方的代碼。
- 優(yōu)化和擴展:因為使用了動態(tài)代理,增加新的功能或改變業(yè)務邏輯時,只需更改代理的實現(xiàn)即可,而不需要修改每一個具體的服務調用。
動態(tài)代理技術和RPC架構的結合為分布式系統(tǒng)的開發(fā)提供了靈活和高效的解決方案。它們使得系統(tǒng)能夠在復雜的基礎設施中輕松處理遠程服務的調用,同時還增強了代碼的可重用性和可維護性。這種技術的廣泛應用,無疑對現(xiàn)代軟件架構的演變產生了深遠的影響。
作者介紹
張凱,中國農業(yè)銀行股份有限公司研發(fā)中心軟件研發(fā)工程師,擅長SpringBoot+Vue全棧式開發(fā),數(shù)據(jù)挖掘與建模,熱愛編程和學習前沿技術信息了解內部的實現(xiàn)邏輯。




























