中介軟體與分散式物件的核心精神
中介軟體提供更高層次的程式抽象,藏起底層異質性,讓互通與可攜變得可能。
先讀原文段落,旁邊就是白話
這是一本英文書。左邊放原文、右邊放白話導讀——你既讀得懂,也順手碰了原文。
中介軟體提供更高層次的程式抽象,藏起底層異質性,讓互通與可攜變得可能。
中介軟體在忙什麼
中介軟體提供更高層次的程式抽象,藏起底層異質性,讓互通與可攜變得可能。
深度探秘
中介軟體到底夾在哪兩層之間
中介軟體是「夾心餅乾的中間層」
一個完整的分散式系統,底層是各種異質的硬體、作業系統與網路;上層是你想寫的應用程式。問題是:底層長得五花八門,你的程式不該被這些細節綁死。
中介軟體 (middleware) 就是夾在中間那層,它有兩個任務:
- 提供更高層次的程式抽象,讓你寫分散式程式時不必每次都從 socket、封包打包開始。
- 透過分層 (layering) 把底層異質性藏起來,促進互通 (interoperability) 與可攜 (portability)。
互通=不同實作之間能彼此溝通;可攜=同一份程式能搬到不同平台跑。
本章聚焦兩種最重要的中介軟體風格:分散式物件 (distributed objects) 與元件 (components)。
中介軟體提供高層抽象並隱藏底層異質性,讓分散式程式好寫又能跨平台。
生活妙喻
中介軟體就像國際會議的同步口譯
同步口譯員的比喻
想像一場聯合國會議,與會者講英文、法文、中文、阿拉伯文(這就是異質的底層平台)。如果每個人都要學會其他所有語言才能開會,根本辦不成事。
口譯員坐在中間,你只要對著麥克風講母語,對方就能用他的母語聽到——你完全不用管翻譯怎麼運作。
- 口譯員 = 中介軟體
- 「對著麥克風講母語」= 高層程式抽象
- 「不用學其他語言」= 隱藏異質性
- 「大家都能參與會議」= 互通與可攜
flowchart TD A[應用程式] --> B[中介軟體 高層抽象] B --> C[作業系統與網路 異質底層] B --> D[隱藏異質性 促進互通與可攜]
中介軟體像同步口譯,讓你只講母語就能和異質平台溝通。
實用超能力
為什麼物件導向特別適合分散式
物件導向的三個好處,恰好對症下藥
分散式物件之所以受歡迎,是因為物件導向的特質剛好解決分散式的難題:
| 物件導向特質 | 帶來的好處 |
|---|---|
| 封裝 (encapsulation) | 把內部實作包起來,天生適合「跨機器只露出介面」 |
| 資料抽象 (data abstraction) | 規格與實作乾淨分離,呼叫方只看介面,不管對方用什麼語言或作業系統 |
| 動態與可擴充 | 可以引入新物件,或用相容物件替換舊物件 |
真實情境:你在寫一個跨公司的訂單系統,對方用 C++、你用 Java。只要雙方同意一份「介面」,你呼叫 placeOrder() 時根本不用知道對方是什麼語言寫的——這就是封裝與資料抽象的威力。
代表性的分散式物件中介軟體包括 Java RMI 與 CORBA。
封裝與資料抽象讓呼叫方只面對介面,這正是分散式程式最需要的乾淨邊界。
你只講母語、對著麥克風,口譯員把訊息翻給講其他語言的人;你不用學任何外語,就像程式不用管底層平台差異。
你看菜單點菜(介面)就好,廚房用瓦斯爐還是電磁爐、廚師是誰(實作細節)你不必知道也不會影響點餐。
本節字彙
分散式物件:熟悉又陌生
分散式物件沿用 OO 模型,但在 class、繼承、遠端參照、例外與垃圾回收上都和本地物件不同。
深度探秘
同樣叫物件,分散式版本多了什麼
分散式物件 = 物件 + 距離的代價
分散式物件讓你「像呼叫本地物件一樣」呼叫遠端物件,主要靠遠端方法呼叫 (RMI)。但因為物件跑在別台機器上,許多概念被升級了:
| 一般物件 | 分散式物件 | 多了什麼 |
|---|---|---|
| 物件參照 | 遠端物件參照 | 全域唯一、可當參數傳遞 |
| 介面 | 遠端介面 | 用 IDL 指定可被遠端呼叫的方法 |
| 動作 | 分散式動作 | 一次呼叫可能引發跨機器的呼叫鏈 |
| 例外 | 分散式例外 | 多了訊息遺失、行程失敗等狀況 |
| 垃圾回收 | 分散式垃圾回收 | 要跨機器判斷物件是否還被參照 |
此外,分散式物件世界裡 class 概念被淡化(異質環境難有共同 class 解讀),而且偏好介面繼承而非實作繼承。
分散式物件沿用物件模型,但遠端參照、分散式例外、分散式垃圾回收等都是為了應付『距離』而生的新成員。
生活妙喻
本地同事 vs. 遠距外包
找同事 vs. 找海外外包
呼叫本地物件像是轉頭問坐你旁邊的同事一件事——他一定在、一定聽得到、回答幾乎瞬間到。
呼叫分散式物件像是找一個海外外包團隊:
- 你得有對方的聯絡方式(遠端物件參照),而且這聯絡方式要能轉交給別人。
- 你只看得到對方公布的服務清單(遠端介面),看不到他們內部怎麼做。
- 你的一個請求可能讓對方再去找他們的下游廠商(分散式動作的呼叫鏈)。
- 訊息可能寄丟、對方公司可能臨時倒閉(分散式例外)。
- 還得有人定期確認「這外包還有沒有人在用」,沒人用才能解約(分散式垃圾回收)。
flowchart TD A[客戶端] -->|遠端方法呼叫| B[遠端物件] B -->|呼叫鏈| C[下游遠端物件] A -.->|可能發生| D[訊息遺失或行程失敗 分散式例外]
分散式物件像找海外外包:要聯絡方式、只看服務清單、可能牽連下游、還可能出包。
實用超能力
中介軟體替你扛的額外苦差事
因為有距離,中介軟體要多做這些事
正因為分散式物件比本地物件複雜,中介軟體必須額外提供:
- 物件間通訊 (inter-object communication):通常用 RMI,也常補上分散式事件等間接通訊。
- 生命週期管理 (lifecycle management):物件的建立、遷移與刪除。
- 啟動與停用 (activation/deactivation):物件數量龐大時不可能全部一直開著,要用到才喚醒,閒置就休眠。
- 持久化 (persistence):物件有狀態,必須跨越啟動/停用循環甚至系統故障保存下來。
- 額外服務:命名、安全、交易等。
為什麼要啟動/停用? 想像一個有上百萬個物件的伺服器,如果全部常駐記憶體會爆炸;所以平常讓它們「睡覺」,有請求進來才「叫醒」(activation),就像旅館不會把每間房的燈都開著等客人。
距離帶來複雜度,於是中介軟體得扛起通訊、生命週期、啟動停用、持久化與各種服務。
它是全域唯一的『聯絡卡』,可以交給別人去聯絡同一個遠端物件,就像把外包窗口的名片轉交給同事。
物件數量太多不可能全常駐,平常讓它休眠省資源,收到請求才喚醒、配給執行緒,這就是啟動;反之則是停用。
本節字彙
CORBA 的語言中立魔法:IDL
OMG 用 ORB 這個比喻來幫客戶端呼叫遠端物件;CORBA 物件實作 IDL 介面,客戶端不一定是物件。
先讀原文段落,旁邊就是白話
這是一本英文書。左邊放原文、右邊放白話導讀——你既讀得懂,也順手碰了原文。
OMG 用 ORB 這個比喻來幫客戶端呼叫遠端物件;CORBA 物件實作 IDL 介面,客戶端不一定是物件。
ORB 與 CORBA 物件模型
OMG 用 ORB 這個比喻來幫客戶端呼叫遠端物件;CORBA 物件實作 IDL 介面,客戶端不一定是物件。
深度探秘
OMG、ORB 與語言中立的野心
CORBA 想解決什麼
OMG (Object Management Group) 在 1989 年成立,目標是讓不同語言、不同硬體、不同作業系統寫的分散式物件都能互相溝通。為此他們設計了一套與實作語言無關的介面語言。
他們提出一個核心比喻:物件請求仲介者 (Object Request Broker, ORB)。ORB 的工作是幫客戶端在遠端物件上呼叫方法,這包含三步:
- 定位物件
- 必要時啟動物件
- 把客戶端的請求轉達給物件,物件執行後回覆
CORBA = Common Object Request Broker Architecture,整個架構就是繞著 ORB 這個仲介者打造的。本書聚焦 CORBA 2 規格。
OMG 用語言中立的介面語言與 ORB 仲介者,讓任何語言寫的物件都能互相呼叫方法。
生活妙喻
ORB 像萬能客服總機
客服總機的比喻
想像你打電話給一家大公司的總機(ORB),說:「我要找會計部處理這張發票。」你不需要知道:
- 會計部在幾樓(定位物件)
- 承辦人現在在不在座位上、要不要先去叫他來(啟動物件)
- 你的需求怎麼被轉接過去(轉達請求)
你只管講出需求,總機幫你搞定一切,承辦人處理完再回覆你。
flowchart TD A[客戶端] -->|我要呼叫某方法| B[ORB 物件請求仲介者] B --> C[定位物件] B --> D[必要時啟動物件] B --> E[轉達請求給遠端物件] E -->|執行並回覆| A
更厲害的是,這台總機聽得懂各國語言——不管承辦人講 C++、Java 還是 Python,總機都能橋接。
ORB 像萬能客服總機,幫你定位、喚醒並轉接到正確的承辦人,還跨語言。
實用超能力
CORBA 物件模型的幾個關鍵差異
CORBA 物件模型:和你想的物件不太一樣
CORBA 物件模型和一般 RMI 相似,但有幾個重要差別:
- 客戶端不一定是物件:客戶端可以是任何送出請求訊息、接收回覆的程式。
- CORBA 物件指的就是遠端物件:它實作一個 IDL 介面、有遠端物件參照、能回應 IDL 介面中方法的呼叫。
- 可以用非物件導向語言實作:例如沒有 class 概念的語言也能寫 CORBA 物件。
為什麼 CORBA 沒有 class? 因為不同實作語言對 class 的理解天差地遠,有些語言甚至沒有 class。所以:
結論:CORBA IDL 不能定義 class
→ 因此 class 的實例不能當參數傳遞
→ 但任意複雜的資料結構(如 struct)可以當參數傳遞
實用差別:和單一語言的 Java RMI 比,寫 CORBA 多了三件事要學——CORBA 的物件模型、IDL、以及 IDL 到實作語言的映射。
CORBA 物件實作 IDL 介面並有遠端參照,但因為跨語言而捨棄 class,改用 struct 等資料結構傳值。
你只講需求,總機幫你定位部門、必要時把承辦人叫來、再轉接過去,還聽得懂各國語言——就像 ORB 跨語言定位、啟動、轉達請求。
不同國家(語言)對精裝合約(class)格式各有規定,難以通用;於是改用大家都看得懂的填空表格(struct)來傳遞資料。
本節字彙
用 IDL 描述介面
IDL 提供 module、interface、型別、屬性與方法簽章;參數用 in/out/inout 標註,並支援例外與介面繼承。
深度探秘
IDL 是一份語言中立的合約
IDL:只說『有哪些方法』,不說『怎麼做』
IDL (Interface Definition Language) 用來指定一個介面的名字與一組客戶端可以呼叫的方法。它的語法是 ANSI C++ 的子集,再加上支援分散式的關鍵字(如 interface、in、out、inout、readonly、raises)。
IDL 提供的工具有:
- module:把介面與型別定義分組成邏輯單位,形成命名範圍避免名稱衝突。
- interface:描述 CORBA 物件提供哪些操作與屬性。
- 型別:15 種基本型別(如 long、boolean)與建構型別(struct、sequence、array、union、enum 等)。
- attribute:類似 public 欄位,編譯器會自動產生取值/設值方法(
readonly只產生取值)。
一個方法簽章的通式:
[oneway] <return_type> <method_name>(parameter1, ..., parameterL)
[raises (except1, ..., exceptN)] [context (...)];
IDL 是語言中立的『介面合約』,描述方法、型別與屬性,但完全不談實作。
生活妙喻
in/out/inout 像寄包裹的方向標籤
參數方向:誰寄給誰
IDL 方法的每個參數都要標明方向,這就像在包裹上貼方向標籤:
| 標籤 | 意思 | 比喻 |
|---|---|---|
| in | 值從客戶端傳到伺服器 | 你寄東西『去』給對方 |
| out | 值從伺服器傳回客戶端 | 對方寄東西『回』給你 |
| inout | 兩個方向都可能傳 | 來回都寄(較少用) |
例如:
void getPerson(in string name, out Person p);
你把 name『寄去』,伺服器把找到的 Person『寄回』。
參數傳遞語意也很關鍵:
- 型別若是某個 IDL 介面的名字 → 傳的是遠端物件參照。
- 基本型別與建構型別 → 以值複製傳遞,到對方那邊產生新的副本。
flowchart LR A[客戶端] -->|in 參數| B[伺服器] B -->|out 參數與回傳| A
in/out/inout 標明參數流向;介面型別傳參照,基本與建構型別以值複製傳遞。
實用超能力
例外、繼承與一段真實 IDL
把它組起來看
一段真實的 IDL(取自白板範例):
struct GraphicalObject {
string type;
Rectangle enclosing;
boolean isFilled;
};
interface Shape {
long getVersion();
GraphicalObject getAllState();
};
typedef sequence <Shape, 100> All;
interface ShapeList {
exception FullException { };
Shape newShape(in GraphicalObject g) raises (FullException);
All allShapes();
long getVersion();
};
重點觀察:
- 例外:
newShape用raises (FullException)宣告它可能丟出使用者自訂例外;CORBA 也會產生系統例外(伺服器太忙、通訊問題等)。客戶端兩種都該處理。 - 介面繼承:若
interface B: A,B 繼承 A 的方法簽章並可加新項;IDL 允許多重繼承(interface Z : B, C),但若 B、C 有同名項則需用B::Q這種範圍名稱消歧。 - 預設呼叫語意是 at-most-once;加
oneway則變成 maybe 語意(不阻塞、不回傳結果)。
IDL 用 raises 宣告例外、用冒號表達介面繼承(可多重),預設 at-most-once,oneway 則不阻塞。
in 是你寄去給對方、out 是對方寄回給你、inout 是來回都寄;標籤讓中介軟體知道資料該往哪個方向搬。
基本與建構型別像影印副本,對方改了不影響你;介面型別像給鑰匙(遠端參照),雙方指向同一個遠端物件。
本節字彙
CORBA 的架構與遠端參照
CORBA 架構在 RMI 基礎上多了物件配接器、實作儲存庫與介面儲存庫,並支援靜態與動態呼叫。
先讀原文段落,旁邊就是白話
這是一本英文書。左邊放原文、右邊放白話導讀——你既讀得懂,也順手碰了原文。
CORBA 架構在 RMI 基礎上多了物件配接器、實作儲存庫與介面儲存庫,並支援靜態與動態呼叫。
ORB core、物件配接器與儲存庫
CORBA 架構在 RMI 基礎上多了物件配接器、實作儲存庫與介面儲存庫,並支援靜態與動態呼叫。
深度探秘
CORBA 架構比基本 RMI 多了三個零件
三個新成員
CORBA 架構建立在 RMI 之上,但多了三個關鍵元件:物件配接器 (object adapter)、實作儲存庫 (implementation repository) 與介面儲存庫 (interface repository)。
各零件職責:
- ORB core:包含通訊模組的全部功能,另外提供啟停 ORB、在遠端參照與字串間轉換、為動態呼叫提供引數清單等操作。
- 物件配接器:橋接『有 IDL 介面的 CORBA 物件』與『實作它的 servant 類別』。它建立遠端物件參照、把每個 RMI 經由 skeleton 派送給對應的 servant、並負責 servant 的啟動與停用。
- skeleton / proxy(stub):由 IDL 編譯器產生,skeleton 在伺服器語言、proxy 在客戶端語言,負責封送與解封參數、例外與結果。
CORBA 標準的物件配接器叫 POA (Portable Object Adapter),之所以『可攜』是因為它讓應用與 servant 能跑在不同開發商的 ORB 上。
CORBA 在 RMI 基礎上加了物件配接器、實作儲存庫、介面儲存庫;POA 負責把 CORBA 物件接到 servant。
生活妙喻
把伺服器想成一棟智慧辦公大樓
辦公大樓的各個角色
把 CORBA 伺服器想成一棟大樓:
- ORB core = 大樓的通訊基礎建設(電話線、網路)。
- 物件配接器 (POA) = 樓層櫃台,知道每個房號(CORBA 物件名)對應到哪位實際員工(servant),並負責把訪客帶到正確的人、員工不在就把他叫來上班(啟動/停用)。
- 實作儲存庫 = 人資檔案室,記錄『哪個部門的程式檔放在哪、要用時去哪台機器哪個埠把伺服器叫醒』,按需啟動已註冊的伺服器。
- 介面儲存庫 = 服務型錄查詢台,告訴你某種介面有哪些方法、每個方法要哪些參數型別——這給了 CORBA 反射 (reflection) 的能力。
flowchart TD A[客戶端] -->|請求| B[ORB core] B --> C[物件配接器 POA] C -->|派送| D[Servant 真正執行的物件] E[實作儲存庫] -->|按需啟動伺服器| C F[介面儲存庫] -->|提供介面資訊 反射| A
POA 是樓層櫃台、實作儲存庫是人資檔案室、介面儲存庫是型錄查詢台,三者協作把請求送到正確的 servant。
實用超能力
靜態 vs. 動態呼叫,與 POA 的彈性策略
編譯期就知道 vs. 執行期才知道
CORBA 同時支援兩種呼叫:
- 靜態呼叫:遠端介面在編譯期就已知,可用客戶端 stub 與伺服器 skeleton。大多數人偏好它,因為程式模型較自然。
- 動態呼叫 (DII):介面在編譯期未知時使用。例如一個物件瀏覽器要顯示系統裡所有 CORBA 物件,不可能事先包含所有 proxy。此時客戶端可向介面儲存庫查出方法資訊,再臨時組出呼叫送給伺服器。對應地,伺服器側有動態 skeleton 能接受未知介面的呼叫。
POA 的彈性:可設定多種策略,例如
- 物件參照是持久 (persistent) 還是暫態 (transient);
- 每次呼叫是否給獨立執行緒;
- 是否每個 CORBA 物件各有專屬 servant(預設是一個 servant 服務該 POA 的所有物件)。
legacy code(舊有非分散式程式碼)也能變成 CORBA 物件:替它定義一個 IDL 介面、提供合適的物件配接器與 skeleton 即可。
介面編譯期已知用靜態呼叫,未知則用動態呼叫;POA 還能設定持久/暫態、執行緒與 servant 對應等策略。
它握有房號(物件名)對員工(servant)的對照表,把訪客帶到正確的人,員工不在還會把他叫來上班(啟動),訪客離開後讓他休息(停用)。
你拿到一個沒見過的物件參照,可以到查詢台問『這種介面有哪些方法、要什麼參數』,這就是 CORBA 的反射能力。
本節字彙
遠端參照 IOR 與命名服務
IOR 包含型別 ID、傳輸協定位址與物件鍵;暫態與持久 IOR 定位 servant 的方式不同,命名服務則把名字對應到參照。
深度探秘
IOR 是 CORBA 物件的完整門牌
IOR:可互通的物件參照
CORBA 規定了一種遠端物件參照格式,叫可互通物件參照 (Interoperable Object Reference, IOR)。它有三大欄位:
| 欄位 | 內容 | 作用 |
|---|---|---|
| IDL 介面型別 ID | 介面的型別識別 | 若有介面儲存庫,可用來取回 IDL 定義 |
| 協定與位址細節 | 例如 IIOP 下的主機網域名稱與埠號 | 找到伺服器在哪 |
| 物件鍵 (object key) | 物件配接器名稱 + 物件名稱 | ORB 用來辨識是哪個 CORBA 物件 |
IIOP (Internet Inter-ORB Protocol) 使用 TCP,位址就是主機網域名稱加埠號。第二個欄位可重複,以指定多個位址,達成複製與容錯。
IOR 是 CORBA 物件的完整門牌,含型別 ID、協定位址與物件鍵三欄。
生活妙喻
暫態 vs. 持久 IOR:臨時攤位 vs. 連鎖總部
兩種門牌,兩種找人方式
IOR 分兩種,定位 servant 的方式不同:
暫態 IOR (transient):只在『裝著該物件的行程』活著時有效,門牌上寫的是伺服器本身的位址。
- 找法:伺服器 ORB core 收到請求 → 用物件配接器名稱找到配接器 → 用物件名稱找到 servant。
- 比喻:路邊臨時攤位,攤位收了門牌就失效。
持久 IOR (persistent):能跨越物件的多次啟動,門牌上寫的是實作儲存庫的位址。
- 找法:實作儲存庫收到請求 → 取出物件配接器名稱 → 必要時在登記的主機位址啟動 CORBA 物件 → 把實際位址回給客戶端 ORB,之後客戶端直接對該位址送請求。
- 比喻:連鎖店總部,你先找總部,總部告訴你現在哪家分店在營業。
flowchart TD A[客戶端 ORB] -->|暫態 IOR| B[伺服器 ORB core 直接定位 servant] A -->|持久 IOR| C[實作儲存庫] C -->|必要時啟動並回傳位址| A
暫態 IOR 直接指向伺服器、隨行程消失;持久 IOR 指向實作儲存庫、可跨啟動定位。
實用超能力
命名服務:給物件一個好記的名字
別讓人類記 IOR
IOR 對機器很完美,但對人類來說又長又難記。命名服務 (Naming service) 就是來解決這件事:它把人類可讀的名字對應到遠端物件參照,是任何 ORB 幾乎都要加上的基礎服務。
典型用法(Java):伺服器把物件以名字 rebind 到命名服務,客戶端再用同樣的名字 resolve 取回參照。
// 伺服器端:把 ShapeList 物件以名字註冊
NameComponent nc = new NameComponent("ShapeList", "");
NameComponent path[] = { nc };
ncRef.rebind(path, SLRef);
// 客戶端:用名字取回參照
ShapeList shapeListRef =
ShapeListHelper.narrow(ncRef.resolve(path));
注意
narrow:resolve回傳的是泛用的Object型別,要『窄化』成你真正需要的型別(ShapeList)。
對比命名與交易服務:命名服務用名字找物件;交易服務 (Trading service) 則用屬性找物件,像目錄服務——你說『我要一個會做 X、價格低於 Y 的服務』。
命名服務把好記的名字對應到遠端參照,用 rebind 註冊、resolve 取回,再用 narrow 窄化型別。
暫態 IOR 像臨時攤位,攤位收了門牌就失效;持久 IOR 像先找連鎖總部,總部再告訴你目前哪家分店在營業(必要時把物件啟動)。
你不必背別人的長串電話號碼(IOR),只要查名字就能找到對應號碼(遠端參照)。
本節字彙
從物件演進到元件
隱性相依、需懂大量中介軟體細節、關注點纏繞、部署全靠手工,是催生元件式做法的四個需求。
先讀原文段落,旁邊就是白話
這是一本英文書。左邊放原文、右邊放白話導讀——你既讀得懂,也順手碰了原文。
隱性相依、需懂大量中介軟體細節、關注點纏繞、部署全靠手工,是催生元件式做法的四個需求。
分散式物件的四大痛點
隱性相依、需懂大量中介軟體細節、關注點纏繞、部署全靠手工,是催生元件式做法的四個需求。
深度探秘
分散式物件很好用,但有四個結構性問題
為什麼還要演進到元件
分散式物件(如 CORBA)很成功,但實務上暴露出四個痛點,正是它們催生了元件式做法:
- 隱性相依 (implicit dependencies):物件介面只說明它『提供』什麼,沒說明它『依賴』什麼。物件內部可能偷偷呼叫別的物件或命名、安全等服務,但這些從外部介面看不出來。
- 與中介軟體的互動太複雜:就算標榜透明,程式設計師仍被迫處理大量低階細節——命名、POA、ORB core 等呼叫,把真正的應用邏輯淹沒。
- 缺乏分散式關注點的分離:安全、交易、協調、複製這些非功能性需求,得由開發者親手在物件中插入呼叫,造成關注點纏繞。
- 沒有部署支援:物件得手動部署到每台機器、手動啟動、手動建立綁定,大規模部署既累又易錯,且不可攜。
分散式物件的四大痛點是:隱性相依、與中介軟體糾纏、無法分離分散式關注點、缺乏部署支援。
生活妙喻
找水電工 vs. 找一個只報價不說工具的師傅
隱性相依:看不見的依賴最可怕
想像你請一位師傅來裝冷氣(他公布的『介面』是:我會裝冷氣)。但他沒告訴你:
- 他得先借用你家的電鑽(依賴別的工具)
- 還得打電話叫一個搬運工來幫忙(依賴別的服務)
這些隱性相依讓你很難評估:換另一位師傅行不行?少了搬運工會不會出事?這正是物件介面『只說提供、不說依賴』的問題。
再看關注點纏繞:好比食譜裡,把『怎麼煮這道菜』和『瓦斯怎麼開、抽油煙機怎麼接電、廚房防火怎麼做』全寫在一起——真正的料理步驟反而被淹沒。
flowchart TD A[物件對外介面 只說提供什麼] --> B[實際上偷偷依賴] B --> C[其他物件] B --> D[命名服務] B --> E[安全與交易服務] A -. 看不見這些依賴 .-> B
隱性相依像『只報價、不說會用到哪些工具與幫手』的師傅,讓人難以替換與安全組裝。
實用超能力
每個痛點都對應一個明確需求
痛點 → 需求:元件要補的洞
書中把每個痛點轉成一個明確需求,這正是元件式中介軟體要實現的目標:
| 痛點 | 對應需求 |
|---|---|
| 隱性相依 | 不只要描述物件提供的介面,也要描述它對其他物件的依賴 |
| 與中介軟體糾纏 | 乾淨分離『中介軟體框架程式』與『應用程式』,讓開發者只專注後者 |
| 關注點未分離 | 把安全、交易等分散式服務的複雜度盡量對開發者隱藏 |
| 無部署支援 | 中介軟體應內建部署支援,讓分散式軟體像安裝單機軟體一樣容易 |
回看真實例子:CORBA 的 whiteboard 範例裡,伺服器其實會對客戶端 callback、客戶端與伺服器都會呼叫命名服務——但這些都沒寫在對外介面上(隱性相依);而真正和白板有關的程式碼很少,卻和一堆分散式系統呼叫交織在一起(關注點纏繞)。這就是為什麼需要演進。
四個痛點各自對應一個需求:顯式描述依賴、分離中介軟體與應用、隱藏分散式服務複雜度、內建部署支援。
你只看到他公布『我會裝冷氣』,卻不知道他暗中依賴你的電鑽和一個搬運工,導致你很難評估替換或安全組裝。
安全、交易等分散式關注點和應用邏輯纏在一起,真正的『料理步驟』被技術細節淹沒,程式變得又長又難懂。
本節字彙
元件、容器與部署
元件用提供介面與必要介面寫清楚相依;容器扛起非功能性需求;部署描述子讓部署像裝單機軟體一樣簡單。
深度探秘
元件:把依賴也寫進合約
元件的定義
Szyperski 的經典定義:
軟體元件是一個組合單位,具有契約式指定的介面與僅有明確的上下文相依。
關鍵字是『僅有』——意思是所有上下文相依都必須明確 (explicit),沒有任何隱性相依。元件像分散式物件一樣是封裝的組合單位,但它的契約同時包含:
- 提供介面 (provided interfaces):它對外提供的服務。
- 必要介面 (required interfaces):它正常運作所依賴、必須被連接上的其他元件。
在一個元件組態中,每個必要介面都必須綁定到另一個元件的提供介面。這就形成了一份軟體架構:元件 + 介面 + 介面間的連接。
元件擁護者強調組合優於繼承,因為繼承會在類別間製造另一種隱性相依(如脆弱基礎類別問題)。
元件同時宣告提供介面與必要介面,把所有依賴寫成明確契約,杜絕隱性相依。
生活妙喻
元件像有公母插頭的電器,容器像保母
提供/必要介面像插頭與插座
把元件想成一台電器:
- 提供介面 = 它對外能輸出的服務,好比它身上的插座,別人可以插進來用。
- 必要介面 = 它自己要運作所需的依賴,好比它的插頭,必須插到別人的插座才能通電。
組裝系統就像把一堆電器的插頭和插座一一接好——軟體開發因此從『開發』走向『組裝 (assembly)』,可以用現成元件拼出更複雜的服務。
容器 (container) 則像一個盡責的保母/管家:它把元件包在裡面,不讓外界直接碰元件,而是攔截 (interception) 所有進來的呼叫,先替元件處理好安全、交易、生命週期等雜事,再放行給元件。
flowchart TD A[進來的呼叫] --> B[容器 攔截] B --> C[呼叫安全與交易服務] B --> D[管理 ORB 與生命週期] B --> E[元件 只負責應用邏輯]
提供/必要介面像插座與插頭,靠組裝拼系統;容器像保母,靠攔截替元件扛下分散式雜事。
實用超能力
容器、應用伺服器與部署描述子
容器補齊了另外三個需求
上一節元件定義解決了『隱性相依』,但簡化開發、隱藏服務複雜度、支援部署這三項,是靠容器補上的。容器支援常見的三層模式:
- 前端(常為 web)客戶端
- 容器,內含實作應用/商業邏輯的元件
- 系統服務,管理持久儲存中的資料
支援容器模式與關注點分離的中介軟體就叫應用伺服器 (application server),例如 WebSphere、EJB、Spring、JBoss、GlassFish。它的好處是把分散式複雜度藏起來,例如 POA 相關呼叫由容器代勞、交易與安全靠攔截自動處理。
部署支援靠部署描述子 (deployment descriptor),通常用 XML 寫,描述:
<!-- 概念示意:部署描述子說明如何部署元件組態 -->
<deployment>
<component name="OrderService" required="InventoryService"/>
<transaction policy="container-managed"/>
<security level="required"/>
</deployment>
元件被部署進容器,容器解讀部署描述子來設定底層中介軟體與分散式服務的政策;工具則據此在實體架構上正確部署。
容器用攔截扛下非功能性需求形成應用伺服器;部署描述子(常為 XML)讓部署像裝單機軟體一樣自動化。
提供介面像插座讓別人插進來用,必要介面像插頭必須接到別人的插座才能通電;組裝系統就是把插頭插座一一接好。
外界的呼叫不直接碰元件,而是先被容器攔截,由它替元件打理好安全、交易、生命週期等雜事,元件只管做自己的應用邏輯。
本節字彙
元件案例:EJB 與 Fractal
EJB 用 POJO 加註解開發 bean,容器負責交易、安全與生命週期,並支援相依注入與攔截。
先讀原文段落,旁邊就是白話
這是一本英文書。左邊放原文、右邊放白話導讀——你既讀得懂,也順手碰了原文。
EJB 用 POJO 加註解開發 bean,容器負責交易、安全與生命週期,並支援相依注入與攔截。
Enterprise JavaBeans:受管的三層式元件
EJB 用 POJO 加註解開發 bean,容器負責交易、安全與生命週期,並支援相依注入與攔截。
深度探秘
EJB 是伺服器端、受管的元件架構
EJB 是什麼
Enterprise JavaBeans (EJB) 是 Java EE 的核心,是一種伺服器端、受管 (managed) 的元件架構。元件在 EJB 中叫 bean,負責承載應用(商業)邏輯,並把這層邏輯和後端資料庫的持久儲存分開——直接支援三層式架構。
『受管』指的是用上一節的容器模式:容器透過攔截,自動替 bean 注入交易、安全、生命週期等服務的呼叫(container-managed);開發者也可選擇自己掌控(bean-managed)。
EJB 3.0 支援兩種主要 bean:
- Session bean(會談 bean):實作某項任務,例如在 eShop 下單。可以是
- stateful:維持與單一客戶端的對話狀態(如購物進度);
- stateless:不維持狀態,能同時和多個客戶端對話。
- Message-driven bean(訊息驅動 bean):支援間接通訊,透過 JMS 的訊息佇列或主題以事件驅動方式互動。
EJB 是伺服器端受管元件架構,bean 承載商業邏輯,容器代管交易、安全與生命週期,直接支援三層式架構。
生活妙喻
POJO 加註解,像給普通員工別上識別證
POJO + 註解:簡單到不可思議
EJB 3.0 大幅簡化了開發:一個 bean 其實就是一個 POJO (Plain Old Java Object)——純粹用 Java 寫的應用邏輯,裡面沒有任何與『它是 bean』有關的程式碼。然後用註解 (annotations) 來告訴框架該怎麼對待它。
比喻:POJO 像一位普通員工,本身就會做事;註解就像別在他身上的識別證與職務標籤——『@Stateful:我是有狀態會談』『@Remote:我這個窗口對外開放』。識別證不改變他會做的事,只是讓大樓系統(容器)知道該給他什麼待遇。
@Stateful public class eShop implements Orders { ... }
@Stateless public class CalculatorBean implements Calculator { ... }
@MessageDriven public class SharePrice implements MessageListener { ... }
@Remote public interface Orders { ... } // 對外遠端介面
@Local public interface Calculator { ... } // 僅本地、較高效
business interface 等於上一節的『提供介面』;@Remote 需透過 RMI/JMS 等通訊中介軟體,@Local 則是更直接、更高效的綁定。
bean 是純 Java 的 POJO,加上註解就像別上識別證,讓容器知道該給它什麼樣的管理待遇。
實用超能力
交易、相依注入與攔截器,全靠註解
用註解駕馭容器
交易管理:先宣告由 bean 還是容器管理:
@TransactionManagement(BEAN):開發者自己用UserTransaction.begin()/commit()框出交易範圍。@TransactionManagement(CONTAINER)(預設):容器決定何時開始/結束,搭配方法上的劃界政策。
常見交易屬性(劃界政策):
| 屬性 | 政策 |
|---|---|
| REQUIRED | 有現存交易就加入,否則開新交易 |
| REQUIRES_NEW | 一律開新交易 |
| MANDATORY | 必須由現存客戶端交易呼叫,否則丟例外 |
| NEVER | 絕不能在交易中被呼叫,否則丟例外 |
相依注入 (dependency injection):用 @Resource 等註解標出依賴,容器在執行期(常用反射)把對的物件填進去——這正是元件『必要介面』在 EJB 的實現。
攔截 (interception):用 @AroundInvoke 在不改商業邏輯下插入橫切行為(如記錄、稽核);用 @PostConstruct/@PreDestroy(stateful 還有 @PostActivate/@PrePassivate)回應生命週期事件。
@Stateful public class eShop implements Orders {
@Resource javax.transaction.UserTransaction ut; // 相依注入
@TransactionAttribute(REQUIRED)
public void MakeOrder(...) { ... }
@AroundInvoke
public Object log(InvocationContext ctx) throws Exception {
System.out.println("called: " + ctx.getMethod().getName());
return ctx.proceed();
}
}
EJB 用註解統一駕馭交易劃界、相依注入與攔截,讓開發者專注商業邏輯、把分散式管理交給容器。
POJO 本身就會做事(應用邏輯),註解像識別證,不改變他做的事,只讓大樓系統(容器)知道該給他什麼待遇與管理。
元件用註解列出依賴,容器這個第三方負責在執行期把對的物件『送進來』填好,你不必自己去找與建立。
本節字彙
Fractal:輕量可重組的元件模型
Fractal 顯式表示軟體架構,提供 server 與 client 介面,刻意極簡、語言中立,常用來建構其他中介軟體。
深度探秘
刻意極簡的元件模型
Fractal 走的是另一條路
EJB 是重量級、處方式 (prescriptive) 的應用伺服器,強大但龐大,只適合三層式、高階伺服器的場景。Fractal 則是輕量級元件模型,刻意極簡:沒有完整容器模式、不內建部署,也沒有應用伺服器那種豐富程式模型。
Fractal 的特點:
- 顯式表示軟體架構:避免隱性相依問題(呼應上一章的元件精神)。
- 語言中立:定義的是程式模型,有 Java(Julia、AOKell)、C(Cecilia、Think)、.NET、Smalltalk、Python 等多種實作。
- 常被用來建構其他中介軟體,例如可設定的 OS 核心 Think、間接通訊平台 DREAM、Grid 平台 Proactive 等。
元件提供兩種介面:
- server 介面:接收進來的呼叫(等於『提供介面』)。
- client 介面:發出向外的呼叫(等於『必要介面』)。
Fractal 是輕量、語言中立、顯式描述架構的元件模型,刻意極簡,常用來打造其他中介軟體。
生活妙喻
膜與控制器,像細胞與它的細胞膜
細胞的比喻
Fractal 元件在實作上由兩部分組成,恰好像一個細胞:
- 內容 (content) = 細胞質:構成這個元件的子元件與它們之間的綁定(階層式組合,子元件本身還能再是複合元件)。
- 膜 (控制器構成的控制部分,定義元件的生命週期、反射、攔截等控制能力。">membrane) = 細胞膜:定義這個元件的控制能力,由一組控制器 (controllers) 組成。膜決定外界如何與內容互動,介面可分『膜內可見』與『對外可見』。
換掉膜上的控制器,就改變了元件的能力——這正是 Fractal『可重組』的關鍵。控制器的用途包括:
- 生命週期管理:如 LifeCycleController 的 startFc/stopFc/getFcState,可在執行期暫停、替換、再恢復。
- 反射/自省:透過 Component 與 ContentController 介面動態探查元件有哪些介面、複合結構長怎樣。
- 攔截:和 EJB 一樣可透明地記錄呼叫或實施存取控制。
flowchart TD A[膜 控制器集合] --> B[生命週期管理] A --> C[反射與自省] A --> D[攔截] A --> E[內容 子元件與綁定]
Fractal 元件像細胞:膜由控制器組成、掌管生命週期反射與攔截,內容是階層式的子元件與綁定。
實用超能力
綁定、ADL 與執行期重組
用綁定與 ADL 組裝,還能熱抽換
綁定 (bindings) 讓元件能組合,有兩種:
- primitive binding:同一位址空間內,一個 client 介面直接對映一個 server 介面(型別相容時),可用直接物件參照高效實作。
- composite binding:任意複雜的架構,實作跨機器多介面的通訊。例如要在 Fractal 裡實作一條 CORBA 連線,這個綁定本身會由 proxy、ORB core、物件配接器、skeleton、servant 等元件組成。複合綁定本身也是元件,所以系統完全可設定、也可在執行期重組。
Fractal ADL(架構描述語言,基於 XML)用來描述組合:
<definition name="cs.ClientServer">
<interface name="r" role="server" signature="java.lang.Runnable" />
<component name="caller" definition="hw.CallerImpl" />
<component name="callee" definition="hw.CalleeImpl" />
<binding client="this.r" server="caller.r" />
<binding client="caller.s" server="callee.s" />
</definition>
膜 vs. 容器:膜和容器都是元件的部署處、都支援隱式分散式管理(容器靠呼叫服務、膜靠控制器)。但膜更有彈性:反射可從黑箱到完整自省、非功能性支援可從『只做封裝』到『像應用伺服器般的交易與安全』,且全程可設定、可重組。正因如此,Fractal 被稱為開放元件模型 (open component model)。
熱抽換的實用情境:把 client-server 配置裡的舊伺服器換成支援多執行緒的新版——可先 suspend、替換 callee、再 resume,避免不一致。
Fractal 用 primitive/composite 綁定與 XML 的 ADL 組裝系統,膜比容器更有彈性,可在執行期安全熱抽換元件。
膜(控制器集合)像細胞膜,掌管元件對外的控制能力;內容像細胞質,是構成元件的子元件與綁定。換掉膜上的控制器就改變了能力。
先暫停受影響的部分、換上更強的元件、再恢復,就像舞台技術人員在表演進行中安全地替換設備,避免不一致。