how to connect codex app to remote ollama backend through caddy relay

如何讓 Codex App 連接公司架設的 LLM

從 Caddy 閘道安裝、遠端 Ollama API 金鑰,到 Codex 的 config.toml 組態設定,一次整理成可直接照做的流程。

前言

OpenAI 提供免費下載的 Codex 系列代理軟體(Agent Software),其中分成 文字命令列版 Codex CLI ,及桌機應用版 Codex App,或稱 Codex Desktop。 兩者皆可代理操作電腦,自動完成眾多工作。 Codex 預設使用 OpenAI 收費的大語言模型(LLM),但允許接到外部架設的語言模型。

由於外部語言模型眾多,最穩定的做法不是直接讓 Codex 連接遠端服務,而是先在本機安裝一個中介閘道(gateway)。此時,建議用免費的 Caddy 閘道軟體,將本機作為中繼站 (relay),把 Codex 送給 http://localhost:11434 的請求,對外轉送到一台支援 /v1/responses 請求的遠端 Ollama 伺服器。下面範例的 IP 與主機名稱已匿名化,請自行替換成公司實際值。

如果自己電腦夠強, Ollama 服務已經在本機 localhost:11434 運行且無需 API 金鑰,則請直接跳過 Caddy 相關步驟,設定讓 Codex 直接連本機即可。但如果打算用公司遠端 Ollama 服務,則 Caddy 可以將本地請求轉發到遠地服務端點,並在轉發時自動加入遠端的 API 金鑰。

整體思路很簡單:

  1. 若本機未安裝 Ollama 服務,先安裝 Caddy 中介閘道,設定 Caddyfile 組態檔,於本機 11434 埠號,轉送請求到遠端 Ollama。
  2. 準備 Caddy 啟動腳本,方便啟動閘道。
  3. 安裝 Codex App,設定 ~/.codex/config.toml 組態檔,設定本機閘道位置與採用模型。

1a. 安裝 Caddy 閘道服務 🔧

Caddy 支援 Windows、macOS 與 Linux 三種平台。以下分別說明:

Windows

使用 Chocolatey 安裝,或手動下載官方安裝檔:

# 使用 Chocolatey(需管理員權限)
> choco install caddy

# 或手動下載:https://caddyserver.com/download?platform=windows&plugins=reverse_proxy

macOS

使用 Homebrew 安裝:

> brew install caddy

Linux(Ubuntu / Debian)

使用官方腳本一鍵安裝:含更新套件,安裝倉庫金鑰,從倉庫下載 caddy

> sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
> curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
> curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
> sudo apt update
> sudo apt install caddy

安裝完成後,所有平台都可以用以下指令確認版本:

> caddy version

1b. 建立 Caddy 組態檔

假設公司 Ollama 伺服器提供 OpenAI 相容服務,並且需要 API 金鑰。則可建立如下 Caddyfile 組態檔:其中,reverse_proxy 標示多對一的反向代理區塊。

:11434 {
  bind 127.0.0.1 [::1]
  reverse_proxy <REMOTE_IP>:443 {
    header_up Host <REMOTE_HOST>
    header_up Authorization "Bearer YOUR_API_KEY"
    header_up X-Forwarded-Proto https
    transport http {
      tls
      tls_insecure_skip_verify
    }
  }
}
這裡的重點是:localhost:11434 代表 Codex 只要連接本機埠號即可,真正的遠端網址與認證都由 Caddy 處理。

其中,REMOTE_IP 填入公司 Ollama 伺服器 IP,REMOTE_HOST 填入公司 Ollama 伺服器域名。若反向代理或TLS握手出問題,header_up Host <REMOTE_HOST> 亦可不寫死,改成 header_up Host {upstream_hostport}。若伺服器使用自簽憑證,加入 tls_insecure_skip_verify 也能避免 TLS 驗證失敗。

2. 準備 Caddy 啟動腳本 🚀

如下以 macOS 及 Linux 平台常見的 bash 腳本語言為例。此腳本利用 caddy 指令,依照 caddyfile 格式,讀入 ./Caddyfile 組態檔,執行閘道轉送工作。至於Windows 用戶,則可直接在 PowerShell 執行 caddy start --config ./Caddyfile,以自動在背景執行。

#!/usr/bin/env bash
set -euo pipefail

nohup caddy run --config ./Caddyfile --adapter caddyfile > /tmp/caddy.log 2>&1 &
如果 Caddy 已經有 autosave.json 或舊設定干擾,記得先清掉,否則 Caddy 可能載入到舊的 host match,導致看起來像「有啟動但沒回應」。

3a. 安裝 Codex App

安裝 Codex App 後,先確認是否正常啟動。

然後,目標是讓 Codex 透過組態檔,使用本地閘道,選用語言模型。

3b. 編輯 ~/.codex/config.toml 組態檔 🧩

如下設定範例把模型提供者指向本機的 Caddy 閘道,並讓 Codex 走 /v1 這種 OpenAI 相容 API 格式。

model = "qwen3.6:35b-a3b-q8_0"
model_provider = "my-caddy-gateway"

[model_providers.my-caddy-gateway]
  name = "Custom Gateway"
  base_url = "http://localhost:11434/v1"
  api_key = "not-empty"
  wire_api = "responses"
  openai_compatibility_fallback = true

model

指定要使用的模型名稱,必須跟遠端 Ollama 服務支援的模型一致。

base_url

指向本機的 Caddy 閘道位置,而不是直接連接遠端伺服器。

wire_api

如果遠端 Ollama 服務支援 /v1/responses,這個設定通常最合適。

4. 驗證連線 ✅

完成設定後,可以先用 curl 網頁請求工具,測試本機 11434 埠號是否通到遠端 LLM:

curl -s http://localhost:11434/v1/models
curl -s http://localhost:11434/v1/responses \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer not-empty" \
  -d '{
    "model": "qwen3.6:35b-a3b-q8_0",
    "input": "Say hello in one word",
    "stream": false
  }'

如果看到回應裡面有 Via: 1.1 Caddy,而且回傳的模型名稱正確,就代表本機中繼服務 與遠端 Ollama 已經接起來了。

5. 啟動 Codex App

最後一步就是啟動 Codex App,選用剛剛設定好的組態檔:

codex app ~/Documents/Codex

剛才的啟動腳本,也可以在啟動 Caddy 閘道後,再開啟 Codex App,帶上初始工作目錄。只要 http://localhost:11434/v1 能正常回應,Codex 就會透過這個埠號 11434,使用遠端模型。

常見問題

Q:為什麼 Codex 明明有連上,但回應看起來像空的?

A:有些模型會把內容放在 reasoning 或其他欄位,而不是 content。另外如果 max_tokens 太小,也可能只拿到半截輸出。

Q:如果 Caddy 起來了但沒有轉發怎麼辦?

A:先檢查 autosave.json 是否有舊設定,再確認 Host、TLS 與 金鑰正確否。

注意事項

Ollama 實作的 /v1/responses 介面僅支援無狀態(non-stateful)請求。以下欄位雖然可以傳入以維持相容性,但 Ollama 伺服器並不會實際使用這些欄位,來維護對話狀態:

  • previous_response_id — 不會用於追蹤歷史回應。
  • conversation — 不會用於維護伺服器端的對話上下文。

這表示每次請求都是獨立的。如果需要多輪對話,必須在客戶端(如 Codex App)自行管理對話歷史,並將完整的訊息列表傳給 Ollama。

結語

把 Codex App 接到本地 LLM 的核心,其實就是「閘道」建置,與「組態」設定。Caddy 閘道負責把流量轉到遠端 Ollama,Codex 只要知道本機的 base_url 閘道位置即可。這樣做的好處是設定清楚、可除錯,也比較容易切換不同後台及語言模型。

a comparison table about disk scheduling algorithms

🚀 四種硬碟排班演算法 (SCAN, LOOK, C-SCAN, C-LOOK) 對比

在作業系統中,硬碟排班 (Disk Scheduling) 的目標是減少磁頭移動的總距離,以縮短磁軌請求的總服務時間。其中,面對 SCAN、LOOK 及其循環變體 C-SCAN 與 C-LOOK,許多初學者常感到混淆。本文透過「服務方向」與「回頭策略」兩個維度,將其整理為 2x2 表格,幫助大家一目瞭然。📖

🎯 回頭策略
(磁頭移動方向的回轉時機)
🏁 軌道兩端終點
(遇磁軌編號 0 或 Max)
👀 目前方向最後請求
(Look-ahead)
🛠️ 服務方向 ↔️ 雙向服務
(上下行方向皆提供服務)

🏗️ SCAN

運作: 磁頭來回服務。不論有無請求,皆須到達磁碟實體邊緣才轉向。

※ 特色:每層都巡,且坐到底。

🔎 LOOK

運作: 磁頭來回服務。若目前方向前方已無請求,即刻轉向。

※ 特色:上方沒人按就不會坐到頂。又稱電梯演算法。
🔄 單向循環
(限定上行或下行才提供服務)

🎡 C-SCAN

運作: 僅單向服務。到達物理端點後立即跳回起點,跳轉期間不服務。

※ 特色: 確保所有軌道請求的等待時間均勻一致。

✨ C-LOOK

運作: 僅單向服務。服務完最後請求後,即跳回另一端首個請求。

※ 特色:結合公平性與高效率,實務最推薦。

📊 小結

  • 回頭策略差異: 端點模式 (SCAN 系列) 邏輯簡單,雖有空跑現象,但適合重負載均勻分布情形;最後請求模式 (LOOK 系列) 效率較高。
  • 服務方向差異: 雙向服務會出現中間磁軌的掃描頻率高於兩端;單向循環 (C 系列) 則提供了最佳的「公平性」。

When to use Java String, StringBuffer, or StringBuilder?

在 Java 開發中,處理文字字串是最常見的操作之一。然而,許多初學者仍容易混淆 StringStringBufferStringBuilder 的使用時機。

