当前位置: 首页 > news >正文

【grpc】利用protobuf实现java或kotlin调用python脚本,含实现过程和全部代码

前言

在一些特殊场景中,我们可能需要使用java或者其他任意语言调用python脚本或sdk等。本文的需求衍生也不例外于此,python端有sdk,但只能在python中调用,于是就有了本文章。
常见的调用方式如jython、python提供http rest接口、python提供rpc实现、java通过jni调用转换成c的python。每种调用方式都有优缺点,我们更期待一种简单、快速、功能更自由、低侵入、方便维护的方式来实现。
快速调研了一下现有的各种实现方式,最后决定采用grpc调用,好处就是代码不多,协议定义简单方便,两端协调好就可以了,非常适合对sdk、算法、脚本、服务的调用,缺点就是更改协议后,两边要重新生成代码来保持同步,不过在有现成插件的情况下,这能很方便的控制,话不多说,下面贴出详细做法。

一、定义proto文件

创建一个文件名为script.proto,稍后需要在java端和python端引入

//@ 1 使用proto3语法
syntax = "proto3";
//@ 2 生成多个类(一个类便于管理)
option java_multiple_files = false;
//@ 3 定义调用时的java包名
option java_package= "com.kamjin.javacallpython.grpc.demo.proto";
//@ 4 生成外部类名
option java_outer_classname = "ScriptProto";
//@ 6. proto包名称(逻辑包名称)
package script;import "google/protobuf/struct.proto";//@ 7 定义一个服务来描述要生成的API接口,类似于Java的业务逻辑接口类
service ScriptService{//定义执行方法,方法名称和参数和返回值都是大驼峰//Note: 这里是 returns,不是 returnrpc Execute (ScriptRequest) returns (ScriptResponse) {}
}//@ 8 定义请求数据结构
//字符串数据类型
//等号后面的数字即索引值(表示参数顺序,以防止参数传递顺序混乱),服务启动后无法更改
//不能使用19000-1999保留数字
message ScriptRequest{string content = 1;google.protobuf.ListValue extract_params = 2;
}
//@ 9 定义响应数据结构
message ScriptResponse{string result = 1;
}

二、java/kotlin端

个人习惯使用kotlin+gradle,此处使用该组合演示,java+maven也可以,主要是gradle配置部分区别较大,有需求可以评论区留言

0.创建服务

创建一个springboot项目,版本为2.x,为了方便起见,需要是web服务,端口默认就可以

1.安装protobuf插件

在IDEA插件市场搜索protobuf下载安装,注意作者是HIGAN,不要装错了,如图
在这里插入图片描述

2.依赖和其他配置

配置模块的build.gradle.kts文件,
新增依赖和plugin如下:

plugins {//protobuf pluginid("com.google.protobuf") version "0.9.4"...
}dependencies {//grpc clientimplementation("net.devh:grpc-client-spring-boot-starter:2.15.0.RELEASE")implementation("io.grpc:grpc-stub:1.15.1")implementation("io.grpc:grpc-protobuf:1.15.1")...
}

protobuf配置和task配置如下:

