Java基于命令行調(diào)用Python腳本的方法詳解
需求描述
利用 Java 基于命令行調(diào)用 Python
環(huán)境信息
- 基于 Ubuntu 24 的 Docker 容器
- Python 3.12
- Java 17
實(shí)現(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)用中試驗(yàn):未此行命令未生效;但開發(fā)者獨(dú)自登錄 docker 容器內(nèi),有生效 # && echo '. $HOME/.venv/bin/activate' >> ~/.bashrc \ # Java程序的子進(jìn)程調(diào)用中試驗(yàn):未此行命令未生效;但開發(fā)者獨(dú)自登錄 docker 容器內(nèi),有生效 # && echo 'export PYTHON=$HOME/.venv/bin/python' >> /etc/profile \ # Java程序的子進(jìn)程調(diào)用中試驗(yàn):未此行命令未生效;但開發(fā)者獨(dú)自登錄 docker 容器內(nèi),有生效 # && echo '. /etc/profile' > $HOME/app.sh \ # Java程序的子進(jìn)程調(diào)用中試驗(yàn):未測(cè)通,有衍生問題未解決掉 # && echo 'java ${JAVA_OPTS:-} -jar app.jar > /dev/null 2>&1 &' >> $HOME/app.sh \ # Java程序的子進(jìn)程調(diào)用中試驗(yàn):未測(cè)通,有衍生問題未解決掉 # && echo 'java ${JAVA_OPTS:-} -jar app.jar' >> $HOME/app.sh \ # Java程序的子進(jìn)程調(diào)用中試驗(yàn):未測(cè)通,有衍生問題未解決掉 # && chmod +x $HOME/app.sh \ # Java程序的子進(jìn)程調(diào)用中試驗(yàn):未測(cè)通,有衍生問題未解決掉 # && chown 777 $HOME/app.sh # Java程序的子進(jìn)程調(diào)用中試驗(yàn):未測(cè)通,有衍生問題未解決掉 EXPOSE 8080 # ENTRYPOINT exec sh $HOME/app.sh # Java程序的子進(jìn)程調(diào)用中試驗(yàn):未測(cè)通,有衍生問題未解決掉 ENTRYPOINT exec java ${JAVA_OPTS:-} -DPYTHON=$HOME/.venv/bin/python -jar app.jar # 通過 Java 獲取 JVM 參數(shù)( System.getProperty("PYTHON") ) 方式獲取 【 Python 可執(zhí)行文件的絕對(duì)路徑】的值
編寫和準(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)鍵點(diǎn):程序阻塞問題
程序阻塞問題
- 通過 Process實(shí)例.getInputStream() 和 Process實(shí)例.getErrorStream() 獲取的輸入流和錯(cuò)誤信息流是緩沖池向當(dāng)前Java程序提供的,而不是直接獲取外部程序的標(biāo)準(zhǔn)輸出流和標(biāo)準(zhǔn)錯(cuò)誤流。
- 而緩沖池的容量是一定的。
因此,若外部程序在運(yùn)行過程中不斷向緩沖池輸出內(nèi)容,當(dāng)緩沖池填滿,那么: 外部程序將暫停運(yùn)行直到緩沖池有空位可接收外部程序的輸出內(nèi)容為止。(
注:采用xcopy命令復(fù)制大量文件時(shí)將會(huì)出現(xiàn)該問題
解決辦法: 當(dāng)前的Java程序不斷讀取緩沖池的內(nèi)容,從而為騰出緩沖池的空間。
Runtime r = Runtime.getRuntime(); try { Process proc = r.exec("cmd /c dir"); // 假設(shè)該操作為造成大量?jī)?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é)束后需自動(dòng)關(guān)閉;否則,不管是字符流還是字節(jié)流均由于既讀不到數(shù)據(jù),又讀不到流結(jié)束符,從而出現(xiàn)阻塞Java進(jìn)程運(yùn)行的情況。
cmd
的參數(shù) “/c
” 表示當(dāng)命令執(zhí)行完成后關(guān)閉自身。
關(guān)鍵點(diǎn): Java Runtime.exec() 方法
基本方法: Runtime.exec()
首先,在Linux系統(tǒng)下,使用Java調(diào)用Python腳本,傳入?yún)?shù),需要使用Runtime.exec()
方法
即 在java
中使用shell
命令
這個(gè)方法有兩種使用形式:
方式1 無參數(shù)傳入 ,直接執(zhí)行Linux相關(guān)命令: Process process = Runtime.getRuntime().exec(String cmd);
無參數(shù)可以直接傳入字符串,如果需要傳參數(shù),就要用方式2的字符串?dāng)?shù)組實(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);
查看錯(cuò)誤信息
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);
簡(jiǎn)單示例
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)鍵點(diǎn): python 絕對(duì)路徑
查看(虛擬環(huán)境的)python 可執(zhí)行程序路徑,然后在Java調(diào)用的時(shí)候?qū)懗鼋^對(duì)路徑。
如:$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 的實(shí)現(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 命令時(shí),報(bào)找不到 可執(zhí)行程序(`Python` 命令)\ * ————建議: java 程序中使用的 `python` 命令的可執(zhí)行程序路徑,使用【絕對(duì)路徑】 * @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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java中的Io(input與output)操作總結(jié)(二)
這一節(jié)我們來討論關(guān)于文件自身的操作包括:創(chuàng)建文件對(duì)象、創(chuàng)建和刪除文件、文件的判斷和測(cè)試、創(chuàng)建目錄、獲取文件信息、列出文件系統(tǒng)的根目錄、列出目錄下的所有文件,等等,感興趣的朋友可以了解下2013-01-01ElasticSearch學(xué)習(xí)之Es索引Api操作
這篇文章主要為大家介紹了ElasticSearch學(xué)習(xí)之Es索引Api操作詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01Java實(shí)現(xiàn)HttpGet請(qǐng)求傳body參數(shù)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)HttpGet請(qǐng)求傳body參數(shù)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02將Java項(xiàng)目提交到云服務(wù)器的流程步驟
所謂將項(xiàng)目提交到云服務(wù)器即將你的項(xiàng)目打成一個(gè) jar 包然后提交到云服務(wù)器即可,因此我們需要準(zhǔn)備服務(wù)器環(huán)境為:Linux + JDK + MariDB(MySQL)+ Git + Maven,文中通過圖文講解的非常詳細(xì),需要的朋友可以參考下2025-04-04Springboot實(shí)現(xiàn)郵箱驗(yàn)證代碼實(shí)例
這篇文章主要介紹了Springboot實(shí)現(xiàn)郵箱驗(yàn)證代碼實(shí)例,在一些業(yè)務(wù)需求中我們經(jīng)常需要使用郵箱進(jìn)行驗(yàn)證碼的收取,本文通過簡(jiǎn)單的代碼實(shí)例來說明,需要的朋友可以參考下2024-01-01