在自動化運維、部署腳本或遠程管理場景中,Java 程式常需通過 SSH 連接 Linux 服務器執行命令。JSch (Java Secure Channel) 是一個純 Java 實現的 SSH2 庫,廣泛用於此類需求。本文提供一個完整、可直接運行的工具類示例,涵蓋會話建立、命令執行、結果讀取與資源釋放。
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version> <!-- 請使用最新穩定版 -->
</dependency>
import com.jcraft.jsch.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.charset.Charset;
import java.util.Properties;
public class SshCommandExecutor {
private static final Logger log = LoggerFactory.getLogger(SshCommandExecutor.class);
private Session session;
private final int timeout = 60_000; // 連接超時 60s
/**
* 建立 SSH 會話
* @param host 服務器 IP/主機名
* @param username 登錄用戶名
* @param password 登錄密碼
* @param port SSH 端口,默認 22
* @throws JSchException 連接失敗拋出
*/
public void connect(String host, String username, String password, int port) throws JSchException {
log.info("嘗試連接 {}:{} 用戶 {}", host, port, username);
JSch jsch = new JSch();
session = jsch.getSession(username, host, port);
session.setPassword(password);
// 禁用嚴格主機密鑰檢查,避免首次連接提示 known_hosts
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.setTimeout(timeout);
session.connect(); // 實際建立 TCP 連接並完成 SSH 握手
log.info("SSH 會話已建立");
}
/**
* 在遠程服務器執行命令並返回標準輸出
* @param command 要執行的 Shell 命令
* @param charset 輸出流編碼,如 "UTF-8"
* @return 命令輸出文本(每行以換行符分隔)
* @throws Exception 執行或 IO 異常
*/
public String executeCommand(String command, String charset) throws Exception {
if (session == null || !session.isConnected()) {
throw new IllegalStateException("SSH 會話未建立或已斷開");
}
ChannelExec channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(command);
channel.setInputStream(null); // 無標準輸入
channel.setErrStream(System.err); // 錯誤輸出重定向到當前進程 stderr
try (InputStream in = channel.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in, Charset.forName(charset)))) {
channel.connect(); // 啟動命令執行
StringBuilder output = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append('\n');
}
// 等待命令真正結束(可選,視業務而定)
while (!channel.isClosed()) {
Thread.sleep(100);
}
return output.toString();
} finally {
channel.disconnect(); // 釋放通道資源
}
}
/** 關閉會話 */
public void disconnect() {
if (session != null && session.isConnected()) {
session.disconnect();
log.info("SSH 會話已關閉");
}
}
// -------- 測試主方法 --------
public static void main(String[] args) {
SshCommandExecutor executor = new SshCommandExecutor();
try {
executor.connect("10.8.12.189", "jmuser", "root1234", 22);
String result = executor.executeCommand("java -version", "UTF-8");
System.out.println("命令輸出:\n" + result);
} catch (Exception e) {
log.error("執行失敗", e);
} finally {
executor.disconnect();
}
}
}
Session 對象建立開銷較大(TCP 握手、密鑰交換、認證),同一主機多次命令執行應復用同一 Session,僅重複打開 ChannelExec。
遠程命令輸出編碼取決於服務器 Locale 設置(常見 UTF-8 或 GBK),傳入正確 charset 參數可避免中文亂碼。
try-with-resources 確保流關閉。finally 塊中 channel.disconnect() 防止通道洩漏導致服務器端殘留進程。jsch.addIdentity(privateKeyPath))或憑證管理系統。StrictHostKeyChecking=no 僅適用於測試/內網環境,生產應維護 known_hosts 防止中間人攻擊。ChannelShell 替代 ChannelExec,支持多命令連續輸入。ChannelSftp 實現上傳/下載/列表操作。session.setPortForwardingL(localPort, remoteHost, remotePort) 實現隧道代理。JSch 提供了輕量級、零原生依賴的 SSH 客戶端能力。通過封裝 Session 與 ChannelExec 生命周期,配合合理的異常處理與編碼設置,即可構建穩健的遠程命令執行組件,滿足自動化運維、CI/CD 流水線、遠程診斷等多種場景需求。