【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.py、script_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脚本,含实现过程和全部代码
前言 在一些特殊场景中,我们可能需要使用java或者其他任意语言调用python脚本或sdk等。本文的需求衍生也不例外于此,python端有sdk,但只能在python中调用,于是就有了本文章。 常见的调用方式如jython、python提供http rest接口、…...
Linux网络 ----- 网络文件共享服务之FTP服务
引言 FTP服务是Internet上最早应用于主机之间进行数据传输的基本服务之一。是目前Internet上使用最广泛的文件传送协议 一、FTP概述 FTP(File TransferProtocol,文件传输协议)是典型的C/S架构的应用层协议,需要由服务端软件、客户端软件两个部分共同实…...
如何避免知识付费小程序平台的陷阱?搭建平台的最佳实践
随着知识经济的兴起,知识付费已经成为一种趋势。越来越多的人开始将自己的知识和技能进行变现,而知识付费小程序平台则成为了一个重要的渠道。然而,市面上的知识付费小程序平台琳琅满目,其中不乏一些不良平台,让老实人…...
第89讲:MySQL数据库迁移方面需要考虑的因素以及XBK企业级备份参数
文章目录 MySQL数据库迁移方面需要考虑的因素1.MySQL数据库迁移方面要考虑的因素2.MySQL5.6升级到5.7版本的方法3.MySQL迁移到其他数据库的方法4.为什么要从XBK备份中还原某张表的数据5.从XBK备份中还原某张表的数据6.XtrBackup企业级备份参数 MySQL数据库迁移方面需要考虑的因…...
Python爬虫经典实战项目——电商数据爬取!
电商数据采集爬虫背景 在如今这个网购风云从不间歇的时代,购物狂欢持续不断,一年一度的“6.18年中大促”、“11.11购物节”等等成为了网购电商平台的盛宴。在买买买的同时,“如何省钱?”成为了大家最关心的问题。 比价、返利、优…...
Qt 快捷键设置
以 “在编辑时自动补齐”快捷键 为例: 位置:红色 搜索快捷键:蓝色 修改方式:绿色 快捷键:黄色...
【C++】取整函数ceil(),floor(),round()
使用 //引入头文件 #include <cmath> //函数使用 double around(double x) double afloor(double x) double aceil(double x) 结果取值 floor(x) 返回是小于或等于x的最大整数,如floor(-9.9)-10,floor(9.9)9;若为整数,最后的结果等于本…...
GoLang刷题之leetcode
题目42:接雨水 题目描述: 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水 题解: 对于每一个横坐标能接到的雨水量max(左侧最大纵坐标,右侧最大…...
uniapp如何分包加载
在uni-app中,可以通过配置manifest.json文件来实现分包加载。以下是具体操作步骤: 在项目根目录下找到并打开manifest.json文件。在"uni-app"节点中,找到并修改"subPackages"节点,添加需要分包的页面路径。 …...
019、错误处理:不可恢复错误与panic!
鉴于上一篇文章过长,不方便大家阅读和理解,因此关于Rust中的错误处理, 我将分以下3篇来讲。 另外,随着我们学习的不断深入,难度也会越来越大,但不用担心。接下来只需要让自己的脚步慢一些,认真搞…...
jar包部署到linux虚拟机的docker中之后连不上mysql
前言: 跟着黑马学习docker的时候,将java项目部署到了docker中,运行访问报错,反馈连不上mysql。 错误描述: 方法解决: 概述:在虚拟中中,我进入项目容器的内部,尝试ping…...
如何筛选小红书护肤达人,笔记类型怎么选?
网络时代,借助KOL来放大产品的声量和产量,是品牌的常见策略,但是,不同的产品对应不同的KOL,价值是完全不一样。如何筛选达人,已经成为了品牌方的必修课!今天我们和大家分享下如何筛选小红书护肤达人&#x…...
红黑树(RBTree)
目录 一、红黑树简介 二、红黑树的来源 三、什么是红黑树 四、红黑树的性质 五、红黑树的节点定义 六、红黑树的操作 6.1、红黑树的查找 6.2、红黑树的插入 七、红黑树的验证 八、红黑树和AVL树的比较 一、红黑树简介 红黑树是一种自平衡的二叉查找树…...
训练YOLOS-S
文章目录 1 数据处理2 配置训练参数3 可能会遇到的报错 1 数据处理 修改类别数:在models/detector.py中定位到def build(args):,将num_classes进行修改,改为最大的类别id1。我有4个类别,类别id是从0~3,因此max_id3&am…...
集成SpringCloudAlibaba短信服务 短信验证码
1.1 SpringCloudAlibaba短信服务简介 短信服务(Short Message Service)是阿里云为用户提供的一种通信服务的能力。 产品优势:覆盖全面、高并发处理、消息堆积处理、开发管理简单、智能监控调度 产品功能:短信通知、短信验证码、…...
存储卷(数据卷)—主要是nfs方式挂载
1、定义 容器内的目录和宿主机的目录进行挂载 容器在系统上的生命周期是短暂的,一旦容器被删除,数据会丢失。k8s基于控制器创建的pod,delete相当于重启,容器的状态会恢复到原始状态。一旦回到原始状态,后天编辑的文件…...
城市酷选模式开发(门店免单排队返利系统)
城市酷选模式开发(门店免单排队返利系统)【阿巴】城市酷选商城开发免单排队返利小程序搭建、城市酷选模式开发、城市酷选系统商城开发、城市酷选APP系统开发、城市酷选 每经AI快讯,有投资者在投资者互动平台提问:“以塑代钢”已成…...
JNPF低代码引擎到底是什么?
最近听说一款可以免费部署本地进行试用的低代码引擎,源码上支持100%源码,提供的功能和技术支持比较完善。借助这篇篇幅我们了解下JNPF到底是什么? JNPF开发平台是一款PaaS服务为核心的零代码开发平台,平台提供了多租户账号管理、主…...
#基础#使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件
我们在使用jupyter 写代码后,经常遇到一些写完想把文件转成markdown格式的场景,这里就教你怎么处理相关的问题 使用Jupyter进行Notebook的转换 .ipynb文件导出为.md文件 pip install nbconvert pip install pandoc jupyter nbconvert --to markdown 文…...
工信部颁发的人工智能证书《自然语言与语音处理设计开发工程师》证书到手啦!
工信部颁发的人工智能证书《自然语言与语音处理设计开发工程师》证书拿到手啦! 近期正在报考的工信部颁发的人工智能证书还有: 《计算机视觉处理设计开发工程师》中级 2024年1月24日至28日-北京 《自然语言与语音处理设计开发工程师》中级 第二期 20…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...
《Docker》架构
文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器,docker,镜像,k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...
Spring AOP代理对象生成原理
代理对象生成的关键类是【AnnotationAwareAspectJAutoProxyCreator】,这个类继承了【BeanPostProcessor】是一个后置处理器 在bean对象生命周期中初始化时执行【org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization】方法时…...
负载均衡器》》LVS、Nginx、HAproxy 区别
虚拟主机 先4,后7...
李沐--动手学深度学习--GRU
1.GRU从零开始实现 #9.1.2GRU从零开始实现 import torch from torch import nn from d2l import torch as d2l#首先读取 8.5节中使用的时间机器数据集 batch_size,num_steps 32,35 train_iter,vocab d2l.load_data_time_machine(batch_size,num_steps) #初始化模型参数 def …...
PLC入门【4】基本指令2(SET RST)
04 基本指令2 PLC编程第四课基本指令(2) 1、运用上接课所学的基本指令完成个简单的实例编程。 2、学习SET--置位指令 3、RST--复位指令 打开软件(FX-TRN-BEG-C),从 文件 - 主画面,“B: 让我们学习基本的”- “B-3.控制优先程序”。 点击“梯形图编辑”…...
