超細(xì)講解Java調(diào)用python文件的幾種方式
前言
java調(diào)用python的契機(jī)來(lái)自于一個(gè)項(xiàng)目需要用到算法,但是算法工程師們寫的python,于是就有了java后端調(diào)用python腳本的需求,中間遇到了許多問題,特此記錄整理了一次。
1、java調(diào)用python的方式有哪幾種
1.1 方法一:jpython
專門為java調(diào)用python2開發(fā)出來(lái)的類庫(kù),但由于不支持python3版本,python2和3之間的語(yǔ)法又不兼容導(dǎo)致jpython庫(kù)并非特別通用。github有人問到過什么時(shí)候出python3版本的庫(kù),官方答復(fù)說(shuō)是可行的但很困難(截止2022年8月份 jpython官方目前沒有開發(fā)出支持python3的類庫(kù))
jpython的語(yǔ)法特別簡(jiǎn)單,使用PythonIntercepter即可簡(jiǎn)單的操作python文件。
1.1.1 導(dǎo)入jar包
<dependency> <groupId>org.python</groupId> <artifactId>jython-standalone</artifactId> <version>2.7.0</version> </dependency>
1.1.2 調(diào)用python腳本中的method1()方法
PythonInterpreter interpreter = new PythonInterpreter();
interpreter.execfile("C:\\Users\\Dick\\Desktop\\demo.py");
// 調(diào)用demo.py中的method1方法
PyFunction func = interpreter.get("method1",PyFunction.class);
Integer a = 10;
Integer b = 10;
PyObject pyobj = func.__call__(new PyInteger(a), new PyInteger(b));
System.out.println("獲得方法的返回值 = " + pyobj.toString());注:如無(wú)返回值 僅執(zhí)行interpreter.execfile()方法即可
1.2 方法二:ProcessBuilder
ProcessBuilder是jdk提供的腳本執(zhí)行工具類,無(wú)論是python文件還是shell腳本還是其他的指令,都可以通過此類來(lái)執(zhí)行,我們來(lái)看看它是如何調(diào)用python腳本的
1.2.1 首先我們把python文件放入resource下

1.2.2 接下來(lái)就是執(zhí)行腳本了
/**
* 執(zhí)行python腳本
* @param fileName 腳本文件名稱
* @param params 腳本參數(shù)
* @throws IOException
*/
public static void execPythonFile(String fileName, String params) throws IOException {
// 獲取python文件所在目錄地址
String windowsPath = ClassUtils.getDefaultClassLoader().getResource("").getPath().substring(1) + "py/";
// windows執(zhí)行腳本需要使用 cmd.exe /c 才能正確執(zhí)行腳本
Process process = new ProcessBuilder("cmd.exe", "/c", "python", windowsPath + fileName, params).start();
logger.info("讀取python文件 開始 fileName={}", fileName);
BufferedReader errorReader = null;
// 腳本執(zhí)行異常時(shí)的輸出信息
errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
List<String> errorString = read(fileName, errorReader);
logger.info("讀取python文件 異常 fileName={}&errorString={}", fileName, errorString);
// 腳本執(zhí)行正常時(shí)的輸出信息
BufferedReader inputReader = null;
inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
List<String> returnString = read(fileName, inputReader);
logger.info("讀取python文件 fileName={}&returnString={}", fileName, returnString);
try {
logger.info("讀取python文件 wait fileName={}", fileName);
process.waitFor();
} catch (InterruptedException e) {
logger.error("讀取python文件 fileName="+fileName+" 等待結(jié)果返回異常", e);
}
logger.info("讀取python文件 fileName={} == 結(jié)束 ==", fileName);
}
private static List<String> read(String fileName, BufferedReader reader) {
List<String> resultList = Lists.newArrayList();
String res = "";
while (true) {
try {
if (!((res = reader.readLine()) != null)) break;
} catch (IOException e) {
logger.error("讀取python文件 fileName=" + fileName + " 讀取結(jié)果異常", e);
}
resultList.add(res);
}
return resultList;
}上述代碼僅考慮了windows,而在Linux中情況會(huì)比較復(fù)雜一點(diǎn)。
1.2.3 Linux中執(zhí)行python存在的問題
我們知道常規(guī)的項(xiàng)目部署是將項(xiàng)目打成jar包,然后直接放入Linux 或者通過docker等容器進(jìn)行部署,這個(gè)時(shí)候resources下的py文件就在jar包里了,但我們執(zhí)行python腳本時(shí)使用的是:
python3 腳本文件所在地
此時(shí)python腳本在jar包里面,不能通過 jar路徑/BOOT-INF/classes/py/xxx.py進(jìn)行訪問【我測(cè)試過一段時(shí)間 發(fā)現(xiàn)python3 (python指令也不行) 指令無(wú)法調(diào)用在jar里面的腳本】,所以我能想到的方案是將python腳本文件直接放入服務(wù)器的某個(gè)文件夾中,方便后續(xù)訪問。如果是docker部署,只需要在dockerfile中加入一個(gè)COPY指令 將py文件放到指定目錄下:

1.2.4 Linux中執(zhí)行python文件
下面代碼將兼容windows和linux調(diào)用py文件【Linux執(zhí)行py文件是使用python還是python3根據(jù)實(shí)際py環(huán)境變量配置來(lái)選擇就好】
/**
* 執(zhí)行python文件
* @param fileName python文件地址
* @param params 參數(shù) 其實(shí)可以改成傳入多個(gè)參數(shù) 一個(gè)個(gè)放入ProcessBuilder中的
* @throws IOException
*/
public static void execPythonFile(String fileName, String params) throws IOException {
// ① 當(dāng)前系統(tǒng)類型
String os = System.getProperty("os.name");
// ② 獲取python文件所在目錄地址
String windowsPath = ClassUtils.getDefaultClassLoader().getResource("").getPath().substring(1) + "py/";
String linuxPath = "/ai/egcc/";
logger.info("讀取python文件 init fileName={}&path={}", fileName);
Process process;
if (os.startsWith("Windows")){
// windows執(zhí)行腳本需要使用 cmd.exe /c 才能正確執(zhí)行腳本
process = new ProcessBuilder("cmd.exe", "/c", "python", windowsPath + fileName, params).start();
}else {
// linux執(zhí)行腳本一般是使用python3 + 文件所在路徑
process = new ProcessBuilder("python3", linuxPath + fileName, params).start();
}
logger.info("讀取python文件 開始 fileName={}", fileName);
BufferedReader errorReader = null;
// 腳本執(zhí)行異常時(shí)的輸出信息
errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
List<String> errorString = read(fileName, errorReader);
logger.info("讀取python文件 異常 fileName={}&errorString={}", fileName, errorString);
// 腳本執(zhí)行正常時(shí)的輸出信息
BufferedReader inputReader = null;
inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
List<String> returnString = read(fileName, inputReader);
logger.info("讀取python文件 fileName={}&returnString={}", fileName, returnString);
try {
logger.info("讀取python文件 wait fileName={}", fileName);
process.waitFor();
} catch (InterruptedException e) {
logger.error("讀取python文件 fileName="+fileName+" 等待結(jié)果返回異常", e);
}
logger.info("讀取python文件 fileName={} == 結(jié)束 ==", fileName);
}
private static List<String> read(String fileName, BufferedReader reader) {
List<String> resultList = Lists.newArrayList();
String res = "";
while (true) {
try {
if (!((res = reader.readLine()) != null)) break;
} catch (IOException e) {
logger.error("讀取python文件 fileName=" + fileName + " 讀取結(jié)果異常", e);
}
resultList.add(res);
}
return resultList;
}以為這就完了嗎,其實(shí)還沒有呢,process.waitFor()方法其實(shí)存在一些問題,如果上線后可能會(huì)造成事故,具體參考:java調(diào)用exe程序 使用process.waitFor()死鎖
那我們就嘗試用線程池來(lái)解決死鎖的問題吧
1.2.5 解決java調(diào)用腳本文件存在的隱式問題解決
以下為終極版代碼:
private static ExecutorService taskPool = new ThreadPoolExecutor(8, 32
,200L,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(600)
,new ThreadFactoryBuilder()
.setNameFormat("thread-自定義線程名-runner-%d").build());
/**
* 執(zhí)行python文件
* @param fileName python文件地址
* @param params 參數(shù) 多個(gè)直接逗號(hào)隔開
* @throws IOException
*/
public static void execPythonFile(String fileName, String params) throws IOException {
// ① 當(dāng)前系統(tǒng)類型
String os = System.getProperty("os.name");
// ② 獲取python文件所在目錄地址
String windowsPath = ClassUtils.getDefaultClassLoader().getResource("").getPath().substring(1) + "py/";
String linuxPath = "/ai/egcc/";
logger.info("讀取python文件 init fileName={}&path={}", fileName);
Process process;
if (os.startsWith("Windows")){
// windows執(zhí)行腳本需要使用 cmd.exe /c 才能正確執(zhí)行腳本
process = new ProcessBuilder("cmd.exe", "/c", "python", windowsPath + fileName, params).start();
}else {
// linux執(zhí)行腳本一般是使用python3 + 文件所在路徑
process = new ProcessBuilder("python3", linuxPath + fileName, params).start();
}
taskPool.submit(() -> {
logger.info("讀取python文件 開始 fileName={}", fileName);
BufferedReader errorReader = null;
// 腳本執(zhí)行異常時(shí)的輸出信息
errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
List<String> errorString = read(fileName, errorReader);
logger.info("讀取python文件 異常 fileName={}&errorString={}", fileName, errorString);
});
taskPool.submit(() -> {
// 腳本執(zhí)行正常時(shí)的輸出信息
BufferedReader inputReader = null;
inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
List<String> returnString = read(fileName, inputReader);
logger.info("讀取python文件 fileName={}&returnString={}", fileName, returnString);
});
try {
logger.info("讀取python文件 wait fileName={}", fileName);
process.waitFor();
} catch (InterruptedException e) {
logger.error("讀取python文件 fileName="+fileName+" 等待結(jié)果返回異常", e);
}
logger.info("讀取python文件 fileName={} == 結(jié)束 ==", fileName);
}
private static List<String> read(String fileName, BufferedReader reader) {
List<String> resultList = Lists.newArrayList();
String res = "";
while (true) {
try {
if (!((res = reader.readLine()) != null)) break;
} catch (IOException e) {
logger.error("讀取python文件 fileName=" + fileName + " 讀取結(jié)果異常", e);
}
resultList.add(res);
}
return resultList;
}好了 上述代碼已經(jīng)可以正確的調(diào)用python腳本了,但博主目前仍然有些問題還沒解決:比如如何調(diào)用java的jar包內(nèi)部的py文件?在windows上的jar包內(nèi)的py文件是可以調(diào)用成功的【我在windows本地啟動(dòng)jar包做過測(cè)試】,但是docker容器里面的jar卻無(wú)法調(diào)用成功的原因是什么?
如果有朋友遇到問題歡迎在評(píng)論區(qū)留言和討論
1.2.6 終極版python執(zhí)行工具類【建議使用】
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* java調(diào)用python的執(zhí)行器
*/
@Component
public class PythonExecutor {
private static final Logger logger = LoggerFactory.getLogger(PythonExecutor.class);
private static final String OS = System.getProperty("os.name");
private static final String WINDOWS_PATH = ClassUtils.getDefaultClassLoader().getResource("").getPath().substring(1) + "py/automl/"; // windows為獲取項(xiàng)目根路徑即可
private static final String LINUX_PATH = "/ai/xx";// linux為python文件所在目錄
private static ExecutorService taskPool = new ThreadPoolExecutor(8, 16
, 200L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(600)
, new ThreadFactoryBuilder()
.setNameFormat("thread-自定義線程名-runner-%d").build());
/**
* 執(zhí)行python文件【異步 無(wú)需等待py文件執(zhí)行完畢】
*
* @param fileName python文件地址
* @param params 參數(shù)
* @throws IOException
*/
public static void execPythonFile(String fileName, String params) {
taskPool.submit(() -> {
try {
exec(fileName, params);
} catch (IOException e) {
logger.error("讀取python文件 fileName=" + fileName + " 異常", e);
}
});
}
/**
* 執(zhí)行python文件 【同步 會(huì)等待py執(zhí)行完畢】
*
* @param fileName python文件地址
* @param params 參數(shù)
* @throws IOException
*/
public static void execPythonFileSync(String fileName, String params) {
try {
execSync(fileName, params);
} catch (IOException e) {
logger.error("讀取python文件 fileName=" + fileName + " 異常", e);
}
}
private static void exec(String fileName, String params) throws IOException {
logger.info("讀取python文件 init fileName={}&path={}", fileName, WINDOWS_PATH);
Process process;
if (OS.startsWith("Windows")) {
// windows執(zhí)行腳本需要使用 cmd.exe /c 才能正確執(zhí)行腳本
process = new ProcessBuilder("cmd.exe", "/c", "python", WINDOWS_PATH + fileName, params).start();
} else {
// linux執(zhí)行腳本一般是使用python3 + 文件所在路徑
process = new ProcessBuilder("python3", LINUX_PATH + fileName, params).start();
}
new Thread(() -> {
logger.info("讀取python文件 開始 fileName={}", fileName);
BufferedReader errorReader = null;
// 腳本執(zhí)行異常時(shí)的輸出信息
errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
List<String> errorString = read(fileName, errorReader);
logger.info("讀取python文件 異常 fileName={}&errorString={}", fileName, errorString);
}).start();
new Thread(() -> {
// 腳本執(zhí)行正常時(shí)的輸出信息
BufferedReader inputReader = null;
inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
List<String> returnString = read(fileName, inputReader);
logger.info("讀取python文件 fileName={}&returnString={}", fileName, returnString);
}).start();
try {
logger.info("讀取python文件 wait fileName={}", fileName);
process.waitFor();
} catch (InterruptedException e) {
logger.error("讀取python文件 fileName=" + fileName + " 等待結(jié)果返回異常", e);
}
logger.info("讀取python文件 fileName={} == 結(jié)束 ==", fileName);
}
private static void execSync(String fileName, String params) throws IOException {
logger.info("同步讀取python文件 init fileName={}&path={}", fileName, WINDOWS_PATH);
Process process;
if (OS.startsWith("Windows")) {
// windows執(zhí)行腳本需要使用 cmd.exe /c 才能正確執(zhí)行腳本
process = new ProcessBuilder("cmd.exe", "/c", "python", WINDOWS_PATH + fileName, params).start();
} else {
// linux執(zhí)行腳本一般是使用python3 + 文件所在路徑
process = new ProcessBuilder("python3", LINUX_PATH + fileName, params).start();
}
taskPool.submit(() -> {
logger.info("讀取python文件 開始 fileName={}", fileName);
BufferedReader errorReader = null;
// 腳本執(zhí)行異常時(shí)的輸出信息
errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
List<String> errorString = read(fileName, errorReader);
logger.info("讀取python文件 異常 fileName={}&errorString={}", fileName, errorString);
});
taskPool.submit(() -> {
// 腳本執(zhí)行正常時(shí)的輸出信息
BufferedReader inputReader = null;
inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
List<String> returnString = read(fileName, inputReader);
logger.info("讀取python文件 fileName={}&returnString={}", fileName, returnString);
});
try {
logger.info("同步讀取python文件 wait fileName={}", fileName);
process.waitFor();
} catch (InterruptedException e) {
logger.error("同步讀取python文件 fileName=" + fileName + " 等待結(jié)果返回異常", e);
}
logger.info("同步讀取python文件 fileName={} == 結(jié)束 ==", fileName);
}
private static List<String> read(String fileName, BufferedReader reader) {
List<String> resultList = Lists.newArrayList();
String res = "";
while (true) {
try {
if (!((res = reader.readLine()) != null)) break;
} catch (IOException e) {
logger.error("讀取python文件 fileName=" + fileName + " 讀取結(jié)果異常", e);
}
resultList.add(res);
}
return resultList;
}
}===== 補(bǔ)充 =====
有小伙伴可能在別的博文上找到下面的java調(diào)用腳本方式
Runtime.getRuntime().exec()
其實(shí)上面的腳本底層用的也是ProcessBuilder對(duì)象,所以是一樣的。
總結(jié)
到此這篇關(guān)于Java調(diào)用python文件的幾種方式的文章就介紹到這了,更多相關(guān)Java調(diào)用python文件的方式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
idea右鍵沒有java class選項(xiàng)問題解決方案
這篇文章主要介紹了idea右鍵沒有java class選項(xiàng)問題解決方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
Spring Boot動(dòng)態(tài)加載Jar包與動(dòng)態(tài)配置實(shí)現(xiàn)
隨著項(xiàng)目的不斷演進(jìn)和業(yè)務(wù)需求的增長(zhǎng),很多場(chǎng)景下需要實(shí)現(xiàn)系統(tǒng)的動(dòng)態(tài)性和靈活性,本文主要介紹了Spring Boot動(dòng)態(tài)加載Jar包與動(dòng)態(tài)配置實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02
SpringBoot實(shí)現(xiàn)防止XSS攻擊的示例詳解
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何實(shí)現(xiàn)防止XSS攻擊,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03
Java時(shí)間類庫(kù)Timer的使用方法與實(shí)例詳解
這篇文章主要介紹了Jave時(shí)間類庫(kù)Timer的使用方法與實(shí)例詳解,需要的朋友可以參考下2020-02-02