import com.google.protobuf.gradle.*
import org.gradle.kotlin.dsl.proto//https://github.com/google/protobuf-gradle-plugin
sourceSets {main {proto {srcDir("src/main/proto")include("**/*.proto")}}test {proto {srcDir("src/test/proto")}}
}
protobuf {protoc {// The artifact spec for the Protobuf Compilerartifact = "com.google.protobuf:protoc:3.17.3"}plugins {// Optional: an artifact spec for a protoc plugin, with "grpc" as// the identifier, which can be referred to in the "plugins"// container of the "generateProtoTasks" closure.id("grpc") {artifact = "io.grpc:protoc-gen-grpc-java:1.40.0"}}generateProtoTasks {ofSourceSet("main").forEach {it.plugins {// Apply the "grpc" plugin whose spec is defined above, without// options. Note the braces cannot be omitted, otherwise the// plugin will not be added. This is because of the implicit way// NamedDomainObjectContainer binds the methods.id("grpc")}}}
}//配置提示proto文件重复的处理策略
tasks.withType<ProcessResources> {duplicatesStrategy = DuplicatesStrategy.INCLUDE
}

配置完成后点一下gradle的刷新按钮reload all gradle projects,此时会下载相关依赖

3.生成代码

在模块的src/main目录下新建名为proto文件夹,将定义好的script.proto文件放入该目录,运行gradle task,如图所示:
在这里插入图片描述
运行该task后将会生成可以调用的proto服务代码,将在文件夹build/generated/source/proto/main可以找到生成的代码,一般无需改动该代码,我们需要使用时直接调用引入即可。

4.服务配置

在模块配置文件application.yaml中配置如下:

grpc:client:scriptServiceGrpc:address: 'static://127.0.0.1:50051'negotiationType: plaintext
  • scriptServiceGrpc是我们在代码里需要声明的grpc server名称,可以任意自定义和在grpc.client下定义多个这样的条目
  • address指定grpc server端的地址+端口,在当前文章中对应的就是python项目中的grpc服务URL地址

关于配置项的更多详情可以查看这里。

5.编写grpc client代码

首先编写一个controller用于调试代码

package com.kamjin.javacallpython.grpc.demo.controller.testimport com.kamjin.javacallpython.grpc.demo.handle.*
import com.kamjin.common.ext.*
import org.springframework.beans.factory.annotation.*
import org.springframework.web.bind.annotation.*/*** <p>** </p>** @author kam* @since 2024/01/08*/
@RequestMapping("/test/proto/")
@RestController
class ProtoTestController {@Autowiredlateinit var grpcScriptExecuter: GrpcScriptExecuter@PostMapping("script")fun script(@RequestBody request: MutableMap<String, Any?>): String? {val contentBase64 = request["content_base64"] as String? ?: return ""return this.grpcScriptExecuter.exec(ScriptContent(content = contentBase64.base64Decode(),extractParams = request["extract_params"] as List<String>? ?: mutableListOf())).result}
}

执行脚本的GrpcScriptExecuter,内容如下:

package com.kamjin.javacallpython.grpc.demo.handleimport com.google.protobuf.*
import com.kamjin.javacallpython.grpc.demo.proto.*
import net.devh.boot.grpc.client.inject.*
import org.springframework.stereotype.*/*** <p>** </p>** @author kam* @since 2024/01/08*/
interface ScriptExecute {fun exec(content: ScriptContent): ScriptExecResult
}data class ScriptContent(val content: String,val extractParams: List<String> = mutableListOf()
)data class ScriptExecResult(val result: String? = null)@Component
class GrpcScriptExecuter : ScriptExecute {@GrpcClient("scriptServiceGrpc")private lateinit var scriptStub: ScriptServiceGrpc.ScriptServiceBlockingStuboverride fun exec(content: ScriptContent): ScriptExecResult {val c = content.contentif (c.isBlank()) return ScriptExecResult()val extractParams = content.extractParamsval r = ScriptProto.ScriptRequest.newBuilder().setContent(c).apply {if (extractParams.isNotEmpty()) {this.extractParams = ListValue.newBuilder().apply {for (ep in extractParams) {this.addValues(Value.newBuilder().setStringValue(ep).build())}}.build()}}.build()try {return ScriptExecResult(scriptStub.execute(r).result)} catch (e: io.grpc.StatusRuntimeException) {throw RuntimeException("script exec error,msg: ${e.message}", e)}}}
  • @GrpcClient("scriptServiceGrpc")的值对应的则是上一步中在appliation.yaml中配置的值
  • 当前文件做了两件事:
    1.定义一个ScriptExecute的interface和请求/响应的data class
    2.实现了GrpcScriptExecuter,用于通过调用grpc server端执行脚本内容

这样就完成了java端grpc client的创建。

三、python端

0.安装protobuf插件

同样需要安装protobuf插件,上文已经描述过了(idea plugin)不再赘述

1.创建项目

创建一个python venv项目,在模块中创建一个新的文件夹:proto_test

2.复制proto文件

把之前定义的script.proto文件复制到其中,要求和java服务端放入的文件保持一致,不用做任何改动。

3.生成代码

转到控制台,使用pip安装需要的依赖

pip install grpcio
pip install grpcio-tools googleapis-common-protos

然后进入proto_test目录,生成相应的grpc代码

python -m grpc_tools.protoc -I . --python_out=. --grpc_python_out=. script.proto

此时会在proto_test目录下生成文件:script_pb2_grpc.pyscript_pb2.py,后面会用到。

4.编写grpc server代码

创建文件:script_server.py,内容如下:

import jsonimport grpc
import script_pb2
import script_pb2_grpc
from concurrent import futures
import time_ONE_DAY_IN_SECONDS = 60 * 60 * 24# service impl
class ScriptServicer(script_pb2_grpc.ScriptServiceServicer):def Execute(self, request, context):s = request.contentresult = {}print("content: %s" % s)exec(s, result)# 根据传入的参数提取值data = {}for p in request.extract_params:data[p] = result.get(p, None)return script_pb2.ScriptResponse(result=json.dumps(data))def serve():server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))script_pb2_grpc.add_ScriptServiceServicer_to_server(ScriptServicer(), server)server.add_insecure_port('[::]:50051')server.start()try:while True:time.sleep(_ONE_DAY_IN_SECONDS)except KeyboardInterrupt:server.stop(0)if __name__ == '__main__':serve()

这样就完成了python端grpc server的创建。

四、验证

1.启动java服务:通过IDEA运行WEB服务
2.启动python服务:python script_server.py
3.使用postman或者IDEA httpclient调用接口,这里使用IDEA的http client
定义文件javacallpython-grpc.http

POST http://localhost:8080/test/proto/script
Content-Type: application/json{"content_base64": "aW1wb3J0IG1hdGgKZGVmIGZ1biAobik6CiAgICBkYXRhID0gbgogICAgZGF0YSA9IGRhdGEgKiBtYXRoLnBpCiAgICByZXR1cm4gZGF0YQpyID0gZnVuKDEwKQ==","extract_params": ["r"]
}

运行该调用,这将会调用刚刚启动的web服务(端口为8080默认)接口:/test/proto/script

  • 此处传的content_base64是因为json中不支持’‘’‘’'标注的字符串,也就没法满足python的缩进要求,故将脚本内容转为base64传入,实际脚本内容为:
import math
def fun (n):data = ndata = data * math.pireturn data
r = fun(10)

转为base64后:

aW1wb3J0IG1hdGgKZGVmIGZ1biAobik6CiAgICBkYXRhID0gbgogICAgZGF0YSA9IGRhdGEgKiBtYXRoLnBpCiAgICByZXR1cm4gZGF0YQpyID0gZnVuKDEwKQ==
  • extract_params是表明我们需要提取脚本中变量名称为r的内容的值作为脚本执行结果返回。

python端控制台打印:
在这里插入图片描述

http client执行结果:

在这里插入图片描述

这表明带import的脚本执行成功,并正确返回了我们想要提取的值

参考文章

1.拥抱云原生,Java与Python基于gRPC通信
2.base64和字符串互转
3.Import Lib not working with exec function?
4.yidongnan/grpc-spring-boot-starter
5.google/protobuf-gradle-plugin

结语

本文实现了通过grpc在java端传入脚本内容,在python端执行的脚本的实现方法,性能状况未测试,后续如果有时间会对其进行使用验证,如果发现问题,可以做相关改进,会在本文进行更新,本文的实现对实际项目中的使用具有一定的参考价值。
后面会继续更新分享更多相关内容,请多多关注~

最后,各位看众可以思考一下:

为什么以上做法可以成功执行带import的脚本?

相关文章:

【grpc】利用protobuf实现java或kotlin调用python脚本,含实现过程和全部代码

前言 在一些特殊场景中&#xff0c;我们可能需要使用java或者其他任意语言调用python脚本或sdk等。本文的需求衍生也不例外于此&#xff0c;python端有sdk&#xff0c;但只能在python中调用&#xff0c;于是就有了本文章。 常见的调用方式如jython、python提供http rest接口、…...

Linux网络 ----- 网络文件共享服务之FTP服务

引言 FTP服务是Internet上最早应用于主机之间进行数据传输的基本服务之一。是目前Internet上使用最广泛的文件传送协议 一、FTP概述 FTP(File TransferProtocol&#xff0c;文件传输协议)是典型的C/S架构的应用层协议&#xff0c;需要由服务端软件、客户端软件两个部分共同实…...

如何避免知识付费小程序平台的陷阱?搭建平台的最佳实践

随着知识经济的兴起&#xff0c;知识付费已经成为一种趋势。越来越多的人开始将自己的知识和技能进行变现&#xff0c;而知识付费小程序平台则成为了一个重要的渠道。然而&#xff0c;市面上的知识付费小程序平台琳琅满目&#xff0c;其中不乏一些不良平台&#xff0c;让老实人…...

第89讲:MySQL数据库迁移方面需要考虑的因素以及XBK企业级备份参数

文章目录 MySQL数据库迁移方面需要考虑的因素1.MySQL数据库迁移方面要考虑的因素2.MySQL5.6升级到5.7版本的方法3.MySQL迁移到其他数据库的方法4.为什么要从XBK备份中还原某张表的数据5.从XBK备份中还原某张表的数据6.XtrBackup企业级备份参数 MySQL数据库迁移方面需要考虑的因…...

Python爬虫经典实战项目——电商数据爬取!

电商数据采集爬虫背景 在如今这个网购风云从不间歇的时代&#xff0c;购物狂欢持续不断&#xff0c;一年一度的“6.18年中大促”、“11.11购物节”等等成为了网购电商平台的盛宴。在买买买的同时&#xff0c;“如何省钱&#xff1f;”成为了大家最关心的问题。 比价、返利、优…...

Qt 快捷键设置

以 “在编辑时自动补齐”快捷键 为例&#xff1a; 位置&#xff1a;红色 搜索快捷键&#xff1a;蓝色 修改方式&#xff1a;绿色 快捷键&#xff1a;黄色...

【C++】取整函数ceil(),floor(),round()

使用 //引入头文件 #include <cmath> //函数使用 double around(double x) double afloor(double x) double aceil(double x) 结果取值 floor(x) 返回是小于或等于x的最大整数&#xff0c;如floor(-9.9)-10,floor(9.9)9&#xff1b;若为整数&#xff0c;最后的结果等于本…...

GoLang刷题之leetcode

题目42&#xff1a;接雨水 题目描述&#xff1a; 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水 题解&#xff1a; 对于每一个横坐标能接到的雨水量max&#xff08;左侧最大纵坐标&#xff0c;右侧最大…...

uniapp如何分包加载

在uni-app中&#xff0c;可以通过配置manifest.json文件来实现分包加载。以下是具体操作步骤&#xff1a; 在项目根目录下找到并打开manifest.json文件。在"uni-app"节点中&#xff0c;找到并修改"subPackages"节点&#xff0c;添加需要分包的页面路径。 …...

019、错误处理:不可恢复错误与panic!

鉴于上一篇文章过长&#xff0c;不方便大家阅读和理解&#xff0c;因此关于Rust中的错误处理&#xff0c; 我将分以下3篇来讲。 另外&#xff0c;随着我们学习的不断深入&#xff0c;难度也会越来越大&#xff0c;但不用担心。接下来只需要让自己的脚步慢一些&#xff0c;认真搞…...

jar包部署到linux虚拟机的docker中之后连不上mysql

前言&#xff1a; 跟着黑马学习docker的时候&#xff0c;将java项目部署到了docker中&#xff0c;运行访问报错&#xff0c;反馈连不上mysql。 错误描述&#xff1a; 方法解决&#xff1a; 概述&#xff1a;在虚拟中中&#xff0c;我进入项目容器的内部&#xff0c;尝试ping…...

如何筛选小红书护肤达人,笔记类型怎么选?

网络时代&#xff0c;借助KOL来放大产品的声量和产量&#xff0c;是品牌的常见策略&#xff0c;但是&#xff0c;不同的产品对应不同的KOL&#xff0c;价值是完全不一样。如何筛选达人&#xff0c;已经成为了品牌方的必修课!今天我们和大家分享下如何筛选小红书护肤达人&#x…...

红黑树(RBTree)

目录​​​​​​​ 一、红黑树简介 二、红黑树的来源 三、什么是红黑树 四、红黑树的性质 五、红黑树的节点定义 六、红黑树的操作 6.1、红黑树的查找 6.2、红黑树的插入 七、红黑树的验证 八、红黑树和AVL树的比较 一、红黑树简介 红黑树是一种自平衡的二叉查找树…...

训练YOLOS-S

文章目录 1 数据处理2 配置训练参数3 可能会遇到的报错 1 数据处理 修改类别数&#xff1a;在models/detector.py中定位到def build(args):&#xff0c;将num_classes进行修改&#xff0c;改为最大的类别id1。我有4个类别&#xff0c;类别id是从0~3&#xff0c;因此max_id3&am…...

集成SpringCloudAlibaba短信服务 短信验证码

1.1 SpringCloudAlibaba短信服务简介 短信服务&#xff08;Short Message Service&#xff09;是阿里云为用户提供的一种通信服务的能力。 产品优势&#xff1a;覆盖全面、高并发处理、消息堆积处理、开发管理简单、智能监控调度 产品功能&#xff1a;短信通知、短信验证码、…...

存储卷(数据卷)—主要是nfs方式挂载

1、定义 容器内的目录和宿主机的目录进行挂载 容器在系统上的生命周期是短暂的&#xff0c;一旦容器被删除&#xff0c;数据会丢失。k8s基于控制器创建的pod&#xff0c;delete相当于重启&#xff0c;容器的状态会恢复到原始状态。一旦回到原始状态&#xff0c;后天编辑的文件…...

城市酷选模式开发(门店免单排队返利系统)

城市酷选模式开发&#xff08;门店免单排队返利系统&#xff09;【阿巴】城市酷选商城开发免单排队返利小程序搭建、城市酷选模式开发、城市酷选系统商城开发、城市酷选APP系统开发、城市酷选 每经AI快讯&#xff0c;有投资者在投资者互动平台提问&#xff1a;“以塑代钢”已成…...

JNPF低代码引擎到底是什么?

最近听说一款可以免费部署本地进行试用的低代码引擎&#xff0c;源码上支持100%源码&#xff0c;提供的功能和技术支持比较完善。借助这篇篇幅我们了解下JNPF到底是什么&#xff1f; JNPF开发平台是一款PaaS服务为核心的零代码开发平台&#xff0c;平台提供了多租户账号管理、主…...

#基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件

我们在使用jupyter 写代码后&#xff0c;经常遇到一些写完想把文件转成markdown格式的场景&#xff0c;这里就教你怎么处理相关的问题 使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件 pip install nbconvert pip install pandoc jupyter nbconvert --to markdown 文…...

工信部颁发的人工智能证书《自然语言与语音处理设计开发工程师》证书到手啦!

工信部颁发的人工智能证书《自然语言与语音处理设计开发工程师》证书拿到手啦&#xff01; 近期正在报考的工信部颁发的人工智能证书还有&#xff1a; 《计算机视觉处理设计开发工程师》中级 2024年1月24日至28日-北京 《自然语言与语音处理设计开发工程师》中级 第二期 20…...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

C++_核心编程_多态案例二-制作饮品

#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为&#xff1a;煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例&#xff0c;提供抽象制作饮品基类&#xff0c;提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

在rocky linux 9.5上在线安装 docker

前面是指南&#xff0c;后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

JDK 17 新特性

#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持&#xff0c;不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的&#xff…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建

华为云FlexusDeepSeek征文&#xff5c;DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色&#xff0c;华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型&#xff0c;能助力我们轻松驾驭 DeepSeek-V3/R1&#xff0c;本文中将分享如何…...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

云原生玩法三问:构建自定义开发环境

云原生玩法三问&#xff1a;构建自定义开发环境 引言 临时运维一个古董项目&#xff0c;无文档&#xff0c;无环境&#xff0c;无交接人&#xff0c;俗称三无。 运行设备的环境老&#xff0c;本地环境版本高&#xff0c;ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

springboot整合VUE之在线教育管理系统简介

可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生&#xff0c;小白用户&#xff0c;想学习知识的 有点基础&#xff0c;想要通过项…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...