選擇錯誤的工具不僅會影響程式的執行效率(Performance),在多執行緒環境下更可能導致難以追蹤的 Bug。為了幫助大家徹底釐清這三者的差異,以下整理了一份綜合比較表,從底層架構到實際應用一目了然。

比較特性 String StringBuffer StringBuilder
可變性 (Mutability) 不可變 (Immutable) 可變 (Mutable) 可變 (Mutable)
執行緒安全 安全 安全 (synchronized) 不安全
效能速度 最慢 居中 最快
修改方式 產生新物件 原地修改 (Buffer) 原地修改 (Buffer)
主要操作方法 concat(), split(), substring(), + append(), insert(), delete(), reverse() append(), insert(), delete(), reverse()
方法傳回值 傳回新 String 傳回 this 傳回 this
儲存位置 字串池 / Heap Heap Heap
引入版本 Java 1.0 Java 1.0 Java 1.5

總結與建議

總結來說,這三者的選擇邏輯非常清晰:

  • 如果處理的是少量數據,或者字串內容不會改變,請直接使用 String
  • 如果須進行大量字串拼接(例如迴圈中組裝 SQL 或 JSON),且僅在單一執行緒中運行,StringBuilder 是最佳選擇。
  • 只有在明確需要多執行緒共享同一個可變字串時,才考慮使用 StringBuffer

掌握以上細節,不僅能讓程式碼跑得更快,也能展現 Java 記憶體管理與並行控制的專業理解。希望這份表格對您的開發工作有所幫助!

quick ways to initialize a list of numbers or strings in java

Java 快速建立整數與字串清單的寫法

在 Java 中,建立 List<Integer>List<String> 是非常常見的需求。 以下依照不同 JDK 版本,整理出幾種快速建立可修改(modifiable) 清單的寫法,可依個人習慣彈性使用。

1️⃣ JDK 5(2004)起支援的寫法

自 JDK 5 起,Java 引入了 泛型(Generics)自動封裝(Autoboxing),因此可以直接將 int 自動轉為 Integer


  Integer[] intArray = {1, 2, 3};
  String[]  strArray = {"A", "B", "C"};

  List<Integer> ints =
    new ArrayList<Integer>(Arrays.asList(intArray));

  List<String> strings =
    new ArrayList<String>(Arrays.asList(strArray));
  
✅ 適用於舊版系統(JDK 5 / 6)
✅ 清楚明確,但型別宣告較冗長

2️⃣ JDK 7(2011)起支援的寫法(鑽石運算子)

JDK 7 引入了 鑽石運算子(<>, 可讓編譯器自動推斷泛型型別,使程式碼更精簡。


  List<Integer> ints =
    new ArrayList<>(Arrays.asList(1, 2, 3));

  List<String> strings =
    new ArrayList<>(Arrays.asList("A", "B"));
  
✅ 語法更簡潔
✅ 仍然回傳可修改的 ArrayList

3️⃣ Java 9(2017)起支援的寫法(搭配 List.of

Java 9 引入了 List.of() 作為集合工廠方法, 可快速建立不可修改的清單。 若實務上仍需要可修改的清單,可再包裝成 ArrayList


  List<Integer> ints =
    new ArrayList<>(List.of(1, 2, 3));

  List<String> strings =
    new ArrayList<>(List.of("A", "B"));
  
✅ 語意清楚、現代化寫法
⚠️ List.of() 本身不可修改,需額外包裝

✅ 小結

  • JDK 5+:可使用泛型與自動封裝
  • JDK 7+:可使用鑽石運算子,讓程式碼更乾淨
  • Java 9+:可搭配 List.of() 撰寫更語意化的程式碼

實務上建議:
👉 需要可修改清單 ⇒ 使用 new ArrayList<>(...)
👉 元素固定不變 ⇒ 直接使用 List.of()

compare java wait/notify synchronization with lock or condition in java.util.concurrent (juc)

為什麼 Java 的 wait/notify 逐漸被新 API 取代?

在 Java 的演進過程中,雖然 Object.wait()Object.notify() 仍存在於核心庫中,但在現代開發場景,java.util.concurrent (JUC) 提供的 LockCondition 已成為主流。以下是三大核心取代原因:

1. 靈活性與精確控制 (Multiple Conditions)

這是最關鍵的進步。舊有機制中,每個物件僅有一個「等待集」。

  • 舊機制: 生產者與消費者共用同一個等待隊列。呼叫 notify() 無法指定喚醒誰,常被迫改用 notifyAll(),導致驚群效應 (Thundering Herd)
  • 新機制 (Condition): 一個鎖可建立多個條件物件(如 notFull, notEmpty)。生產者只需喚醒消費者,反之亦然,效能大幅提升。

2. 安全性與健壯性 (Robustness)

新 API 解決了許多語法上的僵化問題:

特性 Object.wait/notify JUC Condition
鎖的依賴 強烈耦合 synchronized 配合 ReentrantLock,更靈活
中斷響應 僅支援基本中斷 支援不可中斷的等待 (awaitUninterruptibly)
超時控制 毫秒級 精確至奈米級 (nanoseconds)

3. 功能擴展性 (Advanced Features)

ReentrantLock 提供了舊機制無法達成的進階功能:

公平性 (Fairness): 新 API 支援公平鎖設定,確保等待最久的執行緒優先獲取資源。
非阻塞嘗試: 透過 tryLock(),執行緒可以在拿不到鎖時立即返回,避免死等。
總結:除非是極簡的同步邏輯,否則在現代 Java 開發中,ReentrantLock + Condition 是更安全、高效的選擇。

choose which S3 object ownership?

S3 Object Ownership 兩種模式差異

Amazon S3 服務全名為 簡單儲存服務 (Simple Storage Service),可支援存取放置於AWS公有雲的檔案,其服務類似 Google Drive 或 Microsoft OneDrive 的網碟功能。S3 儲存容器稱為水桶,儲存的檔案稱為物件。

建立S3水桶時,須決定上傳到水桶的物件,其擁有者將自動歸屬於誰,稱為物件擁有權(Object Ownership)設定。只有物件的擁有者才能設定物件的存取權。這裡簡單介紹兩種設定模式的差異:

🔒 模式一:ACL disabled(recommended)

所有物件都由 Bucket 擁有者持有,不論是誰上傳。ACL 完全被停用,不能再用 ACL 來授權。存取控制只能透過 IAM Policy 或 Bucket Policy 來設定。

優點:集中管理,避免跨帳號物件所有權混亂。

最佳實務:AWS 建議使用這種模式,因為更安全、更容易維護。

🔑 模式二:ACLs enabled

物件可能由上傳者帳號持有,而不是 Bucket 擁有者。存取控制可以透過 ACL(物件或 Bucket 層級)來設定。Bucket Policy 仍然可以使用,但 ACL 會一起參與判斷。

用途:適合跨帳號上傳的特殊情境,例如合作夥伴需要保有自己上傳檔案的所有權。

缺點:管理較複雜,容易造成權限混亂。

⚡ 簡單比較
模式	     物件擁有者	                存取控制方式	      AWS 建議
ACL Disabled Bucket 擁有者	        Policy(IAM / Bucket)✅ 建議
ACL Enabled  上傳者帳號可能成為物件擁有者  ACL + Policy	      僅限特殊需求

✍️ 結語

如果你的環境需要簡單、集中、安全的存取管理,選擇 ACL Disabled(Bucket owner enforced) 是最佳做法。 只有在跨帳號物件所有權的特殊需求下,才需要考慮 ACL Enabled

註: S3 權限管理機制比較:ACL vs. 水桶政策

功能特性 S3 ACL (存取控制清單),舊版 水桶政策 (Bucket Policy),新版
物件所有權 預設由「上傳者」擁有物件,即使上傳者不是水桶擁有者。 由「水桶擁有者」自動擁有水桶內的所有物件。
設定格式 XML 格式。 JSON 格式。
控制粒度 可針對「單一物件」進行微調。 針對整個水桶或特定「前綴 (Prefix/資料夾)」進行管理。
權限複雜度 僅限基本權限(讀取、寫入、完全控制),不支援「拒絕 (Deny)」。 支援複雜條件(IP 限制、MFA、特定時間、明確拒絕等)。
稽核便利性 低。必須逐一檢查每個物件的 ACL 才能得知誰有存取權。 高。只需查看一個 JSON 文件即可瞭解全域權限。
AWS 建議實務 不建議使用(除了特定日誌寫入需求)。 強烈建議使用(最佳實務)。

how to deal with metric scale inconsistency in topn recommendation evaluation

🎯 推薦系統一般會回傳前 N 個排名的物品清單給用戶,稱為 Top‑N 推薦。 遇到推薦模型須要訓練及評估時,習慣先蒐集用戶與物品的互動資料,再將資料拆分成沒有重疊的訓練集及測試集。 模型在訓練時只看得到訓練集,評估時則拿測試集作為驗證的標準答案,以免作弊。

通常,在評估推薦模型時,和一般機器學習的模型評估不同,會採用逐用戶等比例切割 (userwise stratified split) 評估法。 即針對每個用戶的喜好物品,等比例拆分成訓練集及測試集,請模型利用全體訓練集作出該用戶的 Top‑N 推薦,然後利用其個人測試集作核對,計算該用戶的得分指標。 因為模型未看過測試集,所以最後全體用戶的指標平均值就是模型的客觀表現,可供比較之用。

上述作法在實際計算得分指標時,常遇到測試集數量和推薦數量 N 不匹配,造成得分指標尺規不一致的困擾。以下進行問題,原理,及解法說明。

❓ 問題介紹

逐用戶等比例切割 的設定下,每位用戶的測試集互動數量不一樣。 當使用固定的 Top‑N 推薦清單做評估時,就會出現「測試集大小與 Top‑N 回傳數量不匹配」的情況:

  • 👤 輕度互動的用戶:測試集互動數 |Rᵤ| < N
  • 👥 重度互動的用戶:測試集互動數 |Rᵤ| > N

這會讓 回傳數量 N 的精確率 Precision@N,及回傳數量 N 的召回率 Recall@N 產生尺規上限受限的現象, 影響不同用戶之間評比的尺規公平性。

🔍 原理說明(為什麼會不公平)

若使用 Top‑N 評估,指標的分母固定或依用戶而變:

  • Precision@N = 命中數 / N,其分母固定為 N, 輕度互動用戶的精確率最大值會受此尺規「天花板」限制,達不到 1.0。
  • Recall@N = 命中數 / |Rᵤ|,其分母為 |Rᵤ|, 重度互動用戶的召回率最大值會受此尺規「天花板」限制,達不到 1.0。

這並非「計算錯誤」,而是 Top‑N 回傳量的本質所導致的評估上限不一致。

相比之下,✨正規化打折累積增益 NDCG@N 指標會以用戶理想排序的 理想打折累積增益 IDCG 進行正規化, 而 IDCG 的長度是 min(|Rᵤ|, N)。因此每位用戶都能達到 1.0 的尺規滿分, 不會因為測試集大小不同而被系統性壓低。

💡 實務解法

以下是最常見的三種解決做法:

  1. ✅ 接受個別用戶的指標尺規上限不同:只要所有模型都在相同的尺規限制下比較,排名仍有參考價值。 因為各模型可在相同受限指標尺規範圍內求好表現,所以不影響依據指標平均值所作的模型排名比較。
  2. 📋 粗糙解法:改用留1筆 Leave‑One‑Out(LOO)分割法,每位用戶只保留一筆測試互動,再搭配 命中率 Hit Rate (HR@N) = 命中數 指標作評估, 其值為 0 或 1。
  3. ⭐ 精細解法:改以 NDCG@N 作為主指標,並輔以 Precision/Recall/HR, 讓輕重度互動的用戶都能公平比較。

start versus restart for docker containers

Docker start vs restart:快速掌握差異

Docker 是個方便的服務部署工具,可快速安裝執行伺服軟體,供瀏覽器等前台使用。當用戶從 docker 映像檔建立 (create) 及啟動 (start) 容器時,容器將分別處於 巳建立 (created)執行 (running) 狀態。然後,遇到執行意外出錯 或 管理者故意下停止指令 (docker stop) 時,容器將進入退出 (exited) 狀態。

處於退出狀態的容器,因行程退出,將喪失記憶體狀態,但仍保有原來的可寫層 (writable layer) ,即儲存體狀態。此時,兩種指令 docker start 與 docker restart 都能讓既有容器,從現有可寫層狀態,再跑起來,效果完全一樣。以下用表格整理說明兩指令適用時機之差異。

1. 指令行為比較

指令 行為說明 適用狀態
docker start 啟動 巳建立 (created)退出 (exited) 的既有容器,直接執行其進入點命令 (entrypoint command) created / exited
docker restart 同上,但遇到執行狀態的容器會先讓其 stop,再 start created / running / exited

2. 執行期影響比較

項目 start restart
送 SIGTERM / SIGKILL
PID 改變
記憶體狀態重置
中斷連線
停機時間短暫

3. docker restart 的清理範圍

區域 是否清理
In-memory data
Processes / threads
Open sockets
TCP connections
Writable filesystem
Volumes
Env / config
Image

4. 結論:適用性與效果

docker startdocker restart 兩個指令目的都在不放棄既有容器的可寫層之下,以新記憶體,執行容器的服務。在容器處於 退出 狀態時,兩者效果完全相同,都會重新啟動容器主行程,重新分配記憶體,並執行 entrypoint command。 其適用時機摘要如下表。

適用情境 效果
容器正常退出,要再跑一次docker start
就現有可寫層,啟動既有容器
程式卡住、連線異常docker restart
保留可寫層,重新啟動既有容器
組態設定或資料壞掉docker run
放棄可寫層,重新建立啟動新容器

Linked Lists from C to Java

C Pointer Concepts in Java」一文提到 Java 沒有指標型別 (pointer type) ,但有參照型別 (reference type) 的設計。在遇到須要處理鏈結清單 (linked list)、圖形 (graph) 等資料結構時,Java 如何透過參照型別,仍能達成類似的效果。本文將以「鏈結清單」為例,分別用 C 與 Java 實作,說明兩者的差異與對應。


🔹 C 語言:使用指標建立鏈結清單

/*
   1 -> 2 -> 3 NULL
*/
#include <stdio.h>
#include <stdlib.h>

// 定義節點結構
typedef struct Node {
    int data;  // 資料
    struct Node* next;  // 下一節點指標
} Node;

int main() {
    // 建立三個節點
    Node* head = (Node*)malloc(sizeof(Node));
    Node* second = (Node*)malloc(sizeof(Node));
    Node* third = (Node*)malloc(sizeof(Node));

    // 給值與連結
    head->data = 1;
    head->next = second;

    second->data = 2;
    second->next = third;

    third->data = 3;
    third->next = NULL;

    // 印出鏈結清單
    Node* current = head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");

    return 0;
}
    其中,
  • Node* 是指標變數,指向記憶體中的節點。
  • 使用 malloc 配置記憶體,並用 -> 操作指標指向的內容。
  • 每個節點透過 next 指向下一個節點,形成鏈結。

🔹 Java 語言:使用參照建立鏈結清單

/*
   1 -> 2 -> 3 null
*/

// 定義節點結構
class Node {
    int data; // 資料
    Node next;  // 下一節點參照

    Node(int data) {
        this.data = data;
        this.next = null;
    }
}

public class LinkedListDemo {
    public static void main(String[] args) {
        // 建立三個節點
        Node head = new Node(1);
        Node second = new Node(2);
        Node third = new Node(3);

        // 給值與連結
        head.next = second;
        second.next = third;

        // 印出鏈結清單
        Node current = head;
        while (current != null) {
            System.out.print(current.data + " -> ");
            current = current.next;
        }
        System.out.println("null");
    }
}
    其中,
  • Node 是一個類別,變數如 headsecond 是參照變數,指向物件。
  • 使用 new 建立物件,並透過 . 操作物件的屬性。
  • 雖然語法上沒有 *&,但物件的參照本質上就像 C 的指標。

🔄 對照總結:C 指標 vs Java 參照

概念C 語言Java 語言
記憶體操作明確使用 *&隱含於物件參照
記憶體配置mallocnew
節點連結指標指向下一節點物件參照指向下一節點
安全性容易出現記憶體錯誤自動垃圾回收記憶體管理 (GC)

以上說明 Java 雖然沒有指標型別,但仍能透過「參照型別」的設計,實現如鏈結清單這類需要動態記憶體與節點連結的資料結構。希望對於從 C 過渡到 Java 的學習者有幫助。

Installing Code::Blocks on Windows for APCS Preparation

APCS 為大學程式設計先修檢測 (Advanced Placement Computer Science),其檢測等級可供大學個人申請入學之用。各電腦相關科系APCS組都明訂要求的檢測等級,須符合其門檻才具備申請資格。其中,針對 C 及 C++ 程式檢測,考場提供 Linux 環境的輕量級 Code::Blocks 整合開發環境 (IDE) 供程式撰寫及執行之用。

考生若想在家裏 Windows 作業系統上熟悉 Code::Blocks,一種方法是下載考場 Linux 環境 .iso 檔,安裝 VirtualBox 虛擬機軟體,將 Linux 虛擬機跑起來,使用其桌面安裝好的Code::Blocks。另一種方法則是直接在 Windows 自行安裝 Code::Blocks。

本文將介紹後者,如何在 Windows 系統上安裝 Code::Blocks,並參考考場 Linux 虛擬機中的 Code::Blocks 進行設定,以獲得和考場 Linux 環境一樣的 Code::Blocks 寫程式體驗。


📥 Windows安裝 Code::Blocks

 🆚不推薦新版本:Code::Blocks 25.03 (含 MinGW 編譯器)

  • 下載連結:https://www.codeblocks.org/downloads/binaries/
    •   Binary releases
    •     Windows XP / Vista / 7 / 8.x /10
    •       Microsoft Windows (64 bit, default)
    • codeblocks-25.03mingw-setup.exe
  • 適用系統:Windows XP / Vista / 7 / 8.x / 10(64 位元)

     本版本內建 MinGW 編譯器,安裝後即可開始編寫與編譯 C/C++ 程式。但因為安裝時易受某些防毒軟體拒絕,若心有疑慮,不想關閉防毒軟體,建議不要安裝。

 ✅ 推薦舊版:Code::Blocks 20.03  (含 MinGW 編譯器)

      本版本也內建 MinGW 編譯器,安裝後即可開始編寫與編譯 C/C++ 程式。雖然是舊版,但目前為考場安裝版本,且不會受防毒軟體拒絕,故建議安裝。

⚙️ 設定編譯器支援 C++17 / C17

以下依據考場 Linux 虛擬機安裝組態,設定Windows Code::Blocks 20.03 的編譯器選項:

1️⃣ 開啟 Code::Blocks,進入:

Settings → Compiler...

2️⃣ 在「Global compiler settings」中,確認選擇的編譯器為:

Selected compiler: GNU GCC Compiler (default)

3️⃣ 點選「Compiler settings」→「Compiler Flags」,勾選以下選項,點選OK:

  • ✅  Have g++ follow the C++17 ISO C++ language standard [-std=c++17]
  • Have gcc follow the 2017 ISO C language standard [-std=c17]
  • Static linking [-static]
註: 以下為考場提供 .iso 檔安裝的 Linux Code::Blocks 20.03 組態
  • Have gcc follow the 2017 ISO C language standard [-std=c17]
  • Have gcc follow the C++17 ISO C++ language standard [-std=c++17]
  • Static linking [-static]


Building a Lightweight Streamlit Client for Local Ollama LLM Interaction

Ollama 提供端點串接服務,可由程式管理及使用本地大語言模型(LLM, Large Language Model)。 以下程式碼展示如何以 Streamlit 套件,建立一個輕量級的網頁介面,供呼叫本地端安裝的 Ollama 大語言模型。 Ollama 預設的服務端點為 http://localhost:11434。 使用者透過簡單的網頁操作,即可輸入提示詞(prompt),即時獲得模型回應。 此應用支援模型選擇、選字機率均化溫度(temperature)、選字累加機率門檻值(top_p)、問答最大符號數(max_tokens)等參數調整,並允許限定特定使用者(如 "john")。 Ollama Streamlit為一個實用的範例,適合想要快速整合本地 LLM ,建立互動式介面的開發者。

######################################
#  ollama_client_streamlit.py
#   an streamlit client to call ollama LLMs.
#
#   # python 3.7.7 above recommended
#   > conda activate your_streamlit_environment
#   > streamlit run ollama_client_streamlit.py --server.port 8080
#
#   http://localhost:8080?name=john
#
# Reference:
#   GitHub: Ollama API Documentation
#
import streamlit as st
import requests
import time
import json

###################################
# Parameters for the Streamlit App
baseurl = 'http://localhost:11434'
generate_url = baseurl + '/api/generate'
tags_url = baseurl + '/api/tags'
# only names in pass_list are allowed to use LLM
pass_name_list = ["john"]   
default_prompt = 'Where is the capital of Japan?'
# api_key is optional for endpoints with bearer authentication
api_key = ''

# returns a list of llm names installed in ollama
def get_ollama_models():
    """Fetches available models from the Ollama API and returns a list."""
    ##url = "http://localhost:11434/api/tags"  # Ollama API endpoint
    try:
        headers={'Authorization': f'Bearer {api_key}'}
        response = requests.get(tags_url, headers=headers)
        
        # Raises an error for HTTP failures
        response.raise_for_status()
        data = response.json()

        # Extract model names
        return [model["name"] for model in data.get("models", [])]  
    except requests.exceptions.RequestException as e:
        print(f"Error fetching models: {e}")
        return []


# Calls /api/generate to request LLM in stream=true mode
#   model: ollama installed llm
#   temperature: 0 ~ 1 (decide how creative the answer is generated)
#   top_p: 0 ~ 1 (decide how many most probable words to select)
#   max_tokens: 0 ~ 2048 tokens
def generate_stream(prompt, model, temperature=0.7, top_p=0.95, max_tokens=1024):
    args = {
        'prompt': prompt,
        'model': model,
        'options': {
            'temperature': temperature,
            'top_p': top_p,
            'max_tokens': max_tokens},
        'stream': True
    }

    headers = {'Authorization': f'Bearer {api_key}'}
    r = requests.post(generate_url, headers=headers, json=args, stream=True)
    if r.status_code == 200:
    # {"model":"gemma3:12b-it-qat",
    #  "created_at":"2025-05-24T09:18:43.944815912Z",
    #  "response":"The","done":false}
        # print("Request successful!")
        # with requests.post(url, headers=headers, 
        #    data=json.dumps(data), stream=True) as response:
        for line in r.iter_lines():
            if line:
                try:
                    chunk = json.loads(line.decode("utf-8"))
                    message = chunk.get("response", {})
                    # print(message)
                    yield message
                except json.JSONDecodeError:
                    continue

        # message = r.json()['response']
    else:
        message = f"Error: {r.status_code}, Message: {r.text}"

    # response_json = response.json()
    # message = response_json['response']

    yield message


#  no cache resource is needed with ollama endpoints
@st.cache_resource
def _load():
    return

#  calls generate_stream() to collect stream chunk response 
#    in _result.markdown(full_response)
@st.cache_data(ttl='1d')  # cache _result for one day
def _generate(prompt, model, temperature=0.7, top_p=0.95, max_tokens=1024):
    _result = st.empty()
    full_response = ''

    start = time.time()
    for chunk in generate_stream(prompt, model, temperature, top_p, max_tokens):
        full_response += chunk
        _result.markdown(full_response)
        # st.write_stream(response)
    end = time.time()

    st.write(f"generate text in {end - start:.3f} seconds\n\n")
    # return response_placeholder


start = time.time()
_load()
end = time.time()

#############
# filter user
# only names in pass_name_list are allowed to use the app
params = st.query_params
name = params.get("name", "")
if name in pass_name_list:
    result = f"Hello, {name}!"
else:
    #result = "Unsupported user"
    st.write(f"Sorry, you are not supported user.")
    exit(0)

###############
# Streamlit UI
st.sidebar.write(f"Hello, {name}!")

# title for the web page
st.title("Ollama LLM Demo ")

# let sliders be located in left hand side of ui
# slider for temperature
temperature = st.sidebar.slider("temperature", 0.0, 1.0, 0.7, 0.01)

# slider for top_p
top_p = st.sidebar.slider("top_p", 0.0, 1.0, 0.95, 0.01)

# slider for max_tokens
max_tokens = st.sidebar.slider("max_tokens", 0, 2048, 1024, 1)

# selectbox for model_engine
models = get_ollama_models()
#print(models)  # ["llama3.1", "gemma3:12b-it-qat"]
model_engine = st.sidebar.selectbox("model_engine", models)

# input text
prompt = st.text_area("Your Query for LLM", default_prompt)

# output area
_generate(prompt, model_engine, temperature, top_p, max_tokens)

how to install and use Jeddict AI Assistant on NetBeans IDE 23+

Jeddict AI Assistant 為適用於 NetBeans IDE 23 以上版本的插件,能夠連接大語言模型,協助編寫程式碼,大幅提升開發體驗,讓程式撰寫更輕鬆、更智慧。以下簡介其安裝方法及功能。

A.安裝與解除安裝

  • 安裝步驟:
    • 開啟 NetBeans IDE
    • 點選 Tools/Plugins/Available Plugins
    • 搜尋並安裝 Jeddict AI Assistant
    • 點選 Tools/Options/Jeddict AI Assistant
      Providers: OLLAMA,
      URL: http://localhost:11434,
      Model: qwen2.5-coder:7b
  • 解除安裝步驟:
    • 點選 Tools/Plugins/Installed
    • User Installed Plugins 下,選擇 Jeddict AI Assistant
    • 點擊解除作用 (Deactivate)解除安裝 (Uninstall)

B.主要功能與使用說明

Jeddict AI Assistant 提供了多種智慧功能,幫助提升程式碼品質與開發效率:

  1. 智慧程式碼補全

    (Smart Code Completion)
    • 用法: 在編輯程式碼時,按下 Ctrl+SpaceCtrl+Alt+Space 鍵,AI 會提供相關的程式碼建議。按下 Enter 鍵即可接受建議。
    • 說明: AI 會學習程式碼上下文,提供更精準的補全、變數和方法重命名,以及改進日誌訊息或註解的建議,大幅提高編碼效率。
  2. 行內提示

    (Inline Hint)
    • 用法: 在編輯程式碼行時,按下 Enter 鍵,AI Assistant 會直接在程式碼編輯器中顯示基於當前上下文的行內提示。再次按下 Enter 鍵即可確認並整合建議,或按下其他任意鍵取消。
    • 說明: 提供即時、上下文感知的建議,有效提升程式碼品質和效率,且不干擾開發流程。
  3. 為行內提示提供上下文

    (Providing Context to Inline Hint)
    • 用法:
      • 使用註解作為提示: 在程式碼中撰寫註解說明程式意圖,然後按 Enter 鍵。AI 會將註解和現有程式碼作為上下文。
      • 使用儲存的提示: 在設定中定義長提示,然後在編輯器中輸入 /prompt-id 並按 Enter 鍵來重複使用。
      • 即時擴展儲存的提示: 輸入 /{prompt-id} 加上額外上下文,AI 會同時處理儲存的提示和額外文字。
    • 說明: 可更精確地控制 AI 的建議,使其更符合當前的開發任務。
  4. 方法強化

    (Method Enhancement)
    • 用法: 反白顯示任何方法,然後點擊提示:"AI: Enhance the method"。
    • 說明: AI 會根據專案需求,改進方法的邏輯、結構或效率。
  5. Javadoc 創建與改進

    (Javadoc Creation & Improvement)
    • Javadoc 創建用法: 反白顯示任何類別、方法或介面,然後點擊提示:"AI: Create Javadoc using assistance"。
    • Javadoc 改進用法: 反白顯示任何類別、方法或介面中現有的 Javadoc 註解,然後點擊提示:"AI: Enhance existing Javadoc"。
    • 說明: AI 會自動生成符合程式碼上下文的 Javadoc,並提升其清晰度、準確性和符合最佳實踐。
  6. 變數命名建議

    (Variable Naming Suggestions)
    • 用法: 反白顯示任何變數或參數,然後點擊提示:"AI: Improve variable name using assistance"。
    • 說明: 自動建議更有意義且符合上下文的變數名稱,提升程式碼的可讀性和可維護性。
  7. 語法修正

    (Grammar Fixes)
    • 用法: 選擇任何字串文字 (例如註解、Javadoc 或其他文本),然後點擊提示:"AI: Fix grammar issues using assistance"。
    • 說明: 檢測並修正程式碼中註解、Javadoc 或其他文本內容的語法錯誤,確保文件專業且潤飾精良。
  8. 文本強化

    (Text Enhancement)
    • 用法: 選擇任何字串文字 (例如註解、文件或日誌訊息),然後點擊提示:"AI: Enhance text quality using assistance"。
    • 說明: 透過提供更簡潔、更有影響力的替代方案,提升文本內容的品質和可讀性。
  9. 程式碼行強化 (Code Line Enhancement)

    • 用法: 選擇任何單行程式碼片段,然後點擊提示:"AI: Enhance a single line of code"。
    • 說明: 建議單行程式碼的強化或最佳化,確保表達簡潔、高效和可維護。
  10. 學習類別

    (Learn about Class)
    • 用法: 反白顯示類別名稱,然後點擊提示:"AI: Learn about Class"。之後可以在聊天中繼續詢問特定問題。
    • 說明: 提供關於特定類別的深入見解和詳細解釋,包括其方法、屬性以及在專案中的用法。
  11. 生成測試案例

    (Generate Test Cases)
    • 用法: 反白顯示任何類別或方法,然後點擊提示:"AI: Generate Test for Class or Method"。
    • 說明: 根據 AI Assistant 提供的上下文提示,自動為類別或方法生成測試案例,簡化測試寫作流程。

C.AI 聊天功能

Jeddict AI Assistant 提供了多種上下文感知的 AI 聊天模式,可有效解決開發問題:

  1. 感知專案上下文的 AI 聊天

    (Project Context-Aware AI Chat)
    • 用法: 在專案開啟時,右鍵點擊專案並選擇 "AI Assistant" 以啟動 AI 聊天。
    • 說明: AI 聊天會考量整個專案的上下文,提供針對專案結構和組件的全面見解和支援。
  2. 感知 Java 套件與類別上下文的 AI 聊天

    (Java Packages & Classes Context-Aware AI Chat)
    • 用法: 選擇所需的套件或類別,右鍵點擊並選擇 "AI Assistant" 以開始聊天。
    • 說明: 針對特定的 Java 套件或類別與 AI 互動,獲得反映該套件或類別上下文的見解和建議。
  3. 編輯器提供各 Java 類別專屬的 AI 聊天

    (Tailored AI Chat for Individual Java Classes from Editor)
    • 用法: 在編輯器中開啟所需的 Java 類別,右鍵點擊並選擇 "AI Assistant" 以啟動專注於該類別的對話。
    • 說明: 針對單個 Java 類別與 AI Assistant 進行有針對性的討論,接收專門針對該類別的詳細分析和改進建議。
  4. 網頁開發上下文感知的 AI 聊天

    (Web Development Context-Aware AI Chat (HTML, CSS, JS, JSF))
    • 用法: 選擇 webapp 目錄或其任何子目錄,右鍵點擊並選擇 "AI Assistant"。
    • 說明: 與 AI 針對 HTML, CSS, JavaScript 和 JSF 等網頁應用程式組件進行交流,獲得針對網頁開發的上下文感知支援和建議。
  5. 通用 AI 聊天

    (General AI Chat (No Context Required))
    • 用法: 點擊頂部工具列上的 Jeddict 按鈕。
    • 說明: 無需程式碼或專案上下文即可使用 AI 聊天功能,適用於一般查詢或不與特定程式碼元素相關的討論,更具成本效益且速度更快。

D.其他實用功能

  1. 國際化支援

    (Internationalization Support)
    • 可透過在設定面板中設定 系統訊息 (System Message) 來配置 AI 以特定語言回應,例如使用 "Respond in French"。
    • 對於某些語言,可能需要使用相容的字體來正確呈現聊天視窗和編輯器中的文本。
  2. 支援 PlantUML 和 Markdown 支援

    (PlantUML and Markdown Support in Chat)
    • 現在可以直接在聊天視窗中查看和編輯 PlantUML 圖表和 Markdown 內容。當要求圖表或格式化文本時,會即時顯示,可以在提交前預覽和進行更改。
    • 原地編輯 (Edit in-place): 只需點擊圖表或 Markdown 區塊即可直接在聊天中編輯。
    • 請注意: 所做的任何更改不會發送給 AI,直到明確地複製並貼上更新的內容到輸入框並提交。
  3. AI 式提交訊息

    AI Commit Message
    • 用法: 右鍵點擊專案並選擇 "AI Commit Message" 以自動生成提交訊息。此選項僅在 Git 檢測到更改時可見。
    • 說明: 根據 Git 儲存庫中檢測到的差異生成有意義的提交訊息,簡化版本控制流程。
  4. AI式 SQL 聊天助理

    (SQL AI Chat Assistant)
    • 用法: 導航至 Services > Databases > Register DB in Apache NetBeans。右鍵點擊資料庫連接並選擇 "AI Assistant"。
    • 說明: 這將啟動 AI 聊天視窗,提供基於資料庫的上下文。AI 將在 SQL 編輯器中生成結果,能夠在同一個聊天視窗中利用 AI 進行 SQL 查詢的智慧行內補全。
  5. AI 式 SQL 行內補全

    (SQL Inline Completion with AI)
    • 用法:Services > Databases > Register DB in Apache NetBeans 中,右鍵點擊資料庫連接並選擇 "Execute Command..."。在開啟的新視窗中撰寫 SQL 查詢,提供AI 驅動的行內補全建議。
    • 說明: 可以在同一個視窗中執行查詢並立即查看結果,簡化工作流程並減少查詢所花費的時間。

how to write Java HTTP client and server for RESTful web services

供程式串接用的網頁服務 (Web Service) 分成客戶端及服務端。以 Java RESTful Web Service 為例,假設服務端點規格如下:

    http://example.com:8080/api/v1/users/123?name=John 

其中,本體區皆以 JSON 物件字串為資料交換標準,輸入參數用到路徑123,查詢字串name=John,標頭區Area: Taipei,本體區phone: 123-4567。其 POST 請求 (Request) 及回應 (Response) 如下,

Request Header and Body
POST /api/users/123?name=John HTTP/1.1
Host: example.com:8080
Accept: application/json
Area: Taipei

{ "phone": "123-4567" }
Response Header and Body
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 85

{
  "id": 123,
  "name": "John",
  "area": "Taipei",
  "phone": "123-4567"
}

以下介紹上述端點規格的服務端及客戶端寫法:

(一) 服務端寫法,將使用 JAX-RS Jersey 套件

A. Java EE 7 手動轉型寫法如下,須搭配 GlassFish 4.1.2 (Java EE 7) with JDK 8

// Old Version:
//  On NetBeans, use Tools/Server 
//    to install GlassFish 4.1.2 (Java EE 7) with JDK 8
//  Use New/RESTful Web Services from Patterns...
//    to set up ApplicationConfig.java and Resource.java
//
import javax.ws.rs.Consumes;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
//import javax.ws.rs.core.Response;
import com.goolge.gson.Gson;  // install gson-2.7.jar in library

@Path("/api/users")
public class UserResource {

    @POST
    @Path("{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public String updateUser(      // 此方法名無關緊要,回傳型別建議用字串
            @PathParam("id") int id,
            @QueryParam("name") String name,
            @HeaderParam("Area") String area,
            String requestBody) {  // 請求本體區建議用字串接收,避免自動轉型出錯

        // 取出請求本體區物件
        PhoneRequest request = new Gson()
            .fromJson(requestBody, PhoneRequest.class); // 字串轉物件

        // 依據給定 id, name, area, request,執行運算邏輯

        // 建構回應物件,轉型成字串回應
        UserResponse response = new UserResponse();
        response.id = id;
        response.name = name;
        response.area = area;
        response.phone = request.phone;

        String jsonResponse = new Gson().toJson(response);  // 物件轉字串
        return jsonResponse;  // 回傳用字串

        //return Response.ok(response).build();  // 回傳用物件,可能報錯
    }

    // Request body class 請求本體物件
    public static class PhoneRequest {

        public String phone;
    }

    // Response body class 回應本體物件
    public static class UserResponse {

        public int id;
        public String name;
        public String area;
        public String phone;
    }
}

B. Jakarta EE 9.1 自動轉型寫法如下,須搭配 GlassFish 6.2.5 (Jakarta EE 9.1) with JDK 11

// New Version:
//  On NetBeans, use Tools/Server 
//    to install GlassFish 6.2.5 (Jakarta EE 9.1) with JDK 11
//  Use New/RESTful Web Services from Patterns...
//    to set up ApplicationConfig.java and Resource.java
//
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

@Path("/api/users")
public class UserResource {

    @POST
    @Path("{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response updateUser(      // 此方法名無關緊要,回傳型別 Response
            @PathParam("id") int id,
            @QueryParam("name") String name,
            @HeaderParam("Area") String area,
            PhoneRequest requestBody) {  // 請求本體區物件型別 PhoneRequest

        // 依據給定 id, name, area, requestBody,執行運算邏輯

        // 建構回應物件
        UserResponse response = new UserResponse();
        response.id = id;
        response.name = name;
        response.area = area;
        response.phone = requestBody.phone;

        return Response.ok(response).build();  // 回傳成功狀態 Response 物件
        // Response.status(Response.Status.BAD_REQUEST) // 其他錯誤狀態回傳法
        //       .entity("Invalid input")
        //       .build();
    }

    // Request body class 請求本體物件
    public static class PhoneRequest {

        public String phone;
    }

    // Response body class 回應本體物件
    public static class UserResponse {

        public int id;
        public String name;
        public String area;
        public String phone;
    }
}

(二) 至於客戶端有如下三種常用寫法,可供比較選用:

  • JAX-RS 適合用於 Java/Jakarta EE 或 Jersey 等 RESTful 應用。
  • HttpURLConnection 是最基本的方式,無需額外套件,但較繁瑣。
  • HttpClient (JDK 11+) 是現代化且功能強大的選擇,須 JDK11以上才支援。

 A.使用 JAX-RS Client API

// On NetBeans, use New/RESTful Java Client... 
//   to add JAX-RS libraries which support javax/jakarta.ws.rs.* packages
//   and set up NewJerseyClient.java
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

public class JaxRsClientExample {
    public static void main(String[] args) {
        Client client = ClientBuilder.newClient();
        WebTarget target = client
            .target("http://example.com:8080/api/users/123")
            .queryParam("name", "John");

        Invocation.Builder builder = target
            .request(MediaType.APPLICATION_JSON)
            .header("Area", "Taipei");

        String jsonBody = "{ \"phone\": \"123-4567\" }";

        Response response = builder.post(
            Entity.entity(jsonBody, MediaType.APPLICATION_JSON));

        String responseBody = response.readEntity(String.class);
        System.out.println("Response: " + responseBody);
        response.close();
        client.close();
    }
}

B.使用 java.net.HttpURLConnection

import java.io.BufferedReader;
import java.io.IOException;  // thrown by httpURLConnection.openConnection()
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException; // thrown by URL()
import java.net.URL;

public class HttpURLConnectionExample {
    public static void main(String[] args) throws IOException, MalformedURLException {
        URL url = new URL("http://example.com:8080/api/users/123?name=John");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        conn.setRequestMethod("POST");
        conn.setRequestProperty("Accept", "application/json");
        conn.setRequestProperty("Area", "Taipei");
        conn.setRequestProperty("Content-Type", "application/json");
        conn.setDoOutput(true);  // needed before writing the request body

        String jsonInput = "{ \"phone\": \"123-4567\" }";
        // use try with resource block without catch block
        //   output stream will be auto closed when leaving the block
        //   any exception in the block will be passed upward to the calling method
        try (OutputStream os = conn.getOutputStream()) {
            os.write(jsonInput.getBytes());
            os.flush();
        }

        BufferedReader br = new BufferedReader(
            new InputStreamReader(conn.getInputStream()));
        String line;
        StringBuilder response = new StringBuilder();
        while ((line = br.readLine()) != null) {
            response.append(line);
        }

        System.out.println("Response: " + response.toString());
        conn.disconnect();
    }
}

C.使用 java.net.http.HttpClient (JDK 11+)

// JDK 11 above is required
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;

public class HttpClientExample {
    public static void main(String[] args) throws IOException, InterruptedException {
        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("http://example.com:8080/api/users/123?name=John"))
            .header("Accept", "application/json")
            .header("Area", "Taipei")
            .header("Content-Type", "application/json")
            .POST(BodyPublishers.ofString("{ \"phone\": \"123-4567\" }"))
            .build();

        HttpResponse<String> response = client.send(request, BodyHandlers.ofString());

        System.out.println("Response: " + response.body());
    }
}
    參考資料
  1. 對應各 Java/Jakarta EE 版本,與其相容的 GlassFish, JDK 版本可參考下址
    Version history of Java EE, JSF, GlassFish, JDK, and NetBeans

how the http protocol may support crud database operation

RESTful Web Service 為現在流行,用來存取資料庫記錄的 API 設計形式。以下以 JSON 資料交換格式為例,展示幾種對記錄進行增查改刪 (CRUD, Create, Read, Update, Delete) 運算時,常用的 HTTP 請求及回應範例。

設計 RESTful API 時,請求參數如果量小,可以利用 Path Parameter, Query String Parameter, Header Parameter, Body Parameter using application/json, Body Parameter using application/x-www-form-urlencoded 等參數傳遞法。如果參數量大,包含檔案,則可利用 Body Parameter using multipart/form-data 參數傳遞法。

同樣的,回應結果如果量小,可以利用 application/json 結果回傳法。如果量大,包含檔案,則可利用 multipart/mixed 結果回傳法。

以下透過 MS Copilot 生成幾種可能的 HTTP 請求及回應長相,如下。

(一) 利用 HTTP 進行新增查詢記錄的幾種作法如下:
A. 請求(Request)訊息格式
A1: Path Parameter 由路徑上的id,查詢某人基本資料
GET /api/users/123 HTTP/1.1
Host: example.com
Accept: application/json
A2: Query String Parameter 由查詢字串上的id,查詢某人基本資料
GET /api/users?id=123 HTTP/1.1
Host: example.com
Accept: application/json
A3: Header Parameter 由標頭區的id,查詢某人基本資料
GET /api/users HTTP/1.1
Host: example.com
Accept: application/json
id: 123
A4: Body Parameter using application/json 由本體區的id,查詢某人基本資料
POST /api/users HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
Content-Length: 18

{
  "id": 123
}
A5: Body Parameter using application/x-www-form-urlencoded 新增某人基本資料
POST /users/updateProfile HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Content-Length: 27

id=123&name=John+Doe&email=johndoe%40example.com
A6. Body Parameter using multipart/form-data 新增某人基本資料,含照片
POST /users/uploadProfile HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: [length]

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="metadata"
Content-Type: application/json

{
  "id": 123,
  "name": "John Doe",
  "email": "johndoe@example.com"
}
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="profileImage"; filename="profile.jpg"
Content-Type: image/jpeg

[image data here]
------WebKitFormBoundary7MA4YWxkTrZu0gW--
B. 回應(Response)訊息格式 B1. application/json格式,回傳某人基本資料
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 85

{
  "id": 123,
  "name": "John Doe",
  "email": "johndoe@example.com"
}
B2. multipart/mixed格式,回傳某人基本資料,含檔案
HTTP/1.1 200 OK
Content-Type: multipart/mixed; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: [total length]

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Type: application/json

{
  "id": 123,
  "name": "John Doe",
  "email": "johndoe@example.com"
}
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Type: application/octet-stream
Content-Disposition: attachment; filename="example.txt"

[file data here]
------WebKitFormBoundary7MA4YWxkTrZu0gW--
(二) 利用 HTTP 進行修改刪除記錄的作法如下: A. 修改記錄 Request:
PUT /api/users/123 HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
Content-Length: 58

{
  "name": "Jane Doe",
  "email": "janedoe@example.com"
}
Response:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 85

{
  "id": 123,
  "name": "Jane Doe",
  "email": "janedoe@example.com",
  "status": "updated"
}
B.刪除記錄 Request:
DELETE /api/users/123 HTTP/1.1
Host: example.com
Accept: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 45

{
  "id": 123,
  "status": "deleted"
}

how to connect to Java DB from Java application or Java EE container

 假設 Java DB 已建好 db_name 資料庫,帳密為 user/password。

以下展示 Java EE 後台組件如何善用資料來源定義,注入資料來源,定義執行SQL指令,回傳結果集合給前台。


// 定義資料來源
@DataSourceDefinition(
   name = "java:global/jdbc/db_name",
   className = "org.apache.derby.jdbc.ClientDataSource",
   url = "jdbc:derby://localhost:1527/db_name",
   databaseName = "db_name",
   user = "user",
   password = "password")

@Named("bean")
@javax.faces.view.ViewScoped 
public class Bean implements Serializable
{
   // 定義每筆記錄的欄位屬性 f1, f2, f3

   // 要求伺服器注入資料來源成為 dataSource 物件
   @Resource(lookup="java:global/jdbc/db_name")
   DataSource dataSource;
  
   // 回傳查詢結果集合
   public ResultSet query() throws SQLException
   {
      // check whether dataSource was injected by the server
      if (dataSource == null)
      {
         throw new SQLException("Unable to obtain DataSource");
      }
      
      // 透過注入的資料來源,從連線池取得連線
      Connection connection = dataSource.getConnection();

      // check whether connection was successful
      if (connection == null)
      {
         throw new SQLException("Unable to connect to DataSource");
      }
      
      try
      {
         // 建立查詢指令
         PreparedStatement getRecords = connection.prepareStatement(
            "SELECT * FROM tbl");
   
         // 建立存放結果的快取列集合
         CachedRowSet rowSet = 
            RowSetProvider.newFactory().createCachedRowSet();
            
         // 執行查詢指令,將結果填入列集合
         rowSet.populate(getRecords.executeQuery());

         // 回傳列集合,供前台呈現
         return rowSet; 
      } 
      finally
      {
         connection.close(); // return this connection to pool
      } 
   } 
   
   // 新增一筆記錄,回傳去處網頁名稱
   public String insert() throws SQLException
   {
      // check whether dataSource was injected by the server
      if (dataSource == null)
      {
         throw new SQLException("Unable to obtain DataSource");
      }
      
      // 透過注入的資料來源,從連線池取得連線
      Connection connection = dataSource.getConnection();
      
      // check whether connection was successful
      if (connection == null)
      {
         throw new SQLException("Unable to connect to DataSource");
      }
      
      try
      {
         // 建立帶參數新增記錄指令
         PreparedStatement addEntry = 
            connection.prepareStatement("INSERT INTO tbl " +
               "(f1,f2,f3) VALUES (?, ?, ?)");

         // 指定欄位值
         addEntry.setString(1, getF1());
         addEntry.setString(2, getF2());
         addEntry.setString(3, getF3());

         addEntry.executeUpdate(); // insert the entry
         return ""; // 回傳空字串,停在原頁
      } 
      finally
      {
         connection.close(); // 不管發生例外或return,都會關閉連線,歸還給連線池
      } 
   } 
}
    利用 Java EE 後台注入資料來源的好處包括
  1. 連線池管理,可供連線重複使用,減少頻繁開關連線的成本。
  2. 資源管理,伺服器可管理連線生命週期,確保連線正常關閉,返回連線池。
  3. 組態管理,伺服器設定資料庫連線組態細節,程式只要呼叫資料來源即可。

如果願意喪失上述好處,也可採用

A. 單機版如DriverManager操作資料庫作法,一切自己管理,如下。


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class JavaDBExample {
    private static final String DB_URL = "jdbc:derby:myDB;create=true";
    private static final String USER = "user";
    private static final String PASSWORD = "password";

    public static void main(String[] args) {
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
             Statement stmt = conn.createStatement()) {

            // Create a table
            String createTableSQL = "CREATE TABLE addresses (" +
                                    "id INT PRIMARY KEY, " +
                                    "firstname VARCHAR(255), " +
                                    "lastname VARCHAR(255), " +
                                    "street VARCHAR(255), " +
                                    "city VARCHAR(255), " +
                                    "state VARCHAR(255), " +
                                    "zip VARCHAR(255))";
            stmt.execute(createTableSQL);

            // Insert data
            String insertSQL = 
               "INSERT INTO addresses VALUES " + 
               "(1, 'John', 'Doe', '123 Main St', 'Anytown', 'CA', '12345')";
            stmt.execute(insertSQL);

            // Query data
            String querySQL = "SELECT * FROM addresses";
            ResultSet rs = stmt.executeQuery(querySQL);

            while (rs.next()) {
                System.out.println("ID: " + rs.getInt("id"));
                System.out.println("First Name: " + rs.getString("firstname"));
                System.out.println("Last Name: " + rs.getString("lastname"));
                System.out.println("Street: " + rs.getString("street"));
                System.out.println("City: " + rs.getString("city"));
                System.out.println("State: " + rs.getString("state"));
                System.out.println("ZIP: " + rs.getString("zip"));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

或者,
B. 採用單機版Java Persistence API (JPA) 操作資料庫作法,如下。

// Address.java 住址個體類別

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "addresses")
public class Address {
    @Id
    private int id;
    private String firstname;
    private String lastname;
    String city;
    private String state;
    private String zip;

    // Getters and setters
}

由如下個體管理員建立查詢,取用資料庫:

// JpaExample.java

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;
import java.util.List;

public class JpaExample {
    private static final EntityManagerFactory ENTITY_MANAGER_FACTORY =
      Persistence.createEntityManagerFactory("myPersistenceUnit");

    public static void main(String[] args) {
        EntityManager entityManager = ENTITY_MANAGER_FACTORY.createEntityManager();

        try {
            // Create a query to fetch addresses
            TypedQuery<Address> query = 
              entityManager.createQuery("SELECT a FROM Address a", Address.class);
            List<Address> addresses = query.getResultList();

            // Process the result
            for (Address address : addresses) {
                System.out.println("ID: " + address.getId());
                System.out.println("First Name: " + address.getFirstname());
                System.out.println("Last Name: " + address.getLastname());
                System.out.println("Street: " + address.getStreet());
                System.out.println("City: " + address.getCity());
                System.out.println("State: " + address.getState());
               ();
            }
        } finally {
            entityManager.close();
            ENTITY_MANAGER_FACTORY.close();
        }
    }
}

其中,建立個體管理員時,須要參考如下 META-INF/persistence.xml 持續組態檔,

內含 myPersistenceUnit 持續單元的定義:

<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.2">
  <persistence-unit name="myPersistenceUnit">
    <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
    <class>com.example.Address</class>
    <properties>
       <property name="javax.persistence.jdbc.url" value="jdbc:derby:myDB;create=true"/>
       <property name="javax.persistence.jdbc.user" value="user"/>
       <property name="javax.persistence.jdbc.password" value="password"/>
       <property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver"/>
       <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect"/>
       <property name="hibernate.hbm2ddl.auto" value="update"/>
    </properties>
  </persistence-unit>
</persistence>

a table summary of the javaserver faces 2.2 tag libraries defined in java ee 7

Java/Jarkarta EE 版本歷史所列,Java EE 7 標準發佈於 2013 年,伴隨的 JavaServer Faces 2.2 標準,已實作於 GlassFish 4.x 版本。其 .xhtml 網頁支援的標籤可參考 Oracle JavaEE 7 JSF 2.2 網站,共分成如下 6 種命名空間。

  • h: 為 JSF 超文字(html)標籤庫,提供標準 HTML 標籤的 JSF 版本,如 h:form、h:inputText 等,用於生成 HTML 表單元素。

  • f: 為 JSF 特殊功能(function)標籤庫,提供 JSF 的核心功能標籤,如 f:view、f:ajax、f:convertDateTime 等,用於處理事件、轉換器和 AJAX 功能。

  • ui: 為 JSF 配置用戶介面(user interface)標籤庫,提供模板和組合元件功能的標籤,如 ui:composition、ui:include、ui:repeat 等,用於定義和重複利用頁面結構。

  • c: 為 JSTL 核心(core)標籤庫,提供 JSTL的核心功能標籤,如 c:forEach、c:if、c:set 等,用於邏輯控制和迭代。

  • cc: 為 JSTL 組合元件(composite component)標籤庫,提供定義和使用組合元件的標籤,如 cc:interface、cc:implementation、cc:attribute 等,用於創建可重複利用的元件。

  • fn: 為 JSTL 函數(function)標籤庫,提供 JSTL 的函數標籤,如 fn:contains、fn:substring、fn:replace 等,用於字符串操作和其他常見功能。

其中,後面3種 JSTL (JavaServer Pages Standard Tag Library) 命名空間主要為了相容於早期發展的 JSP (JavaServer Pages) 網頁。以下針對 JSF (JavaServer Faces) 常見的前3種命名空間標籤,由 Miscosoft Copilot 摘要成表格如下。

h:命名空間標籤 描述
h:body 定義 HTML 文檔的主體。
h:button 渲染 HTML 按鈕。
h:column 定義數據表中的列。
h:commandButton 渲染可以提交表單的按鈕。
h:commandLink 渲染可以提交表單的鏈接。
h:dataTable 渲染數據表。
h:doctype 聲明文檔類型。
h:form 渲染 HTML 表單。
h:graphicImage 渲染圖像。
h:head 定義 HTML 文檔的頭部。
h:inputFile 渲染文件輸入字段。
h:inputHidden 渲染隱藏輸入字段。
h:inputSecret 渲染密碼輸入字段。
h:inputText 渲染文本輸入字段。
h:inputTextarea 渲染文本區輸入字段。
h:link 渲染超鏈接。
h:message 顯示單個驗證消息。
h:messages 顯示所有驗證消息。
h:outputFormat 渲染格式化文本。
h:outputLabel 為輸入字段渲染標籤。
h:outputLink 渲染超鏈接。
h:outputScript 渲染腳本。
h:outputStylesheet 渲染樣式表鏈接。
h:outputText 渲染文本。
h:panelGrid 渲染網格面板。
h:panelGroup 將組件分組在一起。
h:panelpassthrough.Element 渲染透傳元素。
h:selectBooleanCheckbox 渲染布爾值的複選框。
h:selectManyCheckbox 渲染多選複選框組。
h:selectManyListbox 渲染多選列表框。
h:selectManyMenu 渲染多選菜單。
h:selectOneListbox 渲染單選列表框。
h:selectOneMenu 渲染單選菜單。
h:selectOneRadio 渲染單選單選按鈕組。

f:命名空間標籤 描述
f:actionListener 為組件註冊一個動作監聽器。
f:ajax 為組件添加 Ajax 功能。
f:attribute 為組件添加一個屬性。
f:attributes 為組件添加多個屬性。
f:convertDateTime 將字符串轉換為日期/時間對象。
f:converter 為組件註冊一個轉換器。
f:convertNumber 將字符串轉換為數字。
f:event 為組件註冊一個事件監聽器。
f:facet 為組件聲明一個面。
f:loadBundle 加載資源包。
f:metadata 為視圖聲明元數據。
f:param 為組件添加一個參數。
f:passThroughAttribute 為組件添加一個透傳屬性。
f:passThroughAttributes 為組件添加多個透傳屬性。
f:phaseListener 為生命周期註冊一個階段監聽器。
f:resetValues 重置輸入組件的值。
f:selectItem 為選擇組件添加一個項目。
f:selectItems 為選擇組件添加多個項目。
f:setPropertyActionListener 當動作事件發生時設置屬性。
f:subview 在視圖中聲明一個子視圖。
f:validateBean 驗證一個 bean。
f:validateDoubleRange 驗證值是否在雙精度範圍內。
f:validateLength 驗證字符串的長度。
f:validateLongRange 驗證值是否在長整數範圍內。
f:validateRegex 根據正則表達式驗證字符串。
f:validateRequired 驗證值是否不為 null。
f:validator 為組件註冊一個驗證器。
f:valueChangeListener 為組件註冊一個值變更監聽器。
f:verbatim 渲染原始文本。
f:view 聲明一個視圖。
f:viewAction 聲明在視圖處理期間調用的動作。
f:viewParam 聲明一個視圖參數。

ui:命名空間標籤 描述
ui:component 定義可重用的組件,可以包含在其他頁面中。
ui:composition 定義頁面的模板,允許包含其他組件。
ui:debug 提供 JSF 頁面的調試信息。
ui:decorate 類似於 `ui:include`,但允許插入用戶定義的模板。
ui:define 定義模板的一部分,可以填充內容。
ui:fragment 定義頁面的一部分,可以包含在其他頁面中。
ui:include 包含另一個頁面或模板的內容。
ui:insert 將內容插入到模板的定義部分。
ui:param 將參數傳遞給包含的頁面或模板。
ui:remove 從渲染輸出中移除內容。
ui:repeat 對集合中的每個項目重複頁面的一部分。

Version history of Java EE, JPA, Java DB, JavaFX, and JDK

下表為 Java 平台企業版 (EE, Enterprise Edition) 各種開發工具的版本歷史。資料來自網路,可看出從昇陽公司的 J2EE、甲骨文公司的 Java EE,到 Eclipse基金會的 Jakarta EE,EE 標準的主導權不斷異動。 如果有機會拿到依據各代 Java 平台 EE 標準開發的舊程式碼,建議查找下表找出最合適的相關開發工具,以免無法進行測試。

例如,拿到使用 JavaFX 視窗介面程式,若查出其適用版本為 JavaFX 11,可參考下表得知,其合適的開發工具版本為 JavaFX 11  + JDK 11,兩者皆屬 LTS 長期支援版本,較容易找到安裝檔。

Year EE Standard JPA Java DB JavaFX JDK
1999J2EE 1.2
2000
J2SE 1.3
2001J2EE 1.3
2002
J2SE 1.4
2003J2EE 1.4
2004

J2SE 5 (1.5)
2005Java DB 10.1
2006Java EE 5JPA 1.0Java DB 10.2J2SE 6 (1.6)
2007
2008Java DB 10.3, 10.4JavaFX 1.0.2
2009Java EE 6JPA 2.0Java DB 10.5JavaFX 1.1, 1.2
2010Java DB 10.6JavaFX 1.3
2011
JavaFX 2.0J2SE 7 (1.7)
2012JavaFX 2.2
2013Java EE 7JPA 2.1Java DB 10.10
2014Java DB 10.11JavaFX 8J2SE 8 (1.8), LTS
2015Java DB 10.12
2016Java DB 10.13
2017Java EE 8JPA 2.2
J2SE 9 (1.9)
2018Java DB 10.14JavaFX 11 (LTS)J2SE 10 (1.10), J2SE 11 (LTS)
2019
Java DB 10.15JavaFX 12, 13J2SE 12, 13
2020Jakarta EE 9

JavaFX 14, 15J2SE 14, 15
2021Jakarta EE 9.1JavaFX 16, 17 (LTS)J2SE 16, J2SE 17 (LTS)
2022Jakarta EE 10JPA 3.1Java DB 10.16JavaFX 18, 19J2SE 18, 19
2023Java DB 10.17JavaFX 20, 21 (LTS)J2SE 20, J2SE 21 (LTS)
2024Jakarta EE 11JavaFX 23J2SE 22, 23
2025JavaFX 24, 25J2SE 24, J2SE 25 (LTS)

以下表格摘要,由 NetBeans 測試,可和各版本 JavaFX (LTS) 相容的長期支援 JDK (LTS) 版本,供安裝參考。

Year JavaFX (LTS) Compatible JDK (LTS) Note
2018JavaFX 11 (LTS)J2SE 11, 17, 21JDK 8 不支援 JavaFX 11 模組功能
2021JavaFX 17 (LTS)J2SE 11, 17, 21
2023JavaFX 21 (LTS)J2SE 17, 21JDK 11 不支援 JavaFX 21 模組版本

註1: 網路參考來源: 以 Wikipedia 維基百科網站為主

  1. Jakarta EE
  2. JPA
  3. Apache Derby (Java DB)
  4. JavaFX
  5. Java版本歷史

how to set up the NetBeans environment for running Java EE 7 compilant JavaServer Faces with @ManagedBean annotation

若拿到使用 JavaServer Faces (JSF) 開發的套件,內含 @ManagedBean 註記,可依據附註的部落格,查出其合適的開發工具版本為 JSF 2.0 + GlassFish 3.x + JDK 6 + NetBeans 6.x 或 JSF 2.2 + GlassFish 4.x + JDK 7 + NetBeans 7.x。

若找不到JDK 6、JDK 7,支援 LTS (長期服務) 版本的 JDK 8 較好找,應該也可以。至於 NetBeans 版本似乎較不嚴苛,使用較新的 NetBeans 22 也是可以的,只是伺服器要安裝支援 Java EE 6 或 7 的 GlassFish 及 JDK 版本。其安裝步驟如下

1. 假設 NetBeans 要安裝 GlassFish 4.1.2 (內含符合Java EE 7 Web標準的JSF2.2) + JDK 8
      所須 JDK 8 如下:
        OpenJDK Zulu 8.0.442+6
        zulu8.84.0.15-ca-jdk8.0.442-win_x64.zip
        C:\Users\xx\Documents\NetBeansJDKs\zulu8.84.0.15-ca-jdk8.0.442-win_x64\

2. 開舊專案後,改伺服器選用 Java EE 7 Web 相容的 GlassFish 4.1.12
      Resolve Missing Server Problem
        Available Java EE 7 Web compatible servers:
          GlassFish Server 4
     【OK】

3. 啟動 GlassFish Server 4
      Window/Services/Servers: GlassFish Server 4
        GlassFish server couldn ot be started with JDK XX (Default).
        Please select another Java SE Platform.
        Java Platform: Zulu 8.0.442+6
     【OK】

     Windoww安全性警訊
       Windows Defender 允許 OpenJDK xxx 在這些網路上通訊:
       V 網域網路,例如工作場所網路
    【允許存取】

4. 安裝 GlassFish 4.1.2 步驟
    Tools/Servers:
       Add Server...
         Choose Server
           Server: GlassFish Server
           Name: GlassFish Server 4
       Next>
         Server Location
           Installation Location: C:\Users\xx\GlassFish_server4
           X Local Domain
           Choose server to download:
             GlassFish Server 4.1.2
             V I have read and accept the licence agreement...(click)
             Download Now... (104.2m)
       Next>
         Domain Location
           Domain: domain1
           Host: localhost, Vloopback
           DAS Port: 4848, HTTP Port: 8080, VDefault
           Target:
           User Name:
           Password:
      【Finish】
    

        Server Name: GlassFish Server 4
        Server Type: GlassFish Server 4.1.2
        Common:
          Installation Location: C:\Users\xx\GlassFish_server4\glassfish
          Domains Folder: C:\Users\xx\GlassFish_server4\glassfish\domains
          Host: localhost
          DAS Port: 484, HTTP Port:8080
          Domain: domain1
            Enable HTTP Monitor (維持不打勾)
          V Enable JDBC Driver Deployment
          V Preserve Sessions Across Redeployment
          V Start Registered Derby Server
      【Close】

5. 安裝  JDK 8 方法
    Tools/Java Platforms
      Add Platform...
        Select platform type
          Download OpenJDK (via Foojay.io Disco API)
        Connect to OpenJDK Discovery Service
          Quick:
            Version: 23,21,17,11,8
            Distribution: Microsoft or Zulu

附註: 有關於 Java 平台企業版各種開發工具的歷史相容關係,可參考如下說明:
    Blog: Version history of Java EE,  JSF, GlassFish, JDK, and NetBeans

Version history of Java EE, JSF, GlassFish, JDK, and NetBeans

下表為 Java 平台企業版 (EE, Enterprise Edition) 各種開發工具的版本歷史。資料來自網路,可看出從昇陽公司的 J2EE、甲骨文公司的 Java EE,到 Eclipse基金會的 Jakarta EE,EE 標準的主導權不斷異動。 如果有機會拿到依據各代 Java 平台 EE 標準開發的舊程式碼,建議查找下表找出最合適的相關開發工具,以免無法進行測試。

例如,若拿到使用 JavaServer Faces (JSF) 開發的套件,內含 @ManagedBean 註記,可先查出其適用版本為 Java EE 6 或 Java EE 7。接著,參考下表得知,其合適的開發工具版本為 JSF 2.0 + GlassFish 3.x + JDK 6 + NetBeans 6.x 或 JSF 2.2 + GlassFish 4.x + JDK 7 + NetBeans 7.x。

其中,Java 開發套件 (Java Development Kit),簡稱 JDK,也歷經多代更迭。若找不到 JDK 6、JDK 7,支援 LTS (長期服務) 版本的 JDK 8 較好找,應該也可以。至於 NetBeans 版本的要求較不嚴苛,使用較新的 NetBeans 22 也是可以的,只是伺服器要安裝支援 Java EE 6 或 7 的 GlassFish 及 JDK 版本。

Year EE Standard JSF/Jakarta GlassFish JDK NetBeans
1999J2EE 1.2
2000J2SE 1.3
2001J2EE 1.3
2002J2SE 1.4
2003J2EE 1.4
2004JSF 1.0,1.1J2SE 5 (1.5)NetBeans 4.0
2005
2006Java EE 5JSF 1.2J2SE 6 (1.6)NetBeans 5.0
2007NetBeans 6.0
2008
2009Java EE 6JSF 2.0GlassFish 3.0
2010
2011J2SE 7 (1.7)NetBeans 7.0
2012
2013Java EE 7JSF 2.2GlassFish 4.0
2014J2SE 8 (1.8), LTSNetBeans 8.0
2015
2016
2017Java EE 8JSF 2.3J2SE 9 (1.9)
2018J2SE 10 (1.10), J2SE 11 (LTS)NetBeans 9.0, 10.0
2019Jakarta 2.3GlassFish 5.0J2SE 12, 13NetBeans 11.0
2020Jakarta EE 9Jakarta 3.0GlassFish 6.0J2SE 14, 15NetBeans 12.0
2021Jakarta EE 9.1J2SE 16, J2SE 17 (LTS)
2022Jakarta EE 10Jakarta 4.0GlassFish 7.0J2SE 18, 19NetBeans 13, 14, 15, 16
2023J2SE 20, J2SE 21 (LTS)NetBeans 17, 18, 19, 20
2024Jakarta EE 11J2SE 22, 23NetBeans 21, 22, 23, 24
2025J2SE 24, J2SE 25 (LTS)NetBeans 25, 26, 27, 28

以下表格摘要,由 NetBeans 測試,列出可和各版本 GlassFish 相容的長期支援 JDK (LTS) 版本,供安裝參考。

Year EE Standard JSF/Jakarta GlassFish Compatible JDK (LTS) Note
2009Java EE 6JSF 2.0GlassFish 3.0JDK 8
2013Java EE 7JSF 2.2GlassFish 4.0JDK 8支援 @ManagedBean 標註的最高版本組合
2019Java EE 8Jakarta 2.3GlassFish 5.0JDK 8@ManagedBean 廢止,改用環境及相依注入(CDI) 的 @Named 標註
2020Jakarta EE 9Jakarta 3.0GlassFish 6.0JDK 11, 17
2022Jakarta EE 10Jakarta 4.0GlassFish 7.0JDK 11, 17, 21

註1: Java EE 6,7 支援的 @ManagedBean 寫法,可參考如下要點,改成 Jakarta EE 支援的 @Named 註記。


  // 原來 Java EE 6,7 寫法
  import javax.faces.bean.ManagedBean;

  @ManagedBean(name="webBean")
  public class MyWebBean
  
  // 改成 Jakarta EE 寫法

  import jakarta.inject.Named;
  import jakarta.enterprise.context.SessionScoped;
  import java.io.Serializable;

  @Named("webBean")
  @SessionScoped
  public class MyWebBean implements Serializable

註2: 網路參考來源: 以 Wikipedia 維基百科網站為主

  1. Jakarta EE
  2. JavaServer Faces
  3. GlassFish
  4. Java版本歷史
  5. NetBeans
  6. NetBeans Release Schedule

how to connect codex app to remote ollama backend through caddy relay

如何讓 Codex App 連接公司架設的 LLM 從 Caddy 閘道安裝、遠端 Ollama API 金鑰,到 Codex 的 config.toml 組態設定,一次整理成可直接照做的流程。 前言 OpenAI 提供免費下載...

總網頁瀏覽量