我有一个 grpc 服务,这里是代码摘录: 原型:
// proto/campus/graduate/v1/graduate.proto
syntax = 'proto3';
package wusthelper.campus.graduate.v1;
option java_package = 'cn.wustlinghang.wusthelper.rpc.graduate.v1';
option java_outer_classname = 'GraduateProto';
option java_generic_services = true;
service GraduateRequestAgent {
// Login Login service for graduate
rpc Login (UsernamePasswordReq) returns (AgentReply);
// GetStudentInfo Get the student info
rpc GetStudentInfo (CookieReq) returns (AgentReply);
//... more
}
service GraduateParser {
// ParseScores Parse the score page
rpc ParseScores (PlainText) returns (ScoreParseReply);
//... more
}
这是服务器实现:
package cn.wustlinghang.wusthelper.campus.graduate.service;
import net.devh.boot.grpc.server.service.GrpcService;
@GrpcService
public class GraduateRequestAgentImpl extends GraduateRequestAgentGrpc.GraduateRequestAgentImplBase {
@Override
public void login(CampusCommonProto.UsernamePasswordReq request,
StreamObserver<CampusCommonProto.AgentReply> responseObserver) {
//...implements
}
//... other implements
}
package cn.wustlinghang.wusthelper.campus.graduate.service;
import net.devh.boot.grpc.server.service.GrpcService;
@GrpcService
public class GraduateParserImpl extends GraduateParserGrpc.GraduateParserImplBase {
@Override
public void parseScores(CommonProto.PlainText request,
StreamObserver<GraduateProto.ScoreParseReply> responseObserver) {
// ...implements
}
}
奇怪的是,当我在idea中启动服务器并调用
login()
或 ParseScores()
时,一切都如我所料。
但是当我将服务器项目打包成jar文件时,如果我调用login()
等方法,我会收到io.grpc.StatusRuntimeException: UNIMPLEMENTED: Method not found: wusthelper.campus.graduate.v1.GraduateRequestAgent/Login
异常。只有当我将服务器打包为jar文件时才会出现该问题。
如果我使用像
grpcurl
或evans
这样的grpc调试工具,无论我将服务器打包为jar还是直接在idea中运行,它们都可以很好地工作,但我就是无法从客户端调用。 😰😫
客户端使用的生成的proto代码与服务器使用的相同,并且服务器日志显示它实际上加载了实现服务:
2023-04-06T20:23:37.358+08:00 INFO 27868 --- [ main] g.s.a.GrpcServerFactoryAutoConfiguration : Detected grpc-netty-shaded: Creating ShadedNettyGrpcServerFactory
2023-04-06T20:23:37.507+08:00 INFO 27868 --- [ main] n.d.b.g.s.s.AbstractGrpcServerFactory : Registered gRPC service: wusthelper.campus.graduate.v1.GraduateParser, bean: graduateParserImpl, class: cn.wustlinghang.wusthelper.campus.graduate.service.GraduateParserImpl
2023-04-06T20:23:37.507+08:00 INFO 27868 --- [ main] n.d.b.g.s.s.AbstractGrpcServerFactory : Registered gRPC service: wusthelper.campus.graduate.v1.GraduateRequestAgent, bean: graduateRequestAgentImpl, class: cn.wustlinghang.wusthelper.campus.graduate.service.GraduateRequestAgentImpl
2023-04-06T20:23:37.507+08:00 INFO 27868 --- [ main] n.d.b.g.s.s.AbstractGrpcServerFactory : Registered gRPC service: grpc.health.v1.Health, bean: grpcHealthService, class: io.grpc.protobuf.services.HealthServiceImpl
2023-04-06T20:23:37.507+08:00 INFO 27868 --- [ main] n.d.b.g.s.s.AbstractGrpcServerFactory : Registered gRPC service: grpc.reflection.v1alpha.ServerReflection, bean: protoReflectionService, class: io.grpc.protobuf.services.ProtoReflectionService
2023-04-06T20:23:37.632+08:00 INFO 27868 --- [ main] n.d.b.g.s.s.GrpcServerLifecycle : gRPC Server started, listening on address: 0.0.0.0, port: 19090
2023-04-06T20:23:37.640+08:00 INFO 27868 --- [ main] c.w.w.c.graduate.GraduateServiceMain : Started GraduateServiceMain in 1.024 seconds (process running for 1.417)
调试日志也和正常运行时一样。
即使我尝试用 Golang 重写客户端代码,也得到了相同的行为。
环境:
我也尝试过删除 springboot 和 grpc-starter 并将其重写为 prue grpc 服务,但没有用。😰
好吧,我又遇到了这个问题,现在对我来说似乎有点傻😂。我已经弄清楚发生了什么事。主要是没有理解Maven内部Springboot项目中依赖的打包机制……
我希望这可以帮助您,或者在您束手无策时提供一些故障排除的想法。
这个项目的Maven模块结构如下(大致是这样,已经很久了,不太记得了,项目现在已经不再使用grpc了😂):
backend-project
- common // the common module that module 'services' and 'gateway' module depends, contains the proto file and protoc generated code
- services // services that implemented the grpc service, the grpc server
- graduate // the graduate service, where file 'GraduateRequestAgentImpl.java' in.
- undergrad // the undergrad service
- library
- ... and so on
- web // web service, for frontend
- gateway // the api gateway that will call the service rpc, the grpc client main
我的项目的顶级父级不是springboot父级,而是项目的根模块。也就是说,它不是任何模块的“子级”,它是父级,是下面所有模块的父级。
我更新了common的proto文件并重新生成了相应的Java proto代码文件,并在各自的服务器上实现了它们,但我没有执行mvn install操作。
后来我反编译了客户端和服务端的jar包,发现里面实际的公共模块代码是不一样的😥。在服务端的jar包中,生成的代码确实有新添加的部分,但是在客户端(网关模块)中,却出奇的没有😰。
我对公共模块执行mvm install操作后,一切都很好,双方都没有问题,一切正常。
但是为什么这在 IDEA 中不是问题呢?
IDEA中的‘运行命令’是这样的:
C:\Users\user\.jdks\graalvm-ce-17\bin\java.exe
-classpath '
C:\Users\user\projects\backend-project\common\target\classes;
C:\Users\user\projects\backend-project\web\gateway\target\classes;
C:\Users\user\.m2\repository\org\springframework\boot\spring-boot-starter-web\3.1.1\spring-boot-starter-web-3.1.1.jar;
... and many dependences
C:\Users\user\.m2\repository\org\springframework\boot\spring-boot-starter\3.1.1\spring-boot-starter-3.1.1.jar;
C:\Users\user\.m2\repository\org\springframework\boot\spring-boot-starter-logging\3.1.1\spring-boot-starter-logging-3.1.1.jar;'
# the client main class
wusthelper.WebBackendMain
可以看到依赖项是通过指定所有依赖项的类路径来导入的,并且其他模块是从编译后的类文件中导入的。显然,那里的文件是新的。
将客户端打包为jar时,依赖项是从maven本地存储库导入的,而不是
target
文件夹。所以客户端仍在使用 common
模块的遗留代码。
这就是为什么我在公共模块中运行公共
mvn install
,问题解决了。
唷