// gRPC 서버 및 클라이언트 스타터
implementation 'net.devh:grpc-server-spring-boot-starter:3.1.0.RELEASE'
implementation 'net.devh:grpc-client-spring-boot-starter:3.1.0.RELEASE'
// gRPC와 Protobuf
implementation 'io.grpc:grpc-netty-shaded:1.64.0'
implementation 'io.grpc:grpc-protobuf:1.64.0'
implementation 'io.grpc:grpc-stub:1.64.0'
// .proto build 시 사용
implementation 'javax.annotation:javax.annotation-api:1.3.2'
// Protobuf 설정
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.25.3" // protoc 컴파일러 최신 버전
}
plugins {
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:1.64.0" // gRPC 플러그인 최신 버전
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}
// 생성된 코드가 classpath에 포함되도록 설정
sourceSets {
main {
java {
srcDirs 'build/generated/source/proto/main/grpc', 'build/generated/source/proto/main/java'
}
}
}
\src\main\proto
syntax = "proto3";
// Java 코드 생성 시 각 메시지 및 서비스에 대해 별도의 .java 파일을 생성하도록 합니다. (true: 각자 파일, false: 단일 파일 안에 중첩)
option java_multiple_files = true;
// 생성될 Java 클래스들이 위치할 패키지명을 지정합니다.
option java_package = "com.example.grpc.service";
// 생성될 Java 외부 클래스(Outer Class)의 이름을 지정합니다. (java_multiple_files가 false일 때 주로 사용)
option java_outer_classname = "HelloServiceProto";
// Protocol Buffers 내부에서 사용될 패키지명을 정의
package com.example.grpc;
// Greeting 기능을 제공하는 서비스 'HelloService'를 정의합니다.
service HelloService {
// 단방향(Unary) RPC 메서드를 정의합니다.
// 클라이언트가 'HelloRequest' 메시지를 보내면, 서버는 'HelloResponse' 메시지로 응답합니다.
rpc requestMessage (HelloRequest) returns (HelloResponse);
}
// 클라이언트가 서버로 보낼 요청 메시지 'HelloRequest'의 구조를 정의합니다.
message HelloRequest {
string jsonString = 1;
}
// 서버가 클라이언트에 보낼 응답 메시지 'HelloResponse'의 구조를 정의합니다.
message HelloResponse {
string jsonString = 1;
}
grpc client에 응답 받은 데이터를 로그로 적재 후 "요청 받은 메세지 : requestMessge" 로 응답해주는 코드
server:
port: 8081
# src/main/resources/application.yml
grpc:
server:
port: 9090 # gRPC 통신용 서버 포트
@Slf4j
@GrpcService
public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase {
@Override
public void requestMessage(HelloRequest request, StreamObserver<HelloResponse> responseObserver) {
String requestMessage = request.getJsonString();
log.info("클라이언트로부터 요청 수신: {}", requestMessage);
// 응답 메시지 생성
HelloResponse response = HelloResponse.newBuilder()
.setJsonString("요청 받은 메세지 : " + requestMessage )
.build();
// 응답 전송
// 생성된 응답 메시지를 클라이언트에게 전송합니다.
// 'onNext()' 메서드는 응답 스트림에 하나의 메시지를 발행합니다.
responseObserver.onNext(response);
// 클라이언트에게 모든 응답 메시지 전송이 완료되었음을 알립니다.
// 이 메서드를 호출하면 RPC 호출이 종료됩니다.
responseObserver.onCompleted();
}
}
rest로 받은 데이터를 grpc 서버에 메세지를 보내 응답을 받는 코드
grpc:
client:
local-grpc-server:
address: 'static://localhost:9090'
enableKeepAlive: true
negotiationType: plaintext # 개발 환경에서만 사용, 실제 운영에서는 TLS 권장
@Slf4j
@Service
public class GrpcClientService {
/**
* 스텁은 클라이언트 측에서 원격 서버의 서비스를 로컬 객체처럼 호출할 수 있게 해주는 프록시(Proxy) 객체
* 클라이언트 코드와 실제 서버 간의 중간 다리 역할
*/
// gRPC 서버의 Stub을 주입. blockingStub은 동기 호출을 지원합니다.
@GrpcClient("local-grpc-server")
private HelloServiceGrpc.HelloServiceBlockingStub helloServiceStub;
@GrpcClient("local-grpc-server")
private HelloServiceGrpc.HelloServiceStub helloServiceAsyncStub;
public String sendMessage(String requestMessage) {
log.info("서버로 'requestMessage' 요청을 보냅니다.");
HelloRequest request = HelloRequest.newBuilder()
.setJsonString(requestMessage)
.build();
HelloResponse response = helloServiceStub.requestMessage(request);
log.info("서버로부터 응답 수신: {}", response.getJsonString());
return response.getJsonString();
}
public CompletableFuture<String> sendMessageAsync(String requestMessage) {
log.info("[비동기 호출] 서버로 'requestMessage' 비동기 요청을 보냅니다. (요청 스레드: {})", Thread.currentThread().getName());
CompletableFuture<String> future = new CompletableFuture<>();
HelloRequest request = HelloRequest.newBuilder()
.setJsonString(requestMessage)
.build();
helloServiceAsyncStub.requestMessage(request, new StreamObserver<HelloResponse>() {
@Override
public void onNext(HelloResponse response) {
log.info("[비동기 콜백] 서버로부터 비동기 응답 수신: {} (콜백 스레드: {})", response.getJsonString(), Thread.currentThread().getName());
future.complete(response.getJsonString());
}
@Override
public void onError(Throwable t) {
log.error("[비동기 콜백] 비동기 호출 중 오류 발생: {} (콜백 스레드: {})", t.getMessage(), Thread.currentThread().getName(), t);
future.completeExceptionally(t);
}
@Override
public void onCompleted() {
log.info("[비동기 콜백] 비동기 호출 완료. (콜백 스레드: {})", Thread.currentThread().getName());
}
});
log.info("[비동기 호출] sendMessageAsync 메서드 종료. CompletableFuture 반환. (요청 스레드: {})", Thread.currentThread().getName());
return future;
}
}
@RestController
@RequiredArgsConstructor
public class HelloController {
private final GrpcClientService grpcClientService;
@GetMapping("/hello")
public String requestMessage(@RequestParam(name = "message" ) String message) {
return grpcClientService.sendMessage(message);
}
@GetMapping("/helloAsync")
public CompletableFuture<String> requestMessageAsync(@RequestParam(name = "message" ) String message) {
return grpcClientService.sendMessageAsync(message);
}
}
postmen
client log
2025-07-29T13:06:25.685+09:00 INFO 31092 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2025-07-29T13:06:25.685+09:00 INFO 31092 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2025-07-29T13:06:25.686+09:00 INFO 31092 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
2025-07-29T13:06:25.735+09:00 INFO 31092 --- [nio-8080-exec-2] c.d.g.grpc.GrpcClientService : 서버로 'requestMessage' 요청을 보냅니다.
2025-07-29T13:06:26.275+09:00 INFO 31092 --- [nio-8080-exec-2] c.d.g.grpc.GrpcClientService : 서버로부터 응답 수신: 요청 받은 메세지 : karim
2025-07-29T13:07:45.421+09:00 INFO 19324 --- [ault-executor-1] c.d.g.grpc.HelloServiceImpl : 클라이언트로부터 요청 수신: karim
📌
📚 참고