很多时候在Java项目中使用python的一些方法还是很不错的,那么有没有办法让二者轻松的结合起来呢?今天我们就来看一个开源的工具spring-boot-python-executor,它可以帮我们实现这个需求。废话不多说,直接看看怎么来用<dependency> <groupId>io.github.w4t3rcs</groupId> <artifactId>spring-boot-python-executor-starter</artifactId> <version>1.0.0</version></dependency>
为了避免Python脚本执行影响本地环境,推荐使用Docker来部署Python gRPC服务,在docker-compose.yaml中添加配置services: python-grpc-server: image: "w4t3rcs/spring-boot-python-executor-python-grpc-server:latest" ports: - "50051:50051" environment: PYTHON_SERVER_TOKEN: secret PYTHON_ADDITIONAL_IMPORTS: scikit-learn,numpy,pandas,scipy
应用的配置很简单application.yaml:spring: python: executor: type: grpc grpc: token: secret
Java服务类,SimplePythonService@Slf4j@Service@RequiredArgsConstructorpublic class SimplePythonService { private static final String SIMPLE_SCRIPT = "simple_script.py"; private static final String NUMERIC_SCRIPT = "numeric_script.py"; private static final String DICT_SCRIPT = "dict_script.py"; private final PythonProcessor pythonProcessor; // 方法执行前,执行指定Python脚本(无需关注返回值) @PythonBefores({ @PythonBefore(SIMPLE_SCRIPT), @PythonBefore(NUMERIC_SCRIPT), @PythonBefore(DICT_SCRIPT), }) public void doSomethingWithPythonBefore() { log.info("doSomethingWithPythonBefore()"); } // 方法内部调用Python脚本,获取返回值并打印 public void doSomethingWithPythonInside() { log.info("doSomethingWithPythonInside()"); log.info("1 --> {}", pythonProcessor.process(SIMPLE_SCRIPT, String.class)); log.info("2 --> {}", pythonProcessor.process(NUMERIC_SCRIPT, Float.class)); log.info("3 --> {}", pythonProcessor.process(DICT_SCRIPT, DictScriptResponse.class)); } // 方法执行后,执行指定Python脚本(无需关注返回值) @PythonAfters({ @PythonAfter(SIMPLE_SCRIPT), @PythonAfter(NUMERIC_SCRIPT), @PythonAfter(DICT_SCRIPT), }) public void doSomethingWithPythonAfter() { log.info("doSomethingWithPythonAfter()"); }}
对应的3个python脚本,需要注意o4java{xxx}表示将python中的xxx这个变量作为结果返回给javahello_world = 'Hello World'print(hello_world)o4java{hello_world}
a = 2.3462b = 14.151c = a + bo4java{c}
result = { "x": "Hello World", "y": 12345}o4java{result}
是不是很简单?接下来我们通过一个例子,看看Java如何将实体类参数传递给Python,python通过SpEL表达式获取参数@Service@RequiredArgsConstructorpublic class PriceCalculatorPythonService { private static final String CALCULATOR_SCRIPT = "price_calculator.py"; private final PythonProcessor pythonProcessor; // 接收Java实体类参数,调用Python脚本计算价格并返回 public double calculatePrice(ProductDto product, CustomerDto customer) { int randomMultiplier = new Random().nextInt(10); product.setBasePrice(product.getBasePrice() * randomMultiplier); // 封装要传递给Python的参数(key为参数名,value为参数值) Map<String, Object> arguments = Map.of( "product", product, "customer", customer ); // 调用Python脚本,指定返回值类型为Double,传递参数 PythonExecutionResponse<Double> response = pythonProcessor.process(CALCULATOR_SCRIPT, Double.class, arguments); return response.body(); }}
python脚本 price_calculator.py# 从Java传递的参数中,获取product的basePricebase_price = spel{#product.getBasePrice()}discount = 0# 应用客户忠诚度折扣(获取customer的loyaltyYears字段)if spel{#customer.loyaltyYears()} > 2: discount += 0.05# 应用批量折扣(获取product的quantity字段)if spel{#product.getQuantity()} > 10: discount += 0.03# 计算最终价格final_price = base_price * (1 - discount)# 将计算结果返回给Javao4java{final_price}