gRPC-Java实现各种服务类型

2023-11-08

gRPC实现

借助gRPC,我们可以在一个.proto文件中定义一次服务,并以gRPC支持的任何语言生成客户端和服务器,而这又可以在从大型数据中心内的服务器到电脑的各种环境中运行– gRPC为您处理不同的语言和环境。还获得了使用协议缓冲区的所有优点,包括有效的序列化,简单的IDL和轻松的接口更新。

grpc中有四种服务类型:

简单rpc:这就是一般的rpc调用,一个请求对象对应一个返回对象

服务端流式rpc:一个请求对象,服务端可以传回多个结果对象

客户端流式rpc:客户端传入多个请求对象,服务端返回一个响应结果

双向流式rpc:结合客户端流式rpc和服务端流式rpc,可以传入多个对象,返回多个响应对象

grpc通过使用流式的方式,返回/接受多个实例可以用于类似不定长数组的入参和出参

准备工作

创建maven项目grpc和生成proto接口项目grpc-protos

grpc-protos项目

在grpc-protos下创建接口文件route_guide.proto,内容如下

// 使用proto3语法
syntax = "proto3";

// 生成多个类
option java_multiple_files = true;

// 生成java类所在的包
option java_package = "io.grpc.examples.routeguide";

// 生成外层类类名
option java_outer_classname = "RouteGuideProto";

// Objective-C类的前缀
option objc_class_prefix = "RTG";

// proto包名
package routeguide;

// 定义RPC服务RouteGuide
service RouteGuide {

    // 简单RPC接受SimpleRpcReq参数返回SimpleRpcRes类型对象
    rpc GetFeature(SimpleRpcReq) returns (SimpleRpcRes) {}

    // 服务端到客户端流式RPC,接受ServerToClientStreamRpcReq对象参数,返回批量ServerToClientStreamRpcRes数据
    rpc ListFeatures(ServerToClientStreamRpcReq) returns (stream ServerToClientStreamRpcRes) {}

    // 客户端到服务端流式RPC,接受批量Point数据,返回RouteSummary类型对象
    rpc RecordRoute(stream ClientToServerStreamRpcReq) returns (ClientToServerStreamRpcReq) {}

    // 双向流式RPC,接受批量RouteNote类型数据,返回批量RouteNote类型数据
    rpc RouteChat(stream TwoWayStreamRpcReq) returns (stream TwoWayStreamRpcRes) {}
}

/** 简单RPC **/
// Point
message SimpleRpcReq {
    int32 latitude = 1;
    int32 longitude = 2;
}

// Feature
message SimpleRpcRes {
    string name = 1;
    Point location = 2;
}
/** 简单RPC **/


/** 服务端到客户端流式RPC **/
message ServerToClientStreamRpcReq {
    Point lo = 1;
    Point hi = 2;
}

// Feature
message ServerToClientStreamRpcRes {
    string name = 1;
    Point location = 2;
}
/** 服务端到客户端流式RPC **/


/** 客户端到服务端流式RPC **/
// Point
message ClientToServerStreamRpcReq {
    Point lo = 1;
    Point hi = 2;
}

message ClientToServerStreamRpcRes {
    int32 point_count = 1;
    int32 feature_count = 2;
    int32 distance = 3;
    int32 elapsed_time = 4;
}
/** 客户端到服务端流式RPC **/


/** 客户端到服务端流式RPC **/
message TwoWayStreamRpcReq {
    Point location = 1;
    string message = 2;
}


message TwoWayStreamRpcRes {
    Point location = 1;
    string message = 2;
}
/** 客户端到服务端流式RPC **/


message Point {
    int32 latitude = 1;
    int32 longitude = 2;
}

message Feature {
    string name = 1;
    Point location = 2;
}

grpc项目

pom文件内容如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.starnet</groupId>
    <artifactId>grpc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>11</java.version>
        <grpc.version>1.29.0</grpc.version>
        <protobuf.version>3.11.0</protobuf.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>${grpc.version}</version>
        </dependency>

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>${grpc.version}</version>
        </dependency>

        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>${grpc.version}</version>
        </dependency>

        <dependency> <!-- necessary for Java 9+ -->
            <groupId>org.apache.tomcat</groupId>
            <artifactId>annotations-api</artifactId>
            <version>6.0.53</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.5.0.Final</version>
            </extension>
        </extensions>

        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.5.1</version>

                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
                    <protoSourceRoot>../grpc-protos</protoSourceRoot>
                </configuration>

                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>


</project>

注意protobuf 插件中配置的protoSourceRoot要是protos接口目录的绝对路径或者相对路径,因为插件需要根据该目录找到proto文件去生成相关代码;如果没有设置protoSourceRoot的话默认为项目的src/main/proto目录

生成grpc protobuf相关类

在grpc目录执行,或者通过Idea直接进行生成

mvn protobuf:compile
mvn protobuf:compile-custom

在target目录下生成了对应的代码
在这里插入图片描述

代码实现

服务端

启动gRPC Server

import io.grpc.Server;
import io.grpc.ServerBuilder;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class GRpcServer {

    private Server server;

    private void start() throws IOException {

        /* The port on which the server should run */
        int port = 50051;

        server = ServerBuilder.forPort(port)
                .addService(new RouteGuideService())  //这里可以添加多个模块
                .build()
                .start();

        System.out.println("Server started, listening on " + port);

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                // Use stderr here since the logger may have been reset by its JVM shutdown hook.
                System.err.println("shutting down gRPC server since JVM is shutting down");

                try {
                    GRpcServer.this.stop();
                } catch (InterruptedException e) {
                    e.printStackTrace(System.err);
                }

                System.err.println("server shut down");
            }
        });
    }

    private void stop() throws InterruptedException {

        if (server != null) {
            server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
        }
    }

    private void blockUntilShutdown() throws InterruptedException {

        if (server != null) {
            server.awaitTermination();
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {

        GRpcServer server = new GRpcServer();
        server.start();
        server.blockUntilShutdown();
    }
}
gRPC服务端处理
import io.grpc.examples.routeguide.*;
import io.grpc.stub.StreamObserver;

import java.util.ArrayList;
import java.util.List;

public class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {

    /**
     * 简单RPC接受SimpleRpcReq参数返回SimpleRpcRes类型对象
     * @param request
     * @param responseObserver
     */
    @Override
    public void getSimpleRpcRes(SimpleRpcReq request, StreamObserver<SimpleRpcRes> responseObserver) {

        Point location = Point.newBuilder()
                .setLatitude(request.getLatitude())
                .setLongitude(request.getLongitude())
                .build();;

        SimpleRpcRes simpleRpcRes = SimpleRpcRes
                .newBuilder()
                .setName("fuzhou")
                .setLocation(location)
                .build();

        // 通过responseObserver.onNext向客户端发送消息
        responseObserver.onNext(simpleRpcRes);
        // 标记服务端响应完成
        responseObserver.onCompleted();
    }

    /**
     * 服务端到客户端流式RPC,接受ServerToClientStreamRpcReq对象参数,返回批量ServerToClientStreamRpcRes数据
     * @param request
     * @param responseObserver
     */
    @Override
    public void listServerToClientStreamRpcRes(ServerToClientStreamRpcReq request, StreamObserver<ServerToClientStreamRpcRes> responseObserver) {

        List<Point> pointList = new ArrayList<>();
        pointList.add(request.getLo());
        pointList.add(request.getHi());

        int i = 1;
        for (Point point : pointList) {
            ServerToClientStreamRpcRes res = ServerToClientStreamRpcRes.newBuilder()
                    .setName("fuzhou" + i++)
                    .setLocation(point)
                    .build();

            // 循环调用responseObserver.onNext向客户端回调发送数据
            responseObserver.onNext(res);
        }

        responseObserver.onCompleted();
    }

    /**
     * 客户端到服务端流式RPC,接受批量Point数据,返回RouteSummary类型对象
     * @param responseObserver
     * @return
     */
    @Override
    public StreamObserver<ClientToServerStreamRpcReq> getClientToServerStreamRpcRes(StreamObserver<ClientToServerStreamRpcRes> responseObserver) {

        // 构造观察者与客户端交互
        return new StreamObserver<ClientToServerStreamRpcReq>() {
            int pointCount;

            // 响应客户端
            @Override
            public void onNext(ClientToServerStreamRpcReq point) {

                System.out.println("point is " + point);
                pointCount++;
            }

            @Override
            public void onError(Throwable t) {

                System.out.println("client to server stream rpc is cancelled");
            }

            // 在客户端调用requestObserver.onCompleted()时触发,标记服务端处理完成
            @Override
            public void onCompleted() {

                // 回调客户端responseObserver.onNext
                responseObserver.onNext(ClientToServerStreamRpcRes.newBuilder().setPointCount(pointCount).build());

                // 回调客户端responseObserver.onCompleted标记完成
                responseObserver.onCompleted();
            }
        };
    }

    /**
     * 双向流式RPC,接受批量RouteNote类型数据,返回批量RouteNote类型数据
     * @param responseObserver
     * @return
     */
    @Override
    public StreamObserver<TwoWayStreamRpcReq> listTwoWayStreamRpcRes(StreamObserver<TwoWayStreamRpcRes> responseObserver) {

        // 构造观察者与客户端交互
        return new StreamObserver<TwoWayStreamRpcReq>() {

            // 响应客户端
            @Override
            public void onNext(TwoWayStreamRpcReq point) {

                // 回调客户端responseObserver.onNext
                responseObserver.onNext(TwoWayStreamRpcRes.newBuilder().setMessage("res" + point.getMessage())
                        .setLocation(Point.newBuilder()
                                .setLatitude(point.getLocation().getLatitude() + 100)
                                .setLongitude(point.getLocation().getLongitude() + 100)
                                .build())
                        .build());
            }

            @Override
            public void onError(Throwable t) {

                System.out.println("client to server stream rpc is cancelled");
            }

            // 在客户端调用requestObserver.onCompleted()时触发,标记服务端处理完成
            @Override
            public void onCompleted() {

                // 回调客户端responseObserver.onCompleted标记完成
                responseObserver.onCompleted();
            }
        };
    }
}
客户端
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.examples.routeguide.*;
import io.grpc.stub.StreamObserver;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class GRpcClient {

    //远程连接管理器,管理连接的生命周期
    private final ManagedChannel channel;
    private final RouteGuideGrpc.RouteGuideBlockingStub blockingStub;
    private final RouteGuideGrpc.RouteGuideStub stub;

    Random random = new Random();

    public GRpcClient(String host, int port) {
        //初始化连接
        channel = ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext()
                .build();

        //初始化远程服务Stub
        blockingStub = RouteGuideGrpc.newBlockingStub(channel);
        stub = RouteGuideGrpc.newStub(channel);
    }


    public void shutdown() throws InterruptedException {
        //关闭连接
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    public static void main(String[] args) throws Exception {

        GRpcClient client = new GRpcClient("127.0.0.1", 50051);

        // 简单gRPC交互
        System.out.println("simple rpc start!!!!!!");
        SimpleRpcRes simpleRpcRes = client.getSimpleRpcRes(12, 16);
        System.out.println("simple rpc response: ");
        System.out.println(simpleRpcRes);
        System.out.println("simple rpc over!!!!!!");
        System.out.println("\n===================================================\n");


        // 服务端到客户端流式交互
        System.out.println("server to client stream rpc start!!!!!!");
        Iterator<ServerToClientStreamRpcRes> serverToClientStreamRpcResIterator = client.listServerToClientStreamRpcRes(11, 22, 666, 777);
        System.out.println("server to client stream rpc response: ");
        while (serverToClientStreamRpcResIterator.hasNext()) {
            System.out.println(serverToClientStreamRpcResIterator.next());
        }
        System.out.println("server to client stream rpc over!!!!!!");
        System.out.println("\n===================================================\n");


        // 客户端到服务端流式交互
        System.out.println("client to server stream rpc start!!!!!!");
        client.getClientToServerStreamRpcRes(10);
        System.out.println("client to server stream rpc over!!!!!!");
        System.out.println("\n===================================================\n");


        // 客户端到服务端流式交互
        System.out.println("two way stream rpc start!!!!!!");
        client.listTwoWayStreamRpcRes(5);
        System.out.println("two way stream rpc over!!!!!!");

        //关闭连接
        client.shutdown();
    }

    public SimpleRpcRes getSimpleRpcRes(int lat, int lon) {

        // 构造服务调用参数对象
        SimpleRpcReq request = SimpleRpcReq.newBuilder().setLatitude(lat).setLongitude(lon).build();
        // 调用远程服务方法
        return blockingStub.getSimpleRpcRes(request);
    }


    public Iterator<ServerToClientStreamRpcRes> listServerToClientStreamRpcRes(int lowLag, int lowLon, int highLag, int highLong) {

        // 构造服务调用参数对象
        ServerToClientStreamRpcReq request = ServerToClientStreamRpcReq.newBuilder()
                .setLo(Point.newBuilder().setLatitude(lowLag).setLongitude(lowLon).build())
                .setHi(Point.newBuilder().setLatitude(highLag).setLongitude(highLong).build()).build();
        // 调用远程服务方法
        return blockingStub.listServerToClientStreamRpcRes(request);
    }

    private void getClientToServerStreamRpcRes(int pointNum) throws Exception {

        List<ClientToServerStreamRpcReq> pointList = new ArrayList<>();
        for (int i = 1; i <= pointNum; i++) {
            pointList.add(ClientToServerStreamRpcReq.newBuilder().setLatitude(i).setLongitude(i).build());
        }

        CountDownLatch finishLatch = new CountDownLatch(1);
        // 创建responseObserver用于服务端回调客户端
        StreamObserver<ClientToServerStreamRpcRes> responseObserver = new StreamObserver<ClientToServerStreamRpcRes>() {

            // 响应服务端responseObserver.onNext回调
            @Override
            public void onNext(ClientToServerStreamRpcRes res) {

                System.out.println("client to server stream rpc response: ");
                System.out.println(res);
            }

            // 响应服务端responseObserver.onError回调
            @Override
            public void onError(Throwable t) {

                System.out.println("client to server stream is error " + t.getMessage() + ": " + t);
            }

            // 响应服务端responseObserver.onCompleted的回调
            @Override
            public void onCompleted() {

                System.out.println("client to server stream, server has been over");
                finishLatch.countDown();
            }
        };

        // 通过异步存根发起调用,参数为响应观察者responseObserver
        StreamObserver<ClientToServerStreamRpcReq> requestObserver = stub.getClientToServerStreamRpcRes(responseObserver);
        try {
            for (ClientToServerStreamRpcReq point : pointList) {
//                Thread.sleep(random.nextInt(1000) + 500);
                if (finishLatch.getCount() == 0) {
                    return;
                }
                // 多次调用requestObserver.onNext向服务端写入数据
                requestObserver.onNext(point);
            }
        } catch (Exception e) {
            requestObserver.onError(e);
        }
        // 标记客户端写入结束
        requestObserver.onCompleted();

        // 由于是异步获得结果,所以sleep一秒,不然程序就已经结束了
        Thread.sleep(1000);
    }

    private void listTwoWayStreamRpcRes(int pointNum) throws Exception {

        List<TwoWayStreamRpcReq> rpcReqList = new ArrayList<>();
        for (int i = 1; i <= pointNum; i++) {
            rpcReqList.add(TwoWayStreamRpcReq.newBuilder().setMessage("" + i)
                    .setLocation(Point.newBuilder().setLatitude(i).setLongitude(i).build())
                    .build());
        }

        CountDownLatch finishLatch = new CountDownLatch(1);
        // 创建responseObserver用于服务端回调客户端
        StreamObserver<TwoWayStreamRpcRes> responseObserver = new StreamObserver<TwoWayStreamRpcRes>() {

            // 响应服务端responseObserver.onNext回调
            @Override
            public void onNext(TwoWayStreamRpcRes res) {

                System.out.println("two way stream rpc response: ");
                System.out.println(res);
            }

            // 响应服务端responseObserver.onError回调
            @Override
            public void onError(Throwable t) {

                System.out.println("two way stream is error " + t.getMessage() + ": " + t);
            }

            // 响应服务端responseObserver.onCompleted的回调
            @Override
            public void onCompleted() {

                System.out.println("two way stream, server has been over");
                finishLatch.countDown();
            }
        };

        // 通过异步存根发起调用,参数为响应观察者responseObserver
        StreamObserver<TwoWayStreamRpcReq> requestObserver = stub.listTwoWayStreamRpcRes(responseObserver);
        try {
            for (TwoWayStreamRpcReq point : rpcReqList) {
//                Thread.sleep(random.nextInt(1000) + 500);
                if (finishLatch.getCount() == 0) {
                    return;
                }
                // 多次调用requestObserver.onNext向服务端写入数据
                requestObserver.onNext(point);
            }
        } catch (Exception e) {
            requestObserver.onError(e);
        }
        // 标记客户端写入结束
        requestObserver.onCompleted();

        // 由于是异步获得结果,所以sleep一秒,不然程序就已经结束了
        Thread.sleep(1000);
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

gRPC-Java实现各种服务类型 的相关文章

随机推荐

  • FreeRTOS+CubeMX系列第一篇——初识FreeRTOS

    文章目录 一 关于FreeRTOS 二 FreeRTOS的特点 三 如何在CubeMX上配置FreeRTOS 四 FreeRTOS文档资料 五 同系列博客 一 关于FreeRTOS 1 什么是FreeRTOS FreeRTOS是一个迷你的实
  • goland os.Open 路径错误

    os Open 报错原因 Open不是当前目录下查找 项目取的是项目根目录 不是执行目录 证明如下 exPath os Getwd fmt Println expath exPath file err os Open exPath file
  • 504 错误码排查

    当出现 504 错误码时 表示请求超时 服务器无法及时响应请求 需要检查下应用是否有什么耗时的操作 比如是否出现了 SQL 慢查询 是否接口发生死循环 是否出现死锁等 同时需要关注服务器系统负载高不高 网络异常 接口原本好好的 突然出现超时
  • JAVA基础算法——将字符串按照字典倒序排序并输出

    JAVA基础算法 将字符串按照字典倒序输出 作者 飞不高的鱼 转载请注明出处 import java util Arrays import java util Comparator 将已知的乱序字符串按照字典倒序排序输出 author xi
  • 矩阵的投影、线性拟合与最小二乘法

    概要 投影矩阵 如果一个b向量进行矩阵运算 Pb 那么向量b就会投影要A的列空间的最近点 目录 一 矩阵的四大基础子空间 二 投影矩阵 三 最小二乘法 一 矩阵的四大基础子空间 一个矩阵有4个子空间 分别是行空间 零空间 列空间和左零空间
  • C#中的委托、事件与接口

    C 中的委托 事件与接口 一 委托 一 委托链 二 匿名函数 三 Lamda语句 二 事件 三 接口 一 委托 委托 将方法以变量的形式传递 以方法的形式执行 注意 赋值方法的返回类型 参数要与委托的一致 如委托定义的参数类型是string
  • 微信小程序上传文件组件

    微信小程序上传文件 一 说明 该拍照组件带有微信授权相机功能 会结合后端接口 将上传的图片以数组集合的形式传值给父级页面 注意 组件适用于 单独上传图片 不携带参数 结合后端接口返回路径之后 再调用另外的保存接口 携带参数与图片提交的场景
  • 抓取第三方网站数据

    最近需要把某网站的统计数据聚合到我们自己的系统里 但是该网站没有提供标准API 所以就尝试自己抓取了一下 本文总结一下一般的方法 分析服务地址 通常网站有2种做法 一种是后端渲染 直接把渲染后的完整界面呈现在浏览器 另一种前端是静态页面 通
  • mybatis plus中的${ew.sqlSegment},${ew.sqlSelect},${ew.customSqlSegment},${ew.sqlSet}使用

    ew是mapper方法里的 Param Constants WRAPPER Wrapper queryWrapper对象 首先判断ew emptyOfWhere是否存在where条件 有的话再拼接上去 ew customSqlSegment
  • java毕业设计——基于JSP+JavaBean+sqlserver的在线购物系统设计与实现(毕业论文+程序源码)——在线购物系统

    基于JSP JavaBean sqlserver的在线购物系统设计与实现 毕业论文 程序源码 大家好 今天给大家介绍基于JSP JavaBean sqlserver的在线购物系统设计与实现 文章末尾附有本毕业设计的论文和源码下载地址哦 需要
  • 品味树莓派:GPIO Zero库使用入门

    文章目录 目的 基础说明 入门使用 LED PWMLED Button 更多入门例程 类基础说明 注意事项 总结 目的 树莓派有很多GPIO口可供用户使用 官方同时也提供了一些方式来操作这些IO口 其中目前主要推荐的是基于Python的GP
  • 1、什么是Shader

    什么是Shader Shader 中文名为着色器 Shader其实就是专门用来渲染图形的一种技术 通过shader 我们可以自定义显卡渲染画面的算法 使画面达到我们想要的效果 小到每一个像素点 大到整个屏幕 Shader分为两类 顶点Sha
  • 使用Gradle构建SpringBoot工程系列:第八篇:使用spring-data-jpa 实现数据持久化

    本篇文章是SpringBoot 系列文章的第八篇文章 由于本人工作原因 中断了一段时间 接下来的一段时间里 将陆续更新本系列的其他文章 回归Spring Boot技术体系 记录本人学习和使用Gradle构建spring Boot工程的过程
  • C/C++蓝桥杯三升序列

    本题答案可能是错的 我算出的是180414 和很多博主的答案不一样 我也不太懂哪里有问题 大家可以探讨一下 题目描述 对于一个字母矩阵 我们称矩阵中的一个三升序列是指在矩阵中找到三个字母 它们在同一行 同一列 或者在同一45 度的斜线上 这
  • OpenFlow流表_时间因素

    目标 现有拓扑结构如下的网络结构 s1 s4为交换机 h1 h9为主机 现欲让h1和h2白天ping不通 晚上ping的通 拓扑结构 s1
  • es6查找指定字符下标,并把第一个字符前面的内容删掉

    前言 用es6实现查找指定字符下标 并把第一个字符前面的内容删掉 原字符串 abc 123 bcd 444 目标字符 123 bcd 444 实现方法 let abc abc 123 bcd 444 let mmm abc indexOf
  • springcloud与springboot版本列表查看及依赖关系查看

    一 版本规则说明 1 springboot版本 Spring Boot 2 2 5 RELEASE表示主版本 次版本 增量版本 Bug修复 主要 版本中的第一个数字 2 和 3 是 Python 的著名 主要版本 主要部分是基于日历的最常见
  • (华硕)电脑第一次开机蓝屏,二次开机启动

    华硕主板 第一次开机蓝屏 二次开机启动 使用了无缓存的M 2 SSD 2个通道的SSD 修改开机模式 改成兼容模式启动
  • 【H5】阻止H5页面播放视频默认全屏

    老是看到有人找不到阻止视频默认全屏的问题 看到别人发的帖子不是隐藏video标签使用canvas绘制视频就是使用插件来禁止视频默认全屏的问题 其实没有那么麻烦的只需要设置一下属性就可以了 x5 playsinline true 安卓需要设置
  • gRPC-Java实现各种服务类型

    gRPC实现 借助gRPC 我们可以在一个 proto文件中定义一次服务 并以gRPC支持的任何语言生成客户端和服务器 而这又可以在从大型数据中心内的服务器到电脑的各种环境中运行 gRPC为您处理不同的语言和环境 还获得了使用协议缓冲区的所