Java基于命令行調(diào)用Python腳本的方法詳解
需求描述
利用 Java 基于命令行調(diào)用 Python
環(huán)境信息
- 基于 Ubuntu 24 的 Docker 容器
- Python 3.12
- Java 17
實現(xiàn)步驟
安裝 Python + PIP 環(huán)境
以基于 Ubuntu 24 的 Docker 環(huán)境為例
Dockerfile
# OS: Ubuntu 24.04
FROM swr.cn-north-4.myhuaweicloud.com/xxx/eclipse-temurin:17-noble
COPY ./target/*.jar /app.jar
COPY ./target/classes/xxx/ /xxx/
# install : python + pip (前置操作: 更新 apt 源)
RUN sed -i 's#http[s]*://[^/]*#http://mirrors.aliyun.com#g' /etc/apt/sources.list \
&& apt-get update \
&& apt-get -y install vim \
&& apt-get -y install --no-install-recommends python3 python3-pip python3-venv \
&& python3 -m venv $HOME/.venv \
&& . $HOME/.venv/bin/activate \ # 注:Linux 中 高版本 Python (3.5以上),必須在虛擬環(huán)境下方可正常安裝所需依賴包
&& pip install -i https://mirrors.aliyun.com/pypi/simple/ can cantools
# && echo "alias python=python3" >> ~/.bashrc \ # Java程序的子進(jìn)程調(diào)用中試驗:未此行命令未生效;但開發(fā)者獨自登錄 docker 容器內(nèi),有生效
# && echo '. $HOME/.venv/bin/activate' >> ~/.bashrc \ # Java程序的子進(jìn)程調(diào)用中試驗:未此行命令未生效;但開發(fā)者獨自登錄 docker 容器內(nèi),有生效
# && echo 'export PYTHON=$HOME/.venv/bin/python' >> /etc/profile \ # Java程序的子進(jìn)程調(diào)用中試驗:未此行命令未生效;但開發(fā)者獨自登錄 docker 容器內(nèi),有生效
# && echo '. /etc/profile' > $HOME/app.sh \ # Java程序的子進(jìn)程調(diào)用中試驗:未測通,有衍生問題未解決掉
# && echo 'java ${JAVA_OPTS:-} -jar app.jar > /dev/null 2>&1 &' >> $HOME/app.sh \ # Java程序的子進(jìn)程調(diào)用中試驗:未測通,有衍生問題未解決掉
# && echo 'java ${JAVA_OPTS:-} -jar app.jar' >> $HOME/app.sh \ # Java程序的子進(jìn)程調(diào)用中試驗:未測通,有衍生問題未解決掉
# && chmod +x $HOME/app.sh \ # Java程序的子進(jìn)程調(diào)用中試驗:未測通,有衍生問題未解決掉
# && chown 777 $HOME/app.sh # Java程序的子進(jìn)程調(diào)用中試驗:未測通,有衍生問題未解決掉
EXPOSE 8080
# ENTRYPOINT exec sh $HOME/app.sh # Java程序的子進(jìn)程調(diào)用中試驗:未測通,有衍生問題未解決掉
ENTRYPOINT exec java ${JAVA_OPTS:-} -DPYTHON=$HOME/.venv/bin/python -jar app.jar # 通過 Java 獲取 JVM 參數(shù)( System.getProperty("PYTHON") ) 方式獲取 【 Python 可執(zhí)行文件的絕對路徑】的值編寫和準(zhǔn)備 Python 業(yè)務(wù)腳本
- step1 編寫 Python 業(yè)務(wù)腳本 (略)
- step2 如果 Python 腳本在 JAVA 工程內(nèi)部(JAR包內(nèi)),則需在 執(zhí)行 Python 腳本前,將其提前拷貝為一份新的腳本文件到指定位置。
public XXX {
private static String scriptFilePath;
public static String TMP_DIR = "/tmp/xxx-sdk/";
static {
prepareHandleScript( TMP_DIR );
}
/**
* 準(zhǔn)備腳本文件到目標(biāo)路徑
* @note 無法直接執(zhí)行 jar 包內(nèi)的腳本文件,需要拷貝出來。
* @param targetScriptDirectory 目標(biāo)腳本的文件夾路徑
* 而非腳本文件路徑 eg: "/tmp/xxx-sdk"
*/
@SneakyThrows
public static void prepareHandleScript(String targetScriptDirectory){
File file = new File(targetScriptDirectory);
//如果目標(biāo)目錄不存在,則創(chuàng)建該目錄
if (!file.exists() && !file.isDirectory()) {
file.mkdirs();
}
File targetScriptFile = new File(targetScriptDirectory + "/xxx-converter.py");// targetScriptFile = "\tmp\xxx-sdk\xxx-converter.py"
scriptFilePath = targetScriptFile.getAbsolutePath(); // scriptFilePath = "D:\tmp\xxx-sdk\xxx-converter.py"
URL resource = CanAscLogGenerator.class
.getClassLoader()
.getResource( "bin/xxx-converter.py");
InputStream converterPythonScriptInputStream = null;
try {
converterPythonScriptInputStream = resource.openStream();
FileUtils.copyInputStreamToFile( converterPythonScriptInputStream, targetScriptFile );
} catch (IOException exception){
log.error("Fail to prepare the script!targetScriptDirectory:{}, exception:", targetScriptDirectory, exception);
throw new RuntimeException(exception);
} finally {
if(converterPythonScriptInputStream != null){
converterPythonScriptInputStream.close();
}
}
}
}Java 調(diào)用 Python 腳本
關(guān)鍵點:程序阻塞問題

程序阻塞問題
- 通過 Process實例.getInputStream() 和 Process實例.getErrorStream() 獲取的輸入流和錯誤信息流是緩沖池向當(dāng)前Java程序提供的,而不是直接獲取外部程序的標(biāo)準(zhǔn)輸出流和標(biāo)準(zhǔn)錯誤流。
- 而緩沖池的容量是一定的。
因此,若外部程序在運行過程中不斷向緩沖池輸出內(nèi)容,當(dāng)緩沖池填滿,那么: 外部程序將暫停運行直到緩沖池有空位可接收外部程序的輸出內(nèi)容為止。(
注:采用xcopy命令復(fù)制大量文件時將會出現(xiàn)該問題
解決辦法: 當(dāng)前的Java程序不斷讀取緩沖池的內(nèi)容,從而為騰出緩沖池的空間。
Runtime r = Runtime.getRuntime();
try {
Process proc = r.exec("cmd /c dir"); // 假設(shè)該操作為造成大量內(nèi)容輸出
// 采用字符流讀取緩沖池內(nèi)容,騰出空間
BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream(), "gbk")));
String line = null;
while ((line = reader.readLine()) != null){
System.out.println(line);
}
/* 或采用字節(jié)流讀取緩沖池內(nèi)容,騰出空間
ByteArrayOutputStream pool = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int count = -1;
while ((count = proc.getInputStream().read(buffer)) != -1){
pool.write(buffer, 0, count);
buffer = new byte[1024];
}
System.out.println(pool.toString("gbk"));
*/
int exitVal = proc.waitFor();
System.out.println(exitVal == 0 ? "成功" : "失敗");
} catch(Exception e){
e.printStackTrace();
}注意:外部程序在執(zhí)行結(jié)束后需自動關(guān)閉;否則,不管是字符流還是字節(jié)流均由于既讀不到數(shù)據(jù),又讀不到流結(jié)束符,從而出現(xiàn)阻塞Java進(jìn)程運行的情況。
cmd的參數(shù) “/c” 表示當(dāng)命令執(zhí)行完成后關(guān)閉自身。
關(guān)鍵點: Java Runtime.exec() 方法
基本方法: Runtime.exec()
首先,在Linux系統(tǒng)下,使用Java調(diào)用Python腳本,傳入?yún)?shù),需要使用Runtime.exec()方法
即 在java中使用shell命令
這個方法有兩種使用形式:
方式1 無參數(shù)傳入 ,直接執(zhí)行Linux相關(guān)命令: Process process = Runtime.getRuntime().exec(String cmd);
無參數(shù)可以直接傳入字符串,如果需要傳參數(shù),就要用方式2的字符串?dāng)?shù)組實現(xiàn)。
方式2 有參數(shù)傳入,并執(zhí)行Linux命令: Process process = Runtime.getRuntime().exec(String[] cmd);
執(zhí)行結(jié)果
使用exec方法執(zhí)行命令,如果需要執(zhí)行的結(jié)果,用如下方式得到:
String line;
while ((line = processInputStream.readLine()) != null) { // InputStream processInputStream = process.getInputStream();
System.out.println(line);
if ("".equals(line)) {
break;
}
}
System.out.println("line ----> " + line);查看錯誤信息
BufferedReader errorResultReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String errorLine;
while ((errorLine = shellErrorResultReader.readLine()) != null) {
System.out.println("errorStream:" + errorLine);
}
int exitCode = process.waitFor();
System.out.println("exitCode:" + exitCode);簡單示例
String result = "";
String[] cmd = new String [] { "pwd" };
Process process = Runtime.getRuntime().exec(cmd);
InputStreamReader inputStreamReader = new InputStreamReader(process.getInputStream());
LineNumberReader input = new LineNumberReader(inputStreamReader);
result = input.readLine();
System.out.println("result:" + result);關(guān)鍵點: python 絕對路徑
查看(虛擬環(huán)境的)python 可執(zhí)行程序路徑,然后在Java調(diào)用的時候?qū)懗鼋^對路徑。
如:$HOME/.venv/bin/python以解決 Linux 環(huán)境中的 Python 3.X 的虛擬環(huán)境異常問題(pip install XXX : error: externally-managed-environment)。
Cannot run program “python“: error=2, No such file or director (因虛擬環(huán)境問題,找不到python命令和pip安裝的包)
Java 調(diào)用 Python 的實現(xiàn) (必讀)
@Slf4j
public class XxxxGenerator implements IGenerator<XxxxSequenceDto> {
//python jvm 變量 (`-DPYTHON=$HOME/.venv/bin/python`)
public static String PYTHON_VM_PARAM = "PYTHON";//System.getProperty(PYTHON_VM_PARAM)
//python 環(huán)境變量名稱 //eg: "export PYTHON=$HOME/.venv/bin/python" , pythonEnv="$HOME/.venv/bin/python"
public static String PYTHON_ENV_PARAM = "PYTHON";//;System.getenv(PYTHON_ENV_PARAM);
private static String PYTHON_COMMAND ;
//默認(rèn)的 python 命令
private static String PYTHON_COMMAND_DEFAULT = "python";
//...
static {
PYTHON_COMMAND = loadPythonCommand();
log.info("PYTHON_COMMAND:{}, PYTHON_VM:{}, PYTHON_ENV:{}", PYTHON_COMMAND, System.getProperty(PYTHON_VM_PARAM), System.getenv(PYTHON_ENV_PARAM) );
//...
}
/**
* 加載 python 命令的可執(zhí)行程序的路徑
* @note
* Linux 中,尤其是 高版本 Python(3.x) ,為避免 Java 通過 `Runtime.getRuntime().exec(args)` 方式 調(diào)用 Python 命令時,報找不到 可執(zhí)行程序(`Python` 命令)\
* ————建議: java 程序中使用的 `python` 命令的可執(zhí)行程序路徑,使用【絕對路徑】
* @return
*/
private static String loadPythonCommand(){
String pythonVm = System.getProperty(PYTHON_VM_PARAM);
String pythonEnv = System.getenv(PYTHON_ENV_PARAM);
String pythonCommand = pythonVm != null?pythonVm : pythonEnv;
pythonCommand = pythonCommand != null?pythonCommand : PYTHON_COMMAND_DEFAULT;
return pythonCommand;
}
/**
* 業(yè)務(wù)方法: CAN ASC LOG 轉(zhuǎn) BLF
* @param ascLogFilePath
* @param blfFilePath
*/
protected void convertToBlf(File ascLogFilePath, File blfFilePath){
//CanAsclogBlfConverterScriptPath = "/D:/Workspace/CodeRepositories/xxx-platform/xxx-sdk/xxx-sdk-java/target/classes/bin/can-asclog-blf-converter.py"
//String CanAsclogBlfConverterScriptPath = CanAscLogGenerator.class.getClassLoader().getResource("bin/can-asclog-blf-converter.py").getPath();
String canAscLogBlfConverterScriptPath = XxxxGenerator.scriptFilePath;//python 業(yè)務(wù)腳本的文件路徑, eg: "D:\tmp\xxx-sdk\can-asclog-blf-converter.py"
//String [] args = new String [] {"python", "..\\bin\\can-asclog-blf-converter.py", "-i", ascLogFilePath, "-o", blfFilePath};// ascLogFilePath="/tmp/xxx-sdk/can-1.asc" , blfFilePath="/tmp/xxx-sdk/can-1.blf"
String [] args = new String [] { PYTHON_COMMAND, canAscLogBlfConverterScriptPath, "-i", ascLogFilePath.getPath(), "-o", blfFilePath.getPath()};
log.info("args: {} {} {} {} {} {}", args);
Process process = null;
Long startTime = System.currentTimeMillis();
try {
process = Runtime.getRuntime().exec(args);
Long endTime = System.currentTimeMillis();
log.info("Success to convert can asc log file to blf file!ascLogFile:{}, blfFile:{}, timeConsuming:{}ms, pid:{}", ascLogFilePath, blfFilePath, endTime - startTime, process.pid());
} catch (IOException exception) {
log.error("Fail to convert can asc log file to blf file!ascLogFile:{}, blfFile:{}, exception:", ascLogFilePath, blfFilePath, exception);
throw new RuntimeException(exception);
}
//讀取 python 腳本的標(biāo)準(zhǔn)輸出
// ---- input stream ----
List<String> processOutputs = new ArrayList<>();
try(
InputStream processInputStream = process.getInputStream();
BufferedReader processReader = new BufferedReader( new InputStreamReader( processInputStream ));
) {
Long readProcessStartTime = System.currentTimeMillis();
String processLine = null;
while( (processLine = processReader.readLine()) != null ) {
processOutputs.add( processLine );
}
process.waitFor();
Long readProcessEndTime = System.currentTimeMillis();
log.info("Success to read the can asc log to blf file's process standard output!timeConsuming:{}ms", readProcessEndTime - readProcessStartTime );
log.info("processOutputs(System.out):{}", JSON.toJSONString( processOutputs ));
} catch (IOException exception) {
log.error("Fail to get input stream!IOException:", exception);
throw new RuntimeException(exception);
} catch (InterruptedException exception) {
log.error("Fail to wait for the process!InterruptedException:{}", exception);
throw new RuntimeException(exception);
}
// ---- error stream ----
List<String> processErrors = new ArrayList<>();
try(
InputStream processInputStream = process.getErrorStream();
BufferedReader processReader = new BufferedReader( new InputStreamReader( processInputStream ));
) {
Long readProcessStartTime = System.currentTimeMillis();
String processLine = null;
while( (processLine = processReader.readLine()) != null ) {
processErrors.add( processLine );
}
process.waitFor();
Long readProcessEndTime = System.currentTimeMillis();
log.error("Success to read the can asc log to blf file's process standard output!timeConsuming:{}ms", readProcessEndTime - readProcessStartTime );
log.error("processOutputs(System.err):{}", JSON.toJSONString( processOutputs ));
} catch (IOException exception) {
log.error("Fail to get input stream!IOException:", exception);
throw new RuntimeException(exception);
} catch (InterruptedException exception) {
log.error("Fail to wait for the process!InterruptedException:{}", exception);
throw new RuntimeException(exception);
}
if( processErrors.size() > 0 ) {
throw new RuntimeException( "convert to blf failed!\nerrors:" + JSON.toJSONString(processErrors) );
}
}
}到此這篇關(guān)于Java基于命令行調(diào)用Python腳本的方法詳解的文章就介紹到這了,更多相關(guān)Java調(diào)用Python腳本內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java數(shù)據(jù)結(jié)構(gòu)算法稀疏數(shù)組示例詳解
這篇文章主要為大家介紹了java數(shù)據(jù)結(jié)構(gòu)算法稀疏數(shù)組示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Java使用OpenOffice將office文件轉(zhuǎn)換為PDF的示例方法
OpenOffice是一個開源的辦公套件,它包含了文檔處理、電子表格、演示文稿以及繪圖等多種功能,類似于Microsoft Office,本文將給大家介紹Java使用OpenOffice將office文件轉(zhuǎn)換為PDF的示例方法,需要的朋友可以參考下2024-09-09
MybatisPlus處理四種表與實體的映射及id自增策略分析
在最近的工作中,碰到一個比較復(fù)雜的返回結(jié)果,發(fā)現(xiàn)簡單映射已經(jīng)解決不了這個問題了,只好去求助百度,學(xué)習(xí)mybatis表與實體的映射應(yīng)該怎么寫,將學(xué)習(xí)筆記結(jié)合工作碰到的問題寫下本文,供自身查漏補缺,同時已被不時之需2022-10-10
Elasticsearch 映射 fielddata 工作原理解析
在 Elasticsearch 中,fielddata 是一種在內(nèi)存中構(gòu)建的索引,用于加速某些類型的查詢,特別是聚合和排序操作,下面通過本文給大家介紹Elasticsearch 映射 fielddata的相關(guān)知識,感興趣的朋友一起看看吧2025-06-06
SpringBoot集成Jasypt實現(xiàn)敏感信息加密保護(hù)功能
在數(shù)字化時代背景下,互聯(lián)網(wǎng)滲透生活的方方面面,同時也帶來了日益嚴(yán)峻的安全挑戰(zhàn),因此,采用有效的敏感信息加密手段不僅是保護(hù)知識產(chǎn)權(quán)和業(yè)務(wù)安全的必要舉措,所以本文介紹了SpringBoot集成Jasypt實現(xiàn)敏感信息加密保護(hù)功能,需要的朋友可以參考下2025-04-04
idea中創(chuàng)建maven的Javaweb工程并進(jìn)行配置(圖文教程)
這篇文章主要介紹了idea中創(chuàng)建maven的Javaweb工程并進(jìn)行配置,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),文中給大家提到了tomcat的運行方法,具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02
java編程實現(xiàn)簡單的網(wǎng)絡(luò)爬蟲示例過程
這篇文章主要為大家介紹了如何使用java編程實現(xiàn)一個簡單的網(wǎng)絡(luò)爬蟲示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-10-10
spring中WebClient如何設(shè)置連接超時時間以及讀取超時時間
這篇文章主要給大家介紹了關(guān)于spring中WebClient如何設(shè)置連接超時時間以及讀取超時時間的相關(guān)資料,WebClient是Spring框架5.0引入的基于響應(yīng)式編程模型的HTTP客戶端,它提供一種簡便的方式來處理HTTP請求和響應(yīng),需要的朋友可以參考下2024-08-08

