├── .gitignore ├── LICENSE ├── README.md ├── samples-client ├── Dockerfile ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── anoyi │ │ └── grpc │ │ └── client │ │ ├── Application.java │ │ └── controller │ │ ├── V1UserController.java │ │ ├── V2UserController.java │ │ └── V3UserController.java │ └── resources │ └── application.yaml ├── samples-facade ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── anoyi │ └── grpc │ └── facade │ ├── entity │ ├── PetEntity.java │ └── UserEntity.java │ └── service │ ├── UserServiceByFastJSON.java │ ├── UserServiceByProtoStuff.java │ └── UserServiceBySofaHessian.java ├── samples-server ├── Dockerfile ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── anoyi │ │ └── grpc │ │ └── server │ │ ├── Application.java │ │ ├── controller │ │ └── V0UserController.java │ │ └── service │ │ ├── UserServiceByFastJSONImpl.java │ │ ├── UserServiceByProtoStuffImpl.java │ │ └── UserServiceBySofaHessianImpl.java │ └── resources │ └── application.yml ├── spring-boot-starter-grpc ├── README.md ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── anoyi │ │ └── grpc │ │ ├── GrpcClient.java │ │ ├── GrpcServer.java │ │ ├── ServerContext.java │ │ ├── annotation │ │ ├── GrpcService.java │ │ └── GrpcServiceScan.java │ │ ├── binding │ │ └── GrpcServiceProxy.java │ │ ├── config │ │ ├── GrpcAutoConfiguration.java │ │ ├── GrpcProperties.java │ │ └── RemoteServer.java │ │ ├── constant │ │ ├── GrpcResponseStatus.java │ │ └── SerializeType.java │ │ ├── exception │ │ ├── GrpcException.java │ │ └── GrpcResponseException.java │ │ ├── service │ │ ├── CommonService.java │ │ ├── GrpcRequest.java │ │ ├── GrpcResponse.java │ │ ├── SerializeService.java │ │ └── impl │ │ │ ├── FastJSONSerializeService.java │ │ │ ├── ProtoStuffSerializeService.java │ │ │ └── SofaHessianSerializeService.java │ │ └── util │ │ ├── ClassNameUtils.java │ │ ├── ProtobufUtils.java │ │ └── SerializeUtils.java │ ├── proto │ └── service.proto │ └── resources │ └── META-INF │ ├── spring-configuration-metadata.json │ └── spring.factories ├── stack.yml └── test ├── README.md ├── pom.xml └── src └── test └── com.anoyi.grpc.test ├── V0LocalTest.scala ├── V1SofaHessianTest.scala ├── V2ProtoStuffTest.scala └── V3FastJsonTest.scala /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | */**/target 4 | 5 | */**/*.iml 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anoyi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 轻量级 RPC 框架 2 | 3 | Spring Boot 快速集成 gRPC,轻松实现远程方法调用。 4 | 5 | ![](https://upload-images.jianshu.io/upload_images/3424642-d75dbd4a26d8174d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 6 | 7 | ### 使用文档 8 | 9 | [新人入门文档](https://github.com/ChinaSilence/spring-boot-starter-grpc/wiki) 10 | 11 | ### 版本信息 12 | 13 | 名称|版本 14 | --|-- 15 | grpc-java|1.33.1 16 | spring-boot|2.4.0.RELEASE 17 | sofa-hessian|3.3.12 18 | fastjson|1.2.75 19 | -------------------------------------------------------------------------------- /samples-client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.cn-hangzhou.aliyuncs.com/micro-java/openjdk:8-jre-alpine 2 | MAINTAINER yangyuandong@tezign.com 3 | ENV TZ="Asia/Shanghai" HOME="/root" JVM_PARAMS=" " SPRING_PARAMS=" " 4 | WORKDIR ${HOME} 5 | ADD target/*.jar ${HOME}/ROOT.jar 6 | EXPOSE 8081 7 | CMD java $JVM_PARAMS -Djava.security.egd=file:/dev/./urandom -jar ${HOME}/ROOT.jar $SPRING_PARAMS -------------------------------------------------------------------------------- /samples-client/README.md: -------------------------------------------------------------------------------- 1 | ### 构建镜像 2 | 3 | 1、修改 src/main/resources/application.yaml 文件为: 4 | ``` 5 | server: 6 | port: 8081 7 | 8 | spring: 9 | grpc: 10 | remote-servers: 11 | - server: user 12 | host: server 13 | port: 6565 14 | ``` 15 | 16 | 2、Maven 打包 17 | ``` 18 | mvn clean package 19 | ``` 20 | 21 | 3、构建 Docker 镜像 22 | ``` 23 | docker build -t client . 24 | ``` -------------------------------------------------------------------------------- /samples-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 2.4.0 11 | 12 | 13 | com.anoyi.grpc 14 | samples-client 15 | 1.0-SNAPSHOT 16 | 17 | 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-web 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-logging 26 | 27 | 28 | 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-log4j2 34 | 35 | 36 | 37 | 38 | com.anoyi.grpc 39 | samples-facade 40 | 1.0-SNAPSHOT 41 | 42 | 43 | 44 | 45 | com.anoyi 46 | spring-boot-starter-grpc 47 | 2.4.0 48 | 49 | 50 | 51 | org.projectlombok 52 | lombok 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-maven-plugin 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /samples-client/src/main/java/com/anoyi/grpc/client/Application.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.client; 2 | 3 | import com.anoyi.grpc.annotation.GrpcServiceScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | @GrpcServiceScan(packages = {"com.anoyi.grpc.facade"}) 9 | public class Application { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(Application.class, args); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /samples-client/src/main/java/com/anoyi/grpc/client/controller/V1UserController.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.client.controller; 2 | 3 | import com.anoyi.grpc.facade.entity.UserEntity; 4 | import com.anoyi.grpc.facade.service.UserServiceBySofaHessian; 5 | import lombok.AllArgsConstructor; 6 | import org.springframework.web.bind.annotation.*; 7 | 8 | import java.util.List; 9 | 10 | @RestController 11 | @AllArgsConstructor 12 | @RequestMapping("/v1/user") 13 | public class V1UserController { 14 | 15 | private final UserServiceBySofaHessian userServiceBySofaHessian; 16 | 17 | @PostMapping("/add") 18 | public UserEntity insertUser(@RequestBody UserEntity userEntity){ 19 | userServiceBySofaHessian.insert(userEntity); 20 | return userEntity; 21 | } 22 | 23 | @GetMapping("/list") 24 | public List findAllUser(){ 25 | return userServiceBySofaHessian.findAll(); 26 | } 27 | 28 | @PostMapping("/remove") 29 | public String removeUser(@RequestParam("id") Long id){ 30 | userServiceBySofaHessian.deleteById(id); 31 | return "success"; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /samples-client/src/main/java/com/anoyi/grpc/client/controller/V2UserController.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.client.controller; 2 | 3 | import com.anoyi.grpc.facade.entity.UserEntity; 4 | import com.anoyi.grpc.facade.service.UserServiceByProtoStuff; 5 | import lombok.AllArgsConstructor; 6 | import org.springframework.web.bind.annotation.*; 7 | 8 | import java.util.List; 9 | 10 | @RestController 11 | @AllArgsConstructor 12 | @RequestMapping("/v2/user") 13 | public class V2UserController { 14 | 15 | private final UserServiceByProtoStuff userServiceByProtoStuff; 16 | 17 | @PostMapping("/add") 18 | public UserEntity insertUser(@RequestBody UserEntity userEntity){ 19 | userServiceByProtoStuff.insert(userEntity); 20 | return userEntity; 21 | } 22 | 23 | @GetMapping("/list") 24 | public List findAllUser(){ 25 | return userServiceByProtoStuff.findAll(); 26 | } 27 | 28 | @PostMapping("/remove") 29 | public String removeUser(@RequestParam("id") Long id){ 30 | userServiceByProtoStuff.deleteById(id); 31 | return "success"; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /samples-client/src/main/java/com/anoyi/grpc/client/controller/V3UserController.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.client.controller; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.anoyi.grpc.facade.entity.UserEntity; 5 | import com.anoyi.grpc.facade.service.UserServiceByFastJSON; 6 | import lombok.AllArgsConstructor; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | import java.util.List; 10 | 11 | @RestController 12 | @AllArgsConstructor 13 | @RequestMapping("/v3/user") 14 | public class V3UserController { 15 | 16 | private final UserServiceByFastJSON userServiceByFastJSON; 17 | 18 | @PostMapping("/add") 19 | public UserEntity insertUser(@RequestBody UserEntity userEntity){ 20 | userServiceByFastJSON.insert(JSONObject.toJSONString(userEntity)); 21 | return userEntity; 22 | } 23 | 24 | @GetMapping("/list") 25 | public List findAllUser(){ 26 | return userServiceByFastJSON.findAll(); 27 | } 28 | 29 | @PostMapping("/remove") 30 | public String removeUser(@RequestParam("id") Long id){ 31 | userServiceByFastJSON.deleteById(String.valueOf(id)); 32 | return "success"; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /samples-client/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | 4 | spring: 5 | grpc: 6 | remote-servers: 7 | - server: user 8 | host: 127.0.0.1 9 | port: 6565 10 | -------------------------------------------------------------------------------- /samples-facade/README.md: -------------------------------------------------------------------------------- 1 | ### 安装到本地 Maven 仓库 2 | 3 | 1、Maven Install 4 | ``` 5 | mvn clean install 6 | ``` -------------------------------------------------------------------------------- /samples-facade/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.anoyi.grpc 8 | samples-facade 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | 14 | com.anoyi 15 | spring-boot-starter-grpc 16 | 2.4.0 17 | provided 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /samples-facade/src/main/java/com/anoyi/grpc/facade/entity/PetEntity.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.facade.entity; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * 宠物 9 | */ 10 | @Data 11 | public class PetEntity implements Serializable { 12 | 13 | private static final long serialVersionUID = 0L; 14 | 15 | private String type; 16 | 17 | private String name; 18 | 19 | private UserEntity owner; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /samples-facade/src/main/java/com/anoyi/grpc/facade/entity/UserEntity.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.facade.entity; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * 人物 11 | */ 12 | @Data 13 | public class UserEntity implements Serializable { 14 | 15 | private static final long serialVersionUID = 0L; 16 | 17 | private Long id; 18 | 19 | private String name; 20 | 21 | private int age; 22 | 23 | private String gender; 24 | 25 | private Map scores; 26 | 27 | private UserEntity friend; 28 | 29 | private PetEntity pet; 30 | 31 | private List listValue; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /samples-facade/src/main/java/com/anoyi/grpc/facade/service/UserServiceByFastJSON.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.facade.service; 2 | 3 | 4 | import com.anoyi.grpc.annotation.GrpcService; 5 | import com.anoyi.grpc.constant.SerializeType; 6 | import com.anoyi.grpc.facade.entity.UserEntity; 7 | 8 | import java.util.List; 9 | 10 | @GrpcService(server = "user", serialization = SerializeType.FASTJSON) 11 | public interface UserServiceByFastJSON { 12 | 13 | void insert(String userEntityJson); 14 | 15 | void deleteById(String id); 16 | 17 | List findAll(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /samples-facade/src/main/java/com/anoyi/grpc/facade/service/UserServiceByProtoStuff.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.facade.service; 2 | 3 | 4 | import com.anoyi.grpc.annotation.GrpcService; 5 | import com.anoyi.grpc.constant.SerializeType; 6 | import com.anoyi.grpc.facade.entity.UserEntity; 7 | 8 | import java.util.List; 9 | 10 | @GrpcService(server = "user", serialization = SerializeType.PROTOSTUFF) 11 | public interface UserServiceByProtoStuff { 12 | 13 | void insert(UserEntity userEntity); 14 | 15 | void deleteById(Long id); 16 | 17 | List findAll(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /samples-facade/src/main/java/com/anoyi/grpc/facade/service/UserServiceBySofaHessian.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.facade.service; 2 | 3 | 4 | import com.anoyi.grpc.annotation.GrpcService; 5 | import com.anoyi.grpc.facade.entity.UserEntity; 6 | 7 | import java.util.List; 8 | 9 | @GrpcService(server = "user") 10 | public interface UserServiceBySofaHessian { 11 | 12 | void insert(UserEntity userEntity); 13 | 14 | void deleteById(Long id); 15 | 16 | List findAll(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /samples-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.cn-hangzhou.aliyuncs.com/micro-java/openjdk:8-jre-alpine 2 | MAINTAINER yangyuandong@tezign.com 3 | ENV TZ="Asia/Shanghai" HOME="/root" JVM_PARAMS=" " SPRING_PARAMS=" " 4 | WORKDIR ${HOME} 5 | ADD target/*.jar ${HOME}/ROOT.jar 6 | EXPOSE 8080 7 | CMD java $JVM_PARAMS -Djava.security.egd=file:/dev/./urandom -jar ${HOME}/ROOT.jar $SPRING_PARAMS -------------------------------------------------------------------------------- /samples-server/README.md: -------------------------------------------------------------------------------- 1 | ### 构建镜像 2 | 3 | 1、Maven 打包 4 | ``` 5 | mvn clean package 6 | ``` 7 | 8 | 2、构建 Docker 镜像 9 | ``` 10 | docker build -t server . 11 | ``` -------------------------------------------------------------------------------- /samples-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 2.4.0 11 | 12 | 13 | com.anoyi.grpc 14 | samples-server 15 | 1.0-SNAPSHOT 16 | 17 | 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-web 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-logging 26 | 27 | 28 | 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-log4j2 34 | 35 | 36 | 37 | 38 | com.anoyi.grpc 39 | samples-facade 40 | 1.0-SNAPSHOT 41 | 42 | 43 | 44 | 45 | com.anoyi 46 | spring-boot-starter-grpc 47 | 2.4.0 48 | 49 | 50 | 51 | org.projectlombok 52 | lombok 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-maven-plugin 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /samples-server/src/main/java/com/anoyi/grpc/server/Application.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.server; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /samples-server/src/main/java/com/anoyi/grpc/server/controller/V0UserController.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.server.controller; 2 | 3 | import com.anoyi.grpc.facade.entity.UserEntity; 4 | import com.anoyi.grpc.facade.service.UserServiceBySofaHessian; 5 | import lombok.AllArgsConstructor; 6 | import org.springframework.web.bind.annotation.*; 7 | 8 | import java.util.List; 9 | 10 | @RestController 11 | @AllArgsConstructor 12 | @RequestMapping("/v0/user") 13 | public class V0UserController { 14 | 15 | private final UserServiceBySofaHessian userServiceBySofaHessian; 16 | 17 | @PostMapping("/add") 18 | public UserEntity insertUser(@RequestBody UserEntity userEntity){ 19 | userServiceBySofaHessian.insert(userEntity); 20 | return userEntity; 21 | } 22 | 23 | @GetMapping("/list") 24 | public List findAllUser(){ 25 | return userServiceBySofaHessian.findAll(); 26 | } 27 | 28 | @PostMapping("/remove") 29 | public String removeUser(@RequestParam("id") Long id){ 30 | userServiceBySofaHessian.deleteById(id); 31 | return "success"; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /samples-server/src/main/java/com/anoyi/grpc/server/service/UserServiceByFastJSONImpl.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.server.service; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.anoyi.grpc.facade.entity.UserEntity; 5 | import com.anoyi.grpc.facade.service.UserServiceByFastJSON; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.util.StringUtils; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collection; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.concurrent.ConcurrentHashMap; 15 | 16 | @Service 17 | @Slf4j 18 | public class UserServiceByFastJSONImpl implements UserServiceByFastJSON { 19 | 20 | /** 21 | * 模拟数据库存储用户信息 22 | */ 23 | private Map userMap = new ConcurrentHashMap<>(); 24 | 25 | @Override 26 | public void insert(String userEntityJson) { 27 | UserEntity userEntity = JSONObject.parseObject(userEntityJson, UserEntity.class); 28 | if (userEntity == null){ 29 | log.warn("insert user fail, userEntity is null!"); 30 | return ; 31 | } 32 | userMap.putIfAbsent(userEntity.getId(), userEntity); 33 | } 34 | 35 | @Override 36 | public void deleteById(String id) { 37 | if (StringUtils.isEmpty(id)){ 38 | log.warn("delete user fail, id is null!"); 39 | } 40 | userMap.remove(Long.valueOf(id)); 41 | } 42 | 43 | @Override 44 | public List findAll() { 45 | Collection values = userMap.values(); 46 | return new ArrayList<>(values); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /samples-server/src/main/java/com/anoyi/grpc/server/service/UserServiceByProtoStuffImpl.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.server.service; 2 | 3 | import com.anoyi.grpc.facade.entity.UserEntity; 4 | import com.anoyi.grpc.facade.service.UserServiceByProtoStuff; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collection; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | @Service 15 | @Slf4j 16 | public class UserServiceByProtoStuffImpl implements UserServiceByProtoStuff { 17 | 18 | /** 19 | * 模拟数据库存储用户信息 20 | */ 21 | private Map userMap = new ConcurrentHashMap<>(); 22 | 23 | @Override 24 | public void insert(UserEntity userEntity) { 25 | if (userEntity == null){ 26 | log.warn("insert user fail, userEntity is null!"); 27 | return ; 28 | } 29 | userMap.putIfAbsent(userEntity.getId(), userEntity); 30 | } 31 | 32 | @Override 33 | public void deleteById(Long id) { 34 | if (id == null){ 35 | log.warn("delete user fail, id is null!"); 36 | } 37 | userMap.remove(id); 38 | } 39 | 40 | @Override 41 | public List findAll() { 42 | Collection values = userMap.values(); 43 | return new ArrayList<>(values); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /samples-server/src/main/java/com/anoyi/grpc/server/service/UserServiceBySofaHessianImpl.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.server.service; 2 | 3 | import com.anoyi.grpc.facade.entity.UserEntity; 4 | import com.anoyi.grpc.facade.service.UserServiceBySofaHessian; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.*; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | @Service 12 | @Slf4j 13 | public class UserServiceBySofaHessianImpl implements UserServiceBySofaHessian { 14 | 15 | /** 16 | * 模拟数据库存储用户信息 17 | */ 18 | private Map userMap = new ConcurrentHashMap<>(); 19 | 20 | @Override 21 | public void insert(UserEntity userEntity) { 22 | if (userEntity == null){ 23 | log.warn("insert user fail, userEntity is null!"); 24 | return ; 25 | } 26 | userMap.putIfAbsent(userEntity.getId(), userEntity); 27 | } 28 | 29 | @Override 30 | public void deleteById(Long id) { 31 | if (id == null){ 32 | log.warn("delete user fail, id is null!"); 33 | } 34 | userMap.remove(id); 35 | } 36 | 37 | @Override 38 | public List findAll() { 39 | log.info("load balance...."); 40 | Collection values = userMap.values(); 41 | return new ArrayList<>(values); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /samples-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | spring: 5 | grpc: 6 | enable: true 7 | port: 6565 -------------------------------------------------------------------------------- /spring-boot-starter-grpc/README.md: -------------------------------------------------------------------------------- 1 | ### 安装到本地 Maven 仓库 2 | 3 | 1、Maven Install 4 | ``` 5 | mvn clean install 6 | ``` -------------------------------------------------------------------------------- /spring-boot-starter-grpc/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.anoyi 8 | spring-boot-starter-grpc 9 | 2.4.0 10 | 11 | 12 | 2.4.0 13 | 1.33.1 14 | 15 | 16 | 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-dependencies 21 | ${spring-boot.version} 22 | pom 23 | import 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | io.grpc 33 | grpc-netty-shaded 34 | ${grpc.version} 35 | 36 | 37 | 38 | io.grpc 39 | grpc-protobuf 40 | ${grpc.version} 41 | 42 | 43 | 44 | io.grpc 45 | grpc-stub 46 | ${grpc.version} 47 | 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter 53 | provided 54 | 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-configuration-processor 60 | true 61 | 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-autoconfigure 67 | provided 68 | 69 | 70 | 71 | 72 | org.projectlombok 73 | lombok 74 | 75 | 76 | 77 | 78 | org.apache.commons 79 | commons-lang3 80 | 3.11 81 | 82 | 83 | 84 | 85 | io.protostuff 86 | protostuff-core 87 | 1.7.2 88 | 89 | 90 | 91 | io.protostuff 92 | protostuff-runtime 93 | 1.7.2 94 | 95 | 96 | 97 | 98 | com.alibaba 99 | fastjson 100 | 1.2.83 101 | 102 | 103 | 104 | 105 | com.alipay.sofa 106 | hessian 107 | 3.3.13 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | kr.motd.maven 116 | os-maven-plugin 117 | 1.6.2 118 | 119 | 120 | 121 | 122 | org.xolstice.maven.plugins 123 | protobuf-maven-plugin 124 | 0.6.1 125 | 126 | com.google.protobuf:protoc:3.14.0:exe:${os.detected.classifier} 127 | grpc-java 128 | io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} 129 | 130 | 131 | 132 | 133 | compile 134 | compile-custom 135 | 136 | 137 | 138 | 139 | 140 | org.apache.maven.plugins 141 | maven-compiler-plugin 142 | 143 | 1.8 144 | 1.8 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | rdc-releases 155 | https://repo.rdc.aliyun.com/repository/73801-release-g4ZTFl/ 156 | 157 | 158 | 159 | rdc-snapshots 160 | https://repo.rdc.aliyun.com/repository/73801-snapshot-e8LM6K/ 161 | 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/GrpcClient.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc; 2 | 3 | import com.anoyi.grpc.config.GrpcProperties; 4 | import com.anoyi.grpc.config.RemoteServer; 5 | import com.anoyi.grpc.service.SerializeService; 6 | import io.grpc.*; 7 | import io.grpc.internal.DnsNameResolverProvider; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.util.CollectionUtils; 10 | 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | @Slf4j 17 | public class GrpcClient { 18 | 19 | private static final Map serverMap = new HashMap<>(); 20 | 21 | private final GrpcProperties grpcProperties; 22 | 23 | private final SerializeService serializeService; 24 | 25 | private ClientInterceptor clientInterceptor; 26 | 27 | public GrpcClient(GrpcProperties grpcProperties, SerializeService serializeService) { 28 | this.grpcProperties = grpcProperties; 29 | this.serializeService = serializeService; 30 | } 31 | 32 | public GrpcClient(GrpcProperties grpcProperties, SerializeService serializeService, ClientInterceptor clientInterceptor) { 33 | this.grpcProperties = grpcProperties; 34 | this.serializeService = serializeService; 35 | this.clientInterceptor = clientInterceptor; 36 | } 37 | 38 | /** 39 | * 初始化 40 | */ 41 | public void init(){ 42 | List remoteServers = grpcProperties.getRemoteServers(); 43 | if (!CollectionUtils.isEmpty(remoteServers)) { 44 | for (RemoteServer server : remoteServers) { 45 | ManagedChannel channel = ManagedChannelBuilder.forAddress(server.getHost(), server.getPort()) 46 | .defaultLoadBalancingPolicy("round_robin") 47 | .nameResolverFactory(new DnsNameResolverProvider()) 48 | .idleTimeout(30, TimeUnit.SECONDS) 49 | .usePlaintext().build(); 50 | if (clientInterceptor != null){ 51 | Channel newChannel = ClientInterceptors.intercept(channel, clientInterceptor); 52 | serverMap.put(server.getServer(), new ServerContext(newChannel, serializeService)); 53 | }else { 54 | Class clazz = grpcProperties.getClientInterceptor(); 55 | if (clazz == null) { 56 | serverMap.put(server.getServer(), new ServerContext(channel, serializeService)); 57 | }else { 58 | try { 59 | ClientInterceptor interceptor = (ClientInterceptor) clazz.newInstance(); 60 | Channel newChannel = ClientInterceptors.intercept(channel, interceptor); 61 | serverMap.put(server.getServer(), new ServerContext(newChannel, serializeService)); 62 | } catch (InstantiationException | IllegalAccessException e) { 63 | log.warn("ClientInterceptor cannot use, ignoring..."); 64 | serverMap.put(server.getServer(), new ServerContext(channel, serializeService)); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * 连接远程服务 74 | */ 75 | public static ServerContext connect(String serverName) { 76 | return serverMap.get(serverName); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/GrpcServer.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc; 2 | 3 | import com.anoyi.grpc.config.GrpcProperties; 4 | import com.anoyi.grpc.service.CommonService; 5 | import io.grpc.Server; 6 | import io.grpc.ServerBuilder; 7 | import io.grpc.ServerInterceptor; 8 | import io.grpc.ServerInterceptors; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.DisposableBean; 11 | 12 | import java.util.Optional; 13 | 14 | /** 15 | * gRPC Server 16 | */ 17 | @Slf4j 18 | public class GrpcServer implements DisposableBean { 19 | 20 | private final GrpcProperties grpcProperties; 21 | 22 | private final CommonService commonService; 23 | 24 | private ServerInterceptor serverInterceptor; 25 | 26 | private Server server; 27 | 28 | public GrpcServer(GrpcProperties grpcProperties, CommonService commonService) { 29 | this.grpcProperties = grpcProperties; 30 | this.commonService = commonService; 31 | } 32 | 33 | public GrpcServer(GrpcProperties grpcProperties, CommonService commonService, ServerInterceptor serverInterceptor) { 34 | this.grpcProperties = grpcProperties; 35 | this.commonService = commonService; 36 | this.serverInterceptor = serverInterceptor; 37 | } 38 | 39 | /** 40 | * 启动服务 41 | * @throws Exception 异常 42 | */ 43 | public void start() throws Exception{ 44 | int port = grpcProperties.getPort(); 45 | if (serverInterceptor != null){ 46 | server = ServerBuilder.forPort(port).addService(ServerInterceptors.intercept(commonService, serverInterceptor)).build().start(); 47 | }else { 48 | Class clazz = grpcProperties.getServerInterceptor(); 49 | if (clazz == null){ 50 | server = ServerBuilder.forPort(port).addService(commonService).build().start(); 51 | }else { 52 | server = ServerBuilder.forPort(port).addService(ServerInterceptors.intercept(commonService, (ServerInterceptor) clazz.newInstance())).build().start(); 53 | } 54 | } 55 | log.info("gRPC Server started, listening on port " + server.getPort()); 56 | startDaemonAwaitThread(); 57 | } 58 | 59 | /** 60 | * 销毁 61 | */ 62 | public void destroy() { 63 | Optional.ofNullable(server).ifPresent(Server::shutdown); 64 | log.info("gRPC server stopped."); 65 | } 66 | 67 | private void startDaemonAwaitThread() { 68 | Thread awaitThread = new Thread(()->{ 69 | try { 70 | GrpcServer.this.server.awaitTermination(); 71 | } catch (InterruptedException e) { 72 | log.warn("gRPC server stopped." + e.getMessage()); 73 | } 74 | }); 75 | awaitThread.setDaemon(false); 76 | awaitThread.start(); 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/ServerContext.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc; 2 | 3 | import com.anoyi.grpc.constant.SerializeType; 4 | import com.anoyi.grpc.service.GrpcRequest; 5 | import com.anoyi.grpc.service.GrpcResponse; 6 | import com.anoyi.grpc.service.SerializeService; 7 | import com.anoyi.grpc.util.SerializeUtils; 8 | import com.anoyi.rpc.CommonServiceGrpc; 9 | import com.anoyi.rpc.GrpcService; 10 | import com.google.protobuf.ByteString; 11 | import io.grpc.Channel; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | @Slf4j 15 | public class ServerContext { 16 | 17 | private Channel channel; 18 | 19 | private final SerializeService defaultSerializeService; 20 | 21 | private CommonServiceGrpc.CommonServiceBlockingStub blockingStub; 22 | 23 | ServerContext(Channel channel, SerializeService serializeService) { 24 | this.channel = channel; 25 | this.defaultSerializeService = serializeService; 26 | blockingStub = CommonServiceGrpc.newBlockingStub(channel); 27 | } 28 | 29 | /** 30 | * 处理 gRPC 请求 31 | */ 32 | public GrpcResponse handle(SerializeType serializeType, GrpcRequest grpcRequest) { 33 | SerializeService serializeService = SerializeUtils.getSerializeService(serializeType, this.defaultSerializeService); 34 | ByteString bytes = serializeService.serialize(grpcRequest); 35 | int value = (serializeType == null ? -1 : serializeType.getValue()); 36 | GrpcService.Request request = GrpcService.Request.newBuilder().setSerialize(value).setRequest(bytes).build(); 37 | GrpcService.Response response = null; 38 | try{ 39 | response = blockingStub.handle(request); 40 | }catch (Exception exception){ 41 | log.warn("rpc exception: {}", exception.getMessage()); 42 | if ("UNAVAILABLE: io exception".equals(exception.getMessage().trim())){ 43 | response = blockingStub.handle(request); 44 | } 45 | } 46 | return serializeService.deserialize(response); 47 | } 48 | 49 | /** 50 | * 获取 Channel 51 | */ 52 | public Channel getChannel() { 53 | return channel; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/annotation/GrpcService.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.annotation; 2 | 3 | import com.anoyi.grpc.constant.SerializeType; 4 | 5 | import java.lang.annotation.Documented; 6 | import java.lang.annotation.Inherited; 7 | import java.lang.annotation.Retention; 8 | 9 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 10 | 11 | @Documented 12 | @Inherited 13 | @Retention(RUNTIME) 14 | public @interface GrpcService { 15 | 16 | /** 17 | * 远程服务名 18 | */ 19 | String server() default ""; 20 | 21 | /** 22 | * 序列化工具实现类 23 | */ 24 | SerializeType[] serialization() default {}; 25 | 26 | } -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/annotation/GrpcServiceScan.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.annotation; 2 | 3 | import com.anoyi.grpc.config.GrpcAutoConfiguration; 4 | import org.springframework.context.annotation.Import; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Target(ElementType.TYPE) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Import({GrpcAutoConfiguration.ExternalGrpcServiceScannerRegistrar.class}) 14 | public @interface GrpcServiceScan { 15 | 16 | /** 17 | * `@GrpcService` 所注解的包扫描路径 18 | */ 19 | String[] packages() default {}; 20 | 21 | } -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/binding/GrpcServiceProxy.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.binding; 2 | 3 | import com.anoyi.grpc.GrpcClient; 4 | import com.anoyi.grpc.annotation.GrpcService; 5 | import com.anoyi.grpc.constant.GrpcResponseStatus; 6 | import com.anoyi.grpc.constant.SerializeType; 7 | import com.anoyi.grpc.exception.GrpcException; 8 | import com.anoyi.grpc.service.GrpcRequest; 9 | import com.anoyi.grpc.service.GrpcResponse; 10 | import org.springframework.cglib.proxy.InvocationHandler; 11 | import org.springframework.util.CollectionUtils; 12 | 13 | import java.lang.reflect.Method; 14 | import java.util.Arrays; 15 | 16 | public class GrpcServiceProxy implements InvocationHandler { 17 | 18 | private Class grpcService; 19 | 20 | private Object invoker; 21 | 22 | public GrpcServiceProxy(Class grpcService, Object invoker) { 23 | this.grpcService = grpcService; 24 | this.invoker = invoker; 25 | } 26 | 27 | @Override 28 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 29 | String methodName = method.getName(); 30 | String className = grpcService.getName(); 31 | if ("toString".equals(methodName) && args.length == 0) { 32 | return className + "@" + invoker.hashCode(); 33 | } else if ("hashCode".equals(methodName) && args.length == 0) { 34 | return invoker.hashCode(); 35 | } else if ("equals".equals(methodName) && args.length == 1) { 36 | Object another = args[0]; 37 | return proxy == another; 38 | } 39 | GrpcService annotation = grpcService.getAnnotation(GrpcService.class); 40 | String server = annotation.server(); 41 | GrpcRequest request = new GrpcRequest(); 42 | request.setClazz(className); 43 | request.setMethod(methodName); 44 | request.setArgs(args); 45 | SerializeType[] serializeTypeArray = annotation.serialization(); 46 | SerializeType serializeType = null; 47 | if (serializeTypeArray.length > 0) { 48 | serializeType = serializeTypeArray[0]; 49 | } 50 | GrpcResponse response = GrpcClient.connect(server).handle(serializeType, request); 51 | if (GrpcResponseStatus.ERROR.getCode() == response.getStatus()) { 52 | Throwable throwable = response.getException(); 53 | GrpcException exception = new GrpcException(throwable.getClass().getName() + ": " + throwable.getMessage()); 54 | StackTraceElement[] exceptionStackTrace = exception.getStackTrace(); 55 | StackTraceElement[] responseStackTrace = response.getStackTrace(); 56 | StackTraceElement[] allStackTrace = Arrays.copyOf(exceptionStackTrace, exceptionStackTrace.length + responseStackTrace.length); 57 | System.arraycopy(responseStackTrace, 0, allStackTrace, exceptionStackTrace.length, responseStackTrace.length); 58 | exception.setStackTrace(allStackTrace); 59 | throw exception; 60 | } 61 | return response.getResult(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/config/GrpcAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.config; 2 | 3 | import com.anoyi.grpc.GrpcClient; 4 | import com.anoyi.grpc.GrpcServer; 5 | import com.anoyi.grpc.annotation.GrpcService; 6 | import com.anoyi.grpc.annotation.GrpcServiceScan; 7 | import com.anoyi.grpc.binding.GrpcServiceProxy; 8 | import com.anoyi.grpc.service.CommonService; 9 | import com.anoyi.grpc.service.SerializeService; 10 | import com.anoyi.grpc.service.impl.SofaHessianSerializeService; 11 | import com.anoyi.grpc.util.ClassNameUtils; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.springframework.beans.BeansException; 14 | import org.springframework.beans.factory.BeanFactory; 15 | import org.springframework.beans.factory.BeanFactoryAware; 16 | import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; 17 | import org.springframework.beans.factory.config.BeanDefinition; 18 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 19 | import org.springframework.beans.factory.support.DefaultListableBeanFactory; 20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 21 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 22 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 23 | import org.springframework.cglib.proxy.InvocationHandler; 24 | import org.springframework.cglib.proxy.Proxy; 25 | import org.springframework.context.ResourceLoaderAware; 26 | import org.springframework.context.annotation.Bean; 27 | import org.springframework.context.annotation.ClassPathBeanDefinitionScanner; 28 | import org.springframework.context.annotation.Configuration; 29 | import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; 30 | import org.springframework.context.support.AbstractApplicationContext; 31 | import org.springframework.core.io.ResourceLoader; 32 | import org.springframework.core.type.AnnotationMetadata; 33 | import org.springframework.core.type.filter.AnnotationTypeFilter; 34 | import org.springframework.util.CollectionUtils; 35 | import org.springframework.util.StringUtils; 36 | 37 | import java.util.*; 38 | 39 | @Slf4j 40 | @Configuration 41 | @EnableConfigurationProperties(GrpcProperties.class) 42 | public class GrpcAutoConfiguration { 43 | 44 | private final AbstractApplicationContext applicationContext; 45 | 46 | private final GrpcProperties grpcProperties; 47 | 48 | public GrpcAutoConfiguration(AbstractApplicationContext applicationContext, GrpcProperties grpcProperties) { 49 | this.applicationContext = applicationContext; 50 | this.grpcProperties = grpcProperties; 51 | } 52 | 53 | /** 54 | * 全局 RPC 序列化/反序列化 55 | */ 56 | @Bean 57 | @ConditionalOnMissingBean(SerializeService.class) 58 | public SerializeService serializeService() { 59 | return new SofaHessianSerializeService(); 60 | } 61 | 62 | /** 63 | * PRC 服务调用 64 | */ 65 | @Bean 66 | public CommonService commonService(SerializeService serializeService) { 67 | return new CommonService(applicationContext, serializeService); 68 | } 69 | 70 | /** 71 | * RPC 服务端 72 | */ 73 | @Bean 74 | @ConditionalOnMissingBean(GrpcServer.class) 75 | @ConditionalOnProperty(value = "spring.grpc.enable", havingValue = "true") 76 | public GrpcServer grpcServer(CommonService commonService) throws Exception { 77 | GrpcServer server = new GrpcServer(grpcProperties, commonService); 78 | server.start(); 79 | return server; 80 | } 81 | 82 | /** 83 | * RPC 客户端 84 | */ 85 | @Bean 86 | @ConditionalOnMissingBean(GrpcClient.class) 87 | public GrpcClient grpcClient(SerializeService serializeService) { 88 | GrpcClient client = new GrpcClient(grpcProperties, serializeService); 89 | client.init(); 90 | return client; 91 | } 92 | 93 | /** 94 | * 手动扫描 @GrpcService 注解的接口,生成动态代理类,注入到 Spring 容器 95 | */ 96 | public static class ExternalGrpcServiceScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware { 97 | 98 | private BeanFactory beanFactory; 99 | 100 | private ResourceLoader resourceLoader; 101 | 102 | @Override 103 | public void setBeanFactory(BeanFactory beanFactory) throws BeansException { 104 | this.beanFactory = beanFactory; 105 | } 106 | 107 | @Override 108 | public void setResourceLoader(ResourceLoader resourceLoader) { 109 | this.resourceLoader = resourceLoader; 110 | } 111 | 112 | @Override 113 | public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { 114 | ClassPathBeanDefinitionScanner scanner = new ClassPathGrpcServiceScanner(registry); 115 | scanner.setResourceLoader(this.resourceLoader); 116 | scanner.addIncludeFilter(new AnnotationTypeFilter(GrpcService.class)); 117 | Set beanDefinitions = scanPackages(importingClassMetadata, scanner); 118 | ProxyUtil.registerBeans(beanFactory, beanDefinitions); 119 | } 120 | 121 | /** 122 | * 包扫描 123 | */ 124 | private Set scanPackages(AnnotationMetadata importingClassMetadata, ClassPathBeanDefinitionScanner scanner) { 125 | List packages = new ArrayList<>(); 126 | Map annotationAttributes = importingClassMetadata.getAnnotationAttributes(GrpcServiceScan.class.getCanonicalName()); 127 | if (annotationAttributes != null) { 128 | String[] basePackages = (String[]) annotationAttributes.get("packages"); 129 | if (basePackages.length > 0) { 130 | packages.addAll(Arrays.asList(basePackages)); 131 | } 132 | } 133 | Set beanDefinitions = new HashSet<>(); 134 | if (CollectionUtils.isEmpty(packages)) { 135 | return beanDefinitions; 136 | } 137 | packages.forEach(pack -> beanDefinitions.addAll(scanner.findCandidateComponents(pack))); 138 | return beanDefinitions; 139 | } 140 | 141 | } 142 | 143 | protected static class ClassPathGrpcServiceScanner extends ClassPathBeanDefinitionScanner { 144 | 145 | ClassPathGrpcServiceScanner(BeanDefinitionRegistry registry) { 146 | super(registry, false); 147 | } 148 | 149 | @Override 150 | protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { 151 | return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); 152 | } 153 | 154 | } 155 | 156 | protected static class ProxyUtil { 157 | static void registerBeans(BeanFactory beanFactory, Set beanDefinitions) { 158 | for (BeanDefinition beanDefinition : beanDefinitions) { 159 | String className = beanDefinition.getBeanClassName(); 160 | if (StringUtils.isEmpty(className)) { 161 | continue; 162 | } 163 | try { 164 | // 创建代理类 165 | Class target = Class.forName(className); 166 | Object invoker = new Object(); 167 | InvocationHandler invocationHandler = new GrpcServiceProxy<>(target, invoker); 168 | Object proxy = Proxy.newProxyInstance(GrpcService.class.getClassLoader(), new Class[]{target}, invocationHandler); 169 | 170 | // 注册到 Spring 容器 171 | String beanName = ClassNameUtils.beanName(className); 172 | ((DefaultListableBeanFactory) beanFactory).registerSingleton(beanName, proxy); 173 | } catch (ClassNotFoundException e) { 174 | log.warn("class not found : " + className); 175 | } 176 | } 177 | } 178 | } 179 | 180 | } -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/config/GrpcProperties.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | @ConfigurationProperties(prefix = "spring.grpc") 10 | public class GrpcProperties { 11 | 12 | /** 13 | * enable server start 14 | */ 15 | private boolean enable; 16 | 17 | /** 18 | * server listen port 19 | */ 20 | private int port; 21 | 22 | /** 23 | * client config 24 | */ 25 | private List remoteServers; 26 | 27 | /** 28 | * client interceptor 29 | */ 30 | private Class clientInterceptor; 31 | 32 | /** 33 | * server interceptor 34 | */ 35 | private Class serverInterceptor; 36 | 37 | } -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/config/RemoteServer.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.config; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 远程服务 7 | */ 8 | @Data 9 | public class RemoteServer { 10 | 11 | /** 12 | * 服务名 13 | */ 14 | private String server; 15 | 16 | /** 17 | * 主机地址 18 | */ 19 | private String host; 20 | 21 | /** 22 | * 服务端口号 23 | */ 24 | private int port; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/constant/GrpcResponseStatus.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.constant; 2 | 3 | public enum GrpcResponseStatus { 4 | 5 | SUCCESS(0), ERROR(-1); 6 | 7 | private int code; 8 | 9 | GrpcResponseStatus(int code) { 10 | this.code = code; 11 | } 12 | 13 | public int getCode() { 14 | return code; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/constant/SerializeType.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.constant; 2 | 3 | import com.anoyi.grpc.service.impl.FastJSONSerializeService; 4 | import com.anoyi.grpc.service.impl.ProtoStuffSerializeService; 5 | import com.anoyi.grpc.service.impl.SofaHessianSerializeService; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * 序列化类型枚举 12 | */ 13 | public enum SerializeType { 14 | 15 | SOFAHESSIAN(1, SofaHessianSerializeService.class), 16 | PROTOSTUFF(2, ProtoStuffSerializeService.class), 17 | FASTJSON(3, FastJSONSerializeService.class); 18 | 19 | private static Map enumMap = new HashMap<>(); 20 | 21 | static { 22 | for (SerializeType serializeType : SerializeType.values()) { 23 | enumMap.put(serializeType.value, serializeType); 24 | } 25 | } 26 | 27 | private int value; 28 | 29 | private Class clazz; 30 | 31 | SerializeType(int value, Class clazz){ 32 | this.clazz = clazz; 33 | this.value = value; 34 | } 35 | 36 | public static SerializeType getSerializeTypeByValue(int value){ 37 | return enumMap.get(value); 38 | } 39 | 40 | public int getValue() { 41 | return value; 42 | } 43 | 44 | public Class getClazz() { 45 | return clazz; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/exception/GrpcException.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.exception; 2 | 3 | public class GrpcException extends RuntimeException { 4 | 5 | public GrpcException(String message){ 6 | super(message); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/exception/GrpcResponseException.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.exception; 2 | 3 | import com.anoyi.grpc.service.GrpcResponse; 4 | 5 | public class GrpcResponseException extends RuntimeException { 6 | 7 | private GrpcResponse response; 8 | 9 | public GrpcResponseException(GrpcResponse response){ 10 | super(response.getMessage()); 11 | this.response = response; 12 | } 13 | 14 | 15 | public GrpcResponse getResponse() { 16 | return response; 17 | } 18 | 19 | public void setResponse(GrpcResponse response) { 20 | this.response = response; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/service/CommonService.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.service; 2 | 3 | import com.anoyi.grpc.exception.GrpcResponseException; 4 | import com.anoyi.grpc.util.SerializeUtils; 5 | import com.anoyi.rpc.CommonServiceGrpc; 6 | import com.anoyi.rpc.GrpcService; 7 | import com.google.protobuf.ByteString; 8 | import io.grpc.stub.StreamObserver; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.apache.commons.lang3.reflect.MethodUtils; 11 | import org.springframework.beans.BeansException; 12 | import org.springframework.beans.factory.NoSuchBeanDefinitionException; 13 | import org.springframework.cglib.reflect.FastClass; 14 | import org.springframework.cglib.reflect.FastMethod; 15 | import org.springframework.context.support.AbstractApplicationContext; 16 | import org.springframework.stereotype.Service; 17 | import java.lang.reflect.InvocationTargetException; 18 | import java.lang.reflect.Method; 19 | import java.util.Map; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | 22 | @Slf4j 23 | @Service 24 | public class CommonService extends CommonServiceGrpc.CommonServiceImplBase { 25 | 26 | private Map serviceBeanMap = new ConcurrentHashMap<>(); 27 | 28 | private Map serviceStringMap = new ConcurrentHashMap<>(); 29 | 30 | private final AbstractApplicationContext applicationContext; 31 | 32 | private final SerializeService defaultSerializationService; 33 | 34 | public CommonService(AbstractApplicationContext applicationContext, SerializeService serializeService) { 35 | this.applicationContext = applicationContext; 36 | this.defaultSerializationService = serializeService; 37 | } 38 | 39 | @Override 40 | public void handle(GrpcService.Request request, StreamObserver responseObserver) { 41 | int serialize = request.getSerialize(); 42 | SerializeService serializeService = SerializeUtils.getSerializeService(serialize, defaultSerializationService); 43 | GrpcResponse response = new GrpcResponse(); 44 | try { 45 | GrpcRequest grpcRequest = serializeService.deserialize(request); 46 | String className = grpcRequest.getClazz(); 47 | Object bean = getBean(className); 48 | Object[] args = grpcRequest.getArgs(); 49 | Class[] argsTypes = getParameterTypes(args); 50 | Method matchingMethod = MethodUtils.getMatchingMethod(bean.getClass(), grpcRequest.getMethod(), argsTypes); 51 | FastClass serviceFastClass = FastClass.create(bean.getClass()); 52 | FastMethod serviceFastMethod = serviceFastClass.getMethod(matchingMethod); 53 | Object result = serviceFastMethod.invoke(bean, args); 54 | response.success(result); 55 | }catch (NoSuchBeanDefinitionException | ClassNotFoundException exception) { 56 | String message = exception.getClass().getName() + ": " + exception.getMessage(); 57 | response.error(message, exception, exception.getStackTrace()); 58 | log.error("method not implement", exception.getCause()); 59 | } catch (GrpcResponseException | InvocationTargetException exception) { 60 | //通过GrpcResponseException,可以在序列化或方法内部做自定义的错误返回 61 | GrpcResponseException grpcException = null; 62 | if(exception instanceof InvocationTargetException 63 | && ((InvocationTargetException) exception).getTargetException() instanceof GrpcResponseException){ 64 | grpcException = (GrpcResponseException)((InvocationTargetException) exception).getTargetException(); 65 | } 66 | if(exception instanceof GrpcResponseException){ 67 | grpcException = (GrpcResponseException)exception; 68 | } 69 | if(grpcException != null && grpcException.getResponse() != null){ 70 | response = grpcException.getResponse(); 71 | }else { 72 | String message = exception.getCause().getClass().getName() + ": " + exception.getCause().getMessage(); 73 | response.error(message, exception.getCause(), exception.getCause().getStackTrace()); 74 | log.error("method invoke error", exception.getCause()); 75 | } 76 | } 77 | ByteString bytes = serializeService.serialize(response); 78 | GrpcService.Response grpcResponse = GrpcService.Response.newBuilder().setResponse(bytes).build(); 79 | responseObserver.onNext(grpcResponse); 80 | responseObserver.onCompleted(); 81 | } 82 | 83 | /** 84 | * 获取 Service Bean 85 | */ 86 | private Object getBean(String className) throws NoSuchBeanDefinitionException, ClassNotFoundException { 87 | Object bean = null; 88 | try { 89 | if(className.indexOf(".") > -1){ 90 | Class clazz = Class.forName(className); 91 | if (serviceBeanMap.containsKey(clazz)) { 92 | return serviceBeanMap.get(clazz); 93 | } 94 | bean = applicationContext.getBean(clazz); 95 | serviceBeanMap.put(clazz, bean); 96 | }else{ 97 | if (serviceStringMap.containsKey(className)) { 98 | return serviceStringMap.get(className); 99 | } 100 | bean = applicationContext.getBean(className); 101 | serviceStringMap.put(className, bean); 102 | } 103 | } catch (BeansException e) { 104 | throw new NoSuchBeanDefinitionException(className); 105 | } 106 | return bean; 107 | } 108 | 109 | /** 110 | * 获取参数类型 111 | */ 112 | private Class[] getParameterTypes(Object[] parameters){ 113 | if (parameters == null){ 114 | return null; 115 | } 116 | Class[] clazzArray = new Class[parameters.length]; 117 | for (int i = 0; i < parameters.length; i++) { 118 | clazzArray[i] = parameters[i].getClass(); 119 | 120 | } 121 | return clazzArray; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/service/GrpcRequest.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.service; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | @Data 8 | public class GrpcRequest implements Serializable { 9 | 10 | private static final long serialVersionUID = 4729940126314117605L; 11 | 12 | /** 13 | * 接口 14 | */ 15 | private String clazz; 16 | 17 | /** 18 | * 方法 19 | */ 20 | private String method; 21 | 22 | /** 23 | * service 方法参数 24 | */ 25 | private Object[] args; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/service/GrpcResponse.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.service; 2 | 3 | import com.anoyi.grpc.constant.GrpcResponseStatus; 4 | import lombok.Data; 5 | 6 | import java.io.Serializable; 7 | 8 | @Data 9 | public class GrpcResponse implements Serializable { 10 | 11 | private static final long serialVersionUID = -7161518426386434816L; 12 | 13 | /** 14 | * 响应状态 15 | */ 16 | private int status; 17 | 18 | /** 19 | * 信息提示 20 | */ 21 | private String message; 22 | 23 | /** 24 | * 返回结果 25 | */ 26 | private Object result; 27 | 28 | /** 29 | * 服务端异常 30 | */ 31 | private Throwable exception; 32 | 33 | /** 34 | * 异常堆栈信息 35 | */ 36 | private StackTraceElement[] stackTrace; 37 | 38 | public GrpcResponse error(String message, Throwable exception, StackTraceElement[] stackTrace){ 39 | this.status = GrpcResponseStatus.ERROR.getCode(); 40 | this.message = message; 41 | this.exception = exception; 42 | this.stackTrace = stackTrace; 43 | return this; 44 | } 45 | 46 | public GrpcResponse success(Object result){ 47 | this.status = GrpcResponseStatus.SUCCESS.getCode(); 48 | this.result = result; 49 | return this; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/service/SerializeService.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.service; 2 | 3 | import com.anoyi.rpc.GrpcService; 4 | import com.google.protobuf.ByteString; 5 | 6 | public interface SerializeService { 7 | 8 | /** 9 | * 序列化 10 | */ 11 | ByteString serialize(GrpcResponse response); 12 | 13 | /** 14 | * 序列化 15 | */ 16 | ByteString serialize(GrpcRequest request); 17 | 18 | /** 19 | * 反序列化 20 | */ 21 | GrpcRequest deserialize(GrpcService.Request request); 22 | 23 | /** 24 | * 反序列化 25 | */ 26 | GrpcResponse deserialize(GrpcService.Response response); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/service/impl/FastJSONSerializeService.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.service.impl; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.parser.Feature; 5 | import com.anoyi.grpc.service.SerializeService; 6 | import com.anoyi.grpc.service.GrpcRequest; 7 | import com.anoyi.grpc.service.GrpcResponse; 8 | import com.anoyi.rpc.GrpcService; 9 | import com.google.protobuf.ByteString; 10 | 11 | /** 12 | * FastJSON 序列化/反序列化工具 13 | */ 14 | public class FastJSONSerializeService implements SerializeService { 15 | 16 | @Override 17 | public ByteString serialize(GrpcResponse response) { 18 | return ByteString.copyFrom(JSON.toJSONBytes(response)); 19 | } 20 | 21 | @Override 22 | public ByteString serialize(GrpcRequest request) { 23 | return ByteString.copyFrom(JSON.toJSONBytes(request)); 24 | } 25 | 26 | @Override 27 | public GrpcRequest deserialize(GrpcService.Request request) { 28 | byte[] bytes = request.getRequest().toByteArray(); 29 | return JSON.parseObject(bytes, GrpcRequest.class, Feature.OrderedField); 30 | } 31 | 32 | @Override 33 | public GrpcResponse deserialize(GrpcService.Response response) { 34 | byte[] bytes = response.getResponse().toByteArray(); 35 | return JSON.parseObject(bytes, GrpcResponse.class, Feature.OrderedField); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/service/impl/ProtoStuffSerializeService.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.service.impl; 2 | 3 | import com.anoyi.grpc.service.SerializeService; 4 | import com.anoyi.grpc.service.GrpcRequest; 5 | import com.anoyi.grpc.service.GrpcResponse; 6 | import com.anoyi.grpc.util.ProtobufUtils; 7 | import com.anoyi.rpc.GrpcService; 8 | import com.google.protobuf.ByteString; 9 | 10 | /** 11 | * ProtoStuff 序列化/反序列化工具 12 | */ 13 | public class ProtoStuffSerializeService implements SerializeService { 14 | 15 | @Override 16 | public GrpcRequest deserialize(GrpcService.Request request) { 17 | return ProtobufUtils.deserialize(request.getRequest().toByteArray(), GrpcRequest.class); 18 | } 19 | 20 | @Override 21 | public GrpcResponse deserialize(GrpcService.Response response) { 22 | return ProtobufUtils.deserialize(response.getResponse().toByteArray(), GrpcResponse.class); 23 | } 24 | 25 | @Override 26 | public ByteString serialize(GrpcResponse response) { 27 | return ByteString.copyFrom(ProtobufUtils.serialize(response)); 28 | } 29 | 30 | @Override 31 | public ByteString serialize(GrpcRequest request) { 32 | return ByteString.copyFrom(ProtobufUtils.serialize(request)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/service/impl/SofaHessianSerializeService.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.service.impl; 2 | 3 | import com.anoyi.grpc.exception.GrpcException; 4 | import com.anoyi.grpc.service.GrpcRequest; 5 | import com.anoyi.grpc.service.GrpcResponse; 6 | import com.anoyi.grpc.service.SerializeService; 7 | import com.anoyi.rpc.GrpcService; 8 | import com.caucho.hessian.io.Hessian2Input; 9 | import com.caucho.hessian.io.Hessian2Output; 10 | import com.caucho.hessian.io.SerializerFactory; 11 | import com.google.protobuf.ByteString; 12 | 13 | import javax.annotation.concurrent.NotThreadSafe; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.io.OutputStream; 17 | import java.util.Arrays; 18 | 19 | /** 20 | * sofa-hessian 序列化/反序列化工具 21 | * 22 | * 参考: 23 | * https://github.com/alipay/sofa-rpc/blob/master/extension-impl/codec-sofa-hessian/src/main/java/com/alipay/sofa/rpc/codec/sofahessian/SofaHessianSerializer.java 24 | */ 25 | public class SofaHessianSerializeService implements SerializeService { 26 | 27 | private SerializerFactory serializerFactory = new SerializerFactory(); 28 | 29 | @Override 30 | public ByteString serialize(GrpcResponse response) { 31 | byte[] bytes = encode(response); 32 | return ByteString.copyFrom(bytes); 33 | } 34 | 35 | @Override 36 | public ByteString serialize(GrpcRequest request) { 37 | byte[] bytes = encode(request); 38 | return ByteString.copyFrom(bytes); 39 | } 40 | 41 | @Override 42 | public GrpcRequest deserialize(GrpcService.Request request) { 43 | byte[] bytes = request.getRequest().toByteArray(); 44 | UnsafeByteArrayInputStream inputStream = new UnsafeByteArrayInputStream(bytes); 45 | try { 46 | Hessian2Input input = new Hessian2Input(inputStream); 47 | input.setSerializerFactory(serializerFactory); 48 | GrpcRequest grpcRequest = (GrpcRequest) input.readObject(); 49 | input.close(); 50 | return grpcRequest; 51 | } catch (IOException e) { 52 | throw new GrpcException("sofa-hessian deserialize fail: " + e.getMessage()); 53 | } 54 | } 55 | 56 | @Override 57 | public GrpcResponse deserialize(GrpcService.Response response) { 58 | byte[] bytes = response.getResponse().toByteArray(); 59 | UnsafeByteArrayInputStream inputStream = new UnsafeByteArrayInputStream(bytes); 60 | try { 61 | Hessian2Input input = new Hessian2Input(inputStream); 62 | input.setSerializerFactory(serializerFactory); 63 | GrpcResponse grpcResponse = (GrpcResponse) input.readObject(); 64 | input.close(); 65 | return grpcResponse; 66 | } catch (IOException e) { 67 | throw new GrpcException("sofa-hessian deserialize fail: " + e.getMessage()); 68 | } 69 | } 70 | 71 | private byte[] encode(Object object) { 72 | UnsafeByteArrayOutputStream byteArray = new UnsafeByteArrayOutputStream(); 73 | Hessian2Output output = new Hessian2Output(byteArray); 74 | try { 75 | output.setSerializerFactory(serializerFactory); 76 | output.writeObject(object); 77 | output.close(); 78 | return byteArray.toByteArray(); 79 | } catch (Exception e) { 80 | throw new GrpcException("sofa-hessian serialize fail: " + e.getMessage()); 81 | } 82 | } 83 | 84 | /** 85 | * 参考: 86 | * https://github.com/alipay/sofa-rpc/blob/master/core/common/src/main/java/com/alipay/sofa/rpc/common/struct/UnsafeByteArrayInputStream.java 87 | */ 88 | @NotThreadSafe 89 | class UnsafeByteArrayInputStream extends InputStream { 90 | 91 | byte[] mData; 92 | 93 | int mPosition, mLimit, mMark = 0; 94 | 95 | UnsafeByteArrayInputStream(byte[] buf) { 96 | this(buf, 0, buf.length); 97 | } 98 | 99 | UnsafeByteArrayInputStream(byte[] buf, int offset, int length) { 100 | mData = buf; 101 | mPosition = mMark = offset; 102 | mLimit = Math.min(offset + length, buf.length); 103 | } 104 | 105 | @Override 106 | public int read() { 107 | return (mPosition < mLimit) ? (mData[mPosition++] & 0xff) : -1; 108 | } 109 | 110 | @Override 111 | public int read(byte[] b, int off, int len) { 112 | if (b == null) { 113 | throw new NullPointerException(); 114 | } 115 | if (off < 0 || len < 0 || len > b.length - off) { 116 | throw new IndexOutOfBoundsException(); 117 | } 118 | if (mPosition >= mLimit) { 119 | return -1; 120 | } 121 | if (mPosition + len > mLimit) { 122 | len = mLimit - mPosition; 123 | } 124 | if (len <= 0) { 125 | return 0; 126 | } 127 | System.arraycopy(mData, mPosition, b, off, len); 128 | mPosition += len; 129 | return len; 130 | } 131 | 132 | @Override 133 | public long skip(long len) { 134 | if (mPosition + len > mLimit) { 135 | len = mLimit - mPosition; 136 | } 137 | if (len <= 0) { 138 | return 0; 139 | } 140 | mPosition += len; 141 | return len; 142 | } 143 | 144 | @Override 145 | public int available() { 146 | return mLimit - mPosition; 147 | } 148 | 149 | @Override 150 | public boolean markSupported() { 151 | return true; 152 | } 153 | 154 | @Override 155 | public void mark(int readAheadLimit) { 156 | mMark = mPosition; 157 | } 158 | 159 | @Override 160 | public void reset() { 161 | mPosition = mMark; 162 | } 163 | 164 | @Override 165 | public void close() throws IOException { 166 | } 167 | 168 | } 169 | 170 | /** 171 | * 参考: 172 | * https://github.com/alipay/sofa-rpc/blob/master/core/common/src/main/java/com/alipay/sofa/rpc/common/struct/UnsafeByteArrayOutputStream.java 173 | */ 174 | @NotThreadSafe 175 | class UnsafeByteArrayOutputStream extends OutputStream { 176 | byte[] mBuffer; 177 | 178 | int mCount; 179 | 180 | UnsafeByteArrayOutputStream() { 181 | this(32); 182 | } 183 | 184 | UnsafeByteArrayOutputStream(int size) { 185 | if (size < 0) { 186 | throw new IllegalArgumentException("Negative initial size: " + size); 187 | } 188 | mBuffer = new byte[size]; 189 | } 190 | 191 | @Override 192 | public void write(int b) { 193 | int newcount = mCount + 1; 194 | if (newcount > mBuffer.length) { 195 | mBuffer = Arrays.copyOf(mBuffer, Math.max(mBuffer.length << 1, newcount)); 196 | } 197 | mBuffer[mCount] = (byte) b; 198 | mCount = newcount; 199 | } 200 | 201 | @Override 202 | public void write(byte[] b, int off, int len) { 203 | if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { 204 | throw new IndexOutOfBoundsException(); 205 | } 206 | if (len == 0) { 207 | return; 208 | } 209 | int newcount = mCount + len; 210 | if (newcount > mBuffer.length) { 211 | mBuffer = Arrays.copyOf(mBuffer, Math.max(mBuffer.length << 1, newcount)); 212 | } 213 | System.arraycopy(b, off, mBuffer, mCount, len); 214 | mCount = newcount; 215 | } 216 | 217 | byte[] toByteArray() { 218 | return Arrays.copyOf(mBuffer, mCount); 219 | } 220 | 221 | @Override 222 | public String toString() { 223 | return new String(mBuffer, 0, mCount); 224 | } 225 | 226 | @Override 227 | public void close() throws IOException { 228 | } 229 | 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/util/ClassNameUtils.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.util; 2 | 3 | public class ClassNameUtils { 4 | 5 | /** 6 | * 将 Class 全限定名转化为 beanName 7 | * 8 | * 比如:com.anoyi.service.UserService -> userService 9 | */ 10 | public static String beanName(String className){ 11 | String[] path = className.split("\\."); 12 | String beanName = path[path.length - 1]; 13 | return Character.toLowerCase(beanName.charAt(0)) + beanName.substring(1); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/util/ProtobufUtils.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.util; 2 | 3 | import io.protostuff.LinkedBuffer; 4 | import io.protostuff.ProtostuffIOUtil; 5 | import io.protostuff.runtime.RuntimeSchema; 6 | 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | public class ProtobufUtils { 11 | 12 | // 缓存 schema 对象的 map 13 | private static Map, RuntimeSchema> cachedSchema = new ConcurrentHashMap<>(); 14 | 15 | /** 16 | * 根据获取相应类型的schema方法 17 | */ 18 | @SuppressWarnings({ "unchecked", "unused" }) 19 | private static RuntimeSchema getSchema(Class clazz) { 20 | RuntimeSchema schema = (RuntimeSchema) cachedSchema.get(clazz); 21 | if (schema == null) { 22 | schema = RuntimeSchema.createFrom(clazz); 23 | cachedSchema.put(clazz, schema); 24 | } 25 | return schema; 26 | } 27 | 28 | /** 29 | * 序列化方法,将对象序列化为字节数组(对象 ---> 字节数组) 30 | */ 31 | @SuppressWarnings("unchecked") 32 | public static byte[] serialize(T obj) { 33 | Class clazz = (Class) obj.getClass(); 34 | RuntimeSchema schema = getSchema(clazz); 35 | LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); 36 | return ProtostuffIOUtil.toByteArray(obj, schema, buffer); 37 | } 38 | 39 | /** 40 | * 反序列化方法,将字节数组反序列化为对象(字节数组 ---> 对象) 41 | */ 42 | public static T deserialize(byte[] data, Class clazz) { 43 | RuntimeSchema schema = RuntimeSchema.createFrom(clazz); 44 | T message = schema.newMessage(); 45 | ProtostuffIOUtil.mergeFrom(data, message, schema); 46 | return message; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/java/com/anoyi/grpc/util/SerializeUtils.java: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.util; 2 | 3 | import com.anoyi.grpc.constant.SerializeType; 4 | import com.anoyi.grpc.service.SerializeService; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.util.StringUtils; 7 | 8 | import java.util.Map; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | @Slf4j 12 | public class SerializeUtils { 13 | 14 | private final static Map cachedMap = new ConcurrentHashMap<>(); 15 | 16 | /** 17 | * 获取 序列化/反序列化 工具实例 18 | * 19 | * @param serializeType 序列化工具类型 20 | * @param defaultSerializationService 默认的序列化方式 21 | */ 22 | public static SerializeService getSerializeService(SerializeType serializeType, SerializeService defaultSerializationService) { 23 | if (!StringUtils.isEmpty(serializeType)) { 24 | Integer value = serializeType.getValue(); 25 | SerializeService cachedSerializationService = cachedMap.get(value); 26 | if (cachedSerializationService != null) { 27 | return cachedSerializationService; 28 | } else { 29 | try { 30 | cachedSerializationService = (SerializeService) serializeType.getClazz().newInstance(); 31 | cachedMap.put(value, cachedSerializationService); 32 | } catch (InstantiationException | IllegalAccessException e) { 33 | log.error("{} newInstance error, use default codecService." + serializeType.getClazz().getName()); 34 | cachedSerializationService = defaultSerializationService; 35 | } 36 | return cachedSerializationService; 37 | } 38 | } else { 39 | return defaultSerializationService; 40 | } 41 | } 42 | 43 | /** 44 | * 获取 序列化/反序列化 工具实例 45 | * 46 | * @param value 序列化工具类型 47 | * @param defaultSerializationService 默认的序列化方式 48 | */ 49 | public static SerializeService getSerializeService(int value, SerializeService defaultSerializationService) { 50 | SerializeType serializeType = SerializeType.getSerializeTypeByValue(value); 51 | return getSerializeService(serializeType, defaultSerializationService); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/proto/service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_package = "com.anoyi.rpc"; 4 | option java_outer_classname = "GrpcService"; 5 | option java_multiple_files = false; 6 | 7 | // 定义通用的 Grpc 服务 8 | service CommonService { 9 | // 处理请求 10 | rpc handle ( Request ) returns ( Response ) {} 11 | } 12 | 13 | // 定义通用的 Grpc 请求体 14 | message Request { 15 | int32 serialize = 1; 16 | bytes request = 2; 17 | } 18 | 19 | // 定义通用的 Grpc 响应体 20 | message Response { 21 | bytes response = 1; 22 | } -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/resources/META-INF/spring-configuration-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "hints": [], 3 | "groups": [ 4 | { 5 | "sourceType": "com.anoyi.grpc.config.GrpcProperties", 6 | "name": "spring.grpc", 7 | "type": "com.anoyi.grpc.config.GrpcProperties" 8 | } 9 | ], 10 | "properties": [ 11 | { 12 | "name": "spring.grpc.enable", 13 | "type": "java.lang.Boolean", 14 | "sourceType": "com.anoyi.grpc.config.GrpcProperties" 15 | }, 16 | { 17 | "name": "spring.grpc.port", 18 | "type": "java.lang.Integer", 19 | "sourceType": "com.anoyi.grpc.config.GrpcProperties", 20 | "defaultValue": 6565 21 | }, 22 | { 23 | "name": "spring.grpc.clientInterceptor", 24 | "type": "java.lang.Class", 25 | "sourceType": "com.anoyi.grpc.config.GrpcProperties" 26 | }, 27 | { 28 | "name": "spring.grpc.serverInterceptor", 29 | "type": "java.lang.Class", 30 | "sourceType": "com.anoyi.grpc.config.GrpcProperties" 31 | }, 32 | { 33 | "name": "spring.grpc.enableNameResolverRefresh", 34 | "type": "java.lang.Boolean", 35 | "sourceType": "com.anoyi.grpc.config.GrpcProperties" 36 | }, 37 | { 38 | "name": "spring.grpc.nameResolverInitialDelay", 39 | "type": "java.lang.Integer", 40 | "sourceType": "com.anoyi.grpc.config.GrpcProperties" 41 | }, 42 | { 43 | "name": "spring.grpc.nameResolverPeriod", 44 | "type": "java.lang.Integer", 45 | "sourceType": "com.anoyi.grpc.config.GrpcProperties" 46 | } 47 | ] 48 | 49 | } -------------------------------------------------------------------------------- /spring-boot-starter-grpc/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.anoyi.grpc.config.GrpcAutoConfiguration -------------------------------------------------------------------------------- /stack.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | server: 4 | image: "server:latest" 5 | networks: 6 | - idp_network 7 | deploy: 8 | replicas: 2 9 | endpoint_mode: dnsrr 10 | client: 11 | image: "client:latest" 12 | networks: 13 | - idp_network 14 | ports: 15 | - target: 8081 16 | published: 8081 17 | protocol: tcp 18 | mode: host 19 | deploy: 20 | mode: global 21 | 22 | networks: 23 | idp_network: 24 | external: true -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | ### 使用帮助 2 | 3 | 修改测试报告的输出路径 `pom.xml`: 4 | ``` 5 | 6 | /Users/admin/code/gatling 7 | 8 | ``` 9 | 10 | 执行测试 11 | ``` 12 | mvn gatling:test 13 | ``` -------------------------------------------------------------------------------- /test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.anoyi.grpc 8 | test 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | 14 | io.gatling.highcharts 15 | gatling-charts-highcharts 16 | 3.0.0-RC1 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | io.gatling 25 | gatling-maven-plugin 26 | 3.0.0 27 | 28 | 29 | src/test/com.anoyi.grpc.test 30 | true 31 | 32 | /Users/admin/code/gatling 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/src/test/com.anoyi.grpc.test/V0LocalTest.scala: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.test 2 | 3 | import io.gatling.core.Predef._ 4 | import io.gatling.http.Predef._ 5 | 6 | class V0LocalTest extends Simulation { 7 | 8 | // 测试数据 9 | val data = "{\"id\":1,\"name\":\"anoyi\",\"age\":20,\"gender\":\"男\",\"scores\":{\"语文\":99,\"数学\":20},\"friend\":{\"id\":2,\"name\":\"乖乖\",\"age\":21,\"gender\":\"女\",\"scores\":{\"语文\":10,\"数学\":98},\"friend\":null,\"pet\":null,\"listValue\":[]},\"pet\":{\"type\":\"龙\",\"name\":\"青尊\",\"owner\":{\"id\":1,\"name\":\"anoyi\",\"age\":20,\"gender\":\"男\",\"scores\":null,\"friend\":null,\"pet\":null,\"listValue\":null}},\"listValue\":[1,\"哇哦\"]}" 10 | 11 | // 请求数 12 | val maxCount = 100000 13 | 14 | // 测试 15 | val userCount = 100 16 | val repeatCount = maxCount / userCount 17 | val scn = scenario("性能测试[并发" + userCount + "][总次数" + maxCount + "]").repeat(repeatCount) { 18 | exec( 19 | http("V0-添加用户/本地调用") 20 | .post("http://localhost:8080/v0/user/add") 21 | .header("Content-Type", "application/json") 22 | .body(StringBody(data)) 23 | .check(status.is(200)) 24 | ) 25 | }.repeat(repeatCount) { 26 | exec( 27 | http("V0-查询用户/本地调用") 28 | .get("http://localhost:8080/v0/user/list") 29 | .check(status.is(200)) 30 | ) 31 | } 32 | 33 | setUp(scn.inject(atOnceUsers(userCount))) 34 | 35 | } -------------------------------------------------------------------------------- /test/src/test/com.anoyi.grpc.test/V1SofaHessianTest.scala: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.test 2 | 3 | import io.gatling.core.Predef._ 4 | import io.gatling.http.Predef._ 5 | 6 | class V1SofaHessianTest extends Simulation { 7 | 8 | // 测试数据 9 | val data = "{\"id\":1,\"name\":\"anoyi\",\"age\":20,\"gender\":\"男\",\"scores\":{\"语文\":99,\"数学\":20},\"friend\":{\"id\":2,\"name\":\"乖乖\",\"age\":21,\"gender\":\"女\",\"scores\":{\"语文\":10,\"数学\":98},\"friend\":null,\"pet\":null,\"listValue\":[]},\"pet\":{\"type\":\"龙\",\"name\":\"青尊\",\"owner\":{\"id\":1,\"name\":\"anoyi\",\"age\":20,\"gender\":\"男\",\"scores\":null,\"friend\":null,\"pet\":null,\"listValue\":null}},\"listValue\":[1,\"哇哦\"]}" 10 | 11 | // 请求数 12 | val maxCount = 100000 13 | 14 | // 测试 15 | val userCount = 100 16 | val repeatCount = maxCount / userCount 17 | val scn = scenario("性能测试[并发" + userCount + "][总次数" + maxCount + "]").repeat(repeatCount) { 18 | exec( 19 | http("V1-添加用户/sofa") 20 | .post("http://localhost:8081/v1/user/add") 21 | .header("Content-Type", "application/json") 22 | .body(StringBody(data)) 23 | .check(status.is(200)) 24 | ) 25 | }.repeat(repeatCount) { 26 | exec( 27 | http("V1-查询用户/sofa") 28 | .get("http://localhost:8081/v1/user/list") 29 | .check(status.is(200)) 30 | ) 31 | } 32 | 33 | setUp(scn.inject(atOnceUsers(userCount))) 34 | 35 | } -------------------------------------------------------------------------------- /test/src/test/com.anoyi.grpc.test/V2ProtoStuffTest.scala: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.test 2 | 3 | import io.gatling.core.Predef._ 4 | import io.gatling.http.Predef._ 5 | 6 | class V2ProtoStuffTest extends Simulation { 7 | 8 | // 测试数据 9 | val data = "{\"id\":1,\"name\":\"anoyi\",\"age\":20,\"gender\":\"男\",\"scores\":{\"语文\":99,\"数学\":20},\"friend\":{\"id\":2,\"name\":\"乖乖\",\"age\":21,\"gender\":\"女\",\"scores\":{\"语文\":10,\"数学\":98},\"friend\":null,\"pet\":null,\"listValue\":[]},\"pet\":{\"type\":\"龙\",\"name\":\"青尊\",\"owner\":{\"id\":1,\"name\":\"anoyi\",\"age\":20,\"gender\":\"男\",\"scores\":null,\"friend\":null,\"pet\":null,\"listValue\":null}},\"listValue\":[1,\"哇哦\"]}" 10 | 11 | // 请求数 12 | val maxCount = 100000 13 | 14 | // 测试 15 | val userCount = 100 16 | val repeatCount = maxCount / userCount 17 | val scn = scenario("性能测试[并发" + userCount + "][总次数" + maxCount + "]").repeat(repeatCount) { 18 | exec( 19 | http("V2-添加用户/proto") 20 | .post("http://localhost:8081/v2/user/add") 21 | .header("Content-Type", "application/json") 22 | .body(StringBody(data)) 23 | .check(status.is(200)) 24 | ) 25 | }.repeat(repeatCount) { 26 | exec( 27 | http("V2-查询用户/proto-stuff") 28 | .get("http://localhost:8081/v2/user/list") 29 | .check(status.is(200)) 30 | ) 31 | } 32 | 33 | setUp(scn.inject(atOnceUsers(userCount))) 34 | 35 | } -------------------------------------------------------------------------------- /test/src/test/com.anoyi.grpc.test/V3FastJsonTest.scala: -------------------------------------------------------------------------------- 1 | package com.anoyi.grpc.test 2 | 3 | import io.gatling.core.Predef._ 4 | import io.gatling.http.Predef._ 5 | 6 | class V3FastJsonTest extends Simulation { 7 | 8 | // 测试数据 9 | val data = "{\"id\":1,\"name\":\"anoyi\",\"age\":20,\"gender\":\"男\",\"scores\":{\"语文\":99,\"数学\":20},\"friend\":{\"id\":2,\"name\":\"乖乖\",\"age\":21,\"gender\":\"女\",\"scores\":{\"语文\":10,\"数学\":98},\"friend\":null,\"pet\":null,\"listValue\":[]},\"pet\":{\"type\":\"龙\",\"name\":\"青尊\",\"owner\":{\"id\":1,\"name\":\"anoyi\",\"age\":20,\"gender\":\"男\",\"scores\":null,\"friend\":null,\"pet\":null,\"listValue\":null}},\"listValue\":[1,\"哇哦\"]}" 10 | 11 | // 请求数 12 | val maxCount = 100000 13 | 14 | // 测试 15 | val userCount = 100 16 | val repeatCount = maxCount / userCount 17 | val scn = scenario("性能测试[并发" + userCount + "][总次数" + maxCount + "]").repeat(repeatCount) { 18 | exec( 19 | http("V3-添加用户/fastjson") 20 | .post("http://localhost:8081/v3/user/add") 21 | .header("Content-Type", "application/json") 22 | .body(StringBody(data)) 23 | .check(status.is(200)) 24 | ) 25 | }.repeat(repeatCount) { 26 | exec( 27 | http("V3-查询用户/fastjson") 28 | .get("http://localhost:8081/v3/user/list") 29 | .check(status.is(200)) 30 | ) 31 | } 32 | 33 | setUp(scn.inject(atOnceUsers(userCount))) 34 | 35 | } --------------------------------------------------------------------------------