文章
问答
冒泡
gRPC的四类服务方法

概述

目前gRPC允许定义四类服务方法:

Unary RPC(单项RPC)

即客户端发送一个请求给服务端,从服务端获取一个应答,就像通常的http的请求/响应。

Server streaming RPC(服务端流式RPC)

即客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取直到没有更多消息为止(服务端完成本次请求)。

Client streaming RPC(客户端流式RPC)

即客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答。

Bidirectional streaming RPC(双向流式RPC)

即两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,例如:服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者是读写相结合的其他方式。每个数据流里消息的顺序会被保持。

Java案例

服务定义

routeguide.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.wy.routeguide";
option java_outer_classname = "RouteGuideProto";
option objc_class_prefix = "HLW";

// The greeting service definition.
service RouteGuide {
    // A simple RPC.
    rpc GetFeature(Point) returns (Feature) {}

    // A server-to-client streaming RPC.
    rpc ListFeatures(Rectangle) returns (stream Feature) {}

    // A client-to-server streaming RPC.
    rpc RecordRoute(stream Point) returns (RouteSummary) {}

    // A Bidirectional streaming RPC.
    rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}

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

message Rectangle {
    Point lo = 1;
    Point hi = 2;
}

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

message RouteNote {
    Point location = 1;
    string message = 2;
}

message RouteSummary {
    int32 point_count = 1;
    int32 feature_count = 2;
    int32 distance = 3;
    int32 elapsed_time = 4;
}

message FeatureDatabase {
    repeated Feature feature = 1;
}

客户端的代码

RouteGuideClient.java

package com.wy.routeguide.client;

import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.Message;
import com.wy.routeguide.Feature;
import com.wy.routeguide.Point;
import com.wy.routeguide.Rectangle;
import com.wy.routeguide.RouteGuideGrpc;
import com.wy.routeguide.RouteGuideGrpc.RouteGuideBlockingStub;
import com.wy.routeguide.RouteGuideGrpc.RouteGuideStub;
import com.wy.routeguide.RouteNote;
import com.wy.routeguide.RouteSummary;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@Slf4j
public class RouteGuideClient {
    private final RouteGuideBlockingStub blockingStub;
    private final RouteGuideStub asyncStub;

    private Random random = new Random();
    private TestHelper testHelper;

    public RouteGuideClient(Channel channel) {
        blockingStub = RouteGuideGrpc.newBlockingStub(channel);
        asyncStub = RouteGuideGrpc.newStub(channel);
    }

    public void getFeature(int lat, int lon) {
        log.info("*** GetFeature: lat={} lon={}", lat, lon);

        Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build();

        Feature feature;
        try {
            feature = blockingStub.getFeature(request);
            if (testHelper != null) {
                testHelper.onMessage(feature);
            }
        } catch (StatusRuntimeException e) {
            log.warn("RPC failed: {}", e.getStatus());
            if (testHelper != null) {
                testHelper.onRpcError(e);
            }
            return;
        }

        if (RouteGuideUtil.exists(feature)) {
            log.info("Found feature called \"{}\" at {}, {}",
                    feature.getName(),
                    RouteGuideUtil.getLatitude(feature.getLocation()),
                    RouteGuideUtil.getLongitude(feature.getLocation()));
        } else {
            log.info("Found no feature at {}, {}",
                    RouteGuideUtil.getLatitude(feature.getLocation()),
                    RouteGuideUtil.getLongitude(feature.getLocation()));
        }
    }

    public void listFeatures(int lowLat, int lowLon, int hiLat, int hiLon) {
        log.info("*** ListFeatures: lowLat={} lowLon={} hiLat={} hiLon={}", lowLat, lowLon, hiLat,
                hiLon);

        Rectangle request =
                Rectangle.newBuilder()
                        .setLo(Point.newBuilder().setLatitude(lowLat).setLongitude(lowLon).build())
                        .setHi(Point.newBuilder().setLatitude(hiLat).setLongitude(hiLon).build()).build();
        Iterator<Feature> features;
        try {
            features = blockingStub.listFeatures(request);
            for (int i = 1; features.hasNext(); i++) {
                Feature feature = features.next();
                log.info("Result #" + i + ": {}", feature);
                if (testHelper != null) {
                    testHelper.onMessage(feature);
                }
            }
        } catch (StatusRuntimeException e) {
            log.warn("RPC failed: {}", e.getStatus());
            if (testHelper != null) {
                testHelper.onRpcError(e);
            }
        }
    }

    public void recordRoute(List<Feature> features, int numPoints) throws InterruptedException {
        log.info("*** RecordRoute");
        final CountDownLatch finishLatch = new CountDownLatch(1);
        StreamObserver<RouteSummary> responseObserver = new StreamObserver<RouteSummary>() {
            @Override
            public void onNext(RouteSummary summary) {
                log.info("Finished trip with {} points. Passed {} features. "
                                + "Travelled {} meters. It took {} seconds.", summary.getPointCount(),
                        summary.getFeatureCount(), summary.getDistance(), summary.getElapsedTime());
                if (testHelper != null) {
                    testHelper.onMessage(summary);
                }
            }

            @Override
            public void onError(Throwable t) {
                log.warn("RecordRoute Failed: {}", Status.fromThrowable(t));
                if (testHelper != null) {
                    testHelper.onRpcError(t);
                }
                finishLatch.countDown();
            }

            @Override
            public void onCompleted() {
                log.info("Finished RecordRoute");
                finishLatch.countDown();
            }
        };

        StreamObserver<Point> requestObserver = asyncStub.recordRoute(responseObserver);
        try {
            // Send numPoints points randomly selected from the features list.
            for (int i = 0; i < numPoints; ++i) {
                int index = random.nextInt(features.size());
                Point point = features.get(index).getLocation();
                log.info("Visiting point {}, {}", RouteGuideUtil.getLatitude(point),
                        RouteGuideUtil.getLongitude(point));
                requestObserver.onNext(point);
                // Sleep for a bit before sending the next one.
                Thread.sleep(random.nextInt(1000) + 500);
                if (finishLatch.getCount() == 0) {
                    // RPC completed or errored before we finished sending.
                    // Sending further requests won't error, but they will just be thrown away.
                    return;
                }
            }
        } catch (RuntimeException e) {
            // Cancel RPC
            requestObserver.onError(e);
            throw e;
        }
        // Mark the end of requests
        requestObserver.onCompleted();

        // Receiving happens asynchronously
        if (!finishLatch.await(1, TimeUnit.MINUTES)) {
            log.warn("recordRoute can not finish within 1 minutes");
        }
    }

    /**
     * Bi-directional example, which can only be asynchronous. Send some chat messages, and print any
     * chat messages that are sent from the client.
     */
    public CountDownLatch routeChat() {
        log.info("*** RouteChat");
        final CountDownLatch finishLatch = new CountDownLatch(1);
        StreamObserver<RouteNote> requestObserver =
                asyncStub.routeChat(new StreamObserver<RouteNote>() {
                    @Override
                    public void onNext(RouteNote note) {
                        log.info("Got message \"{}\" at {}, {}", note.getMessage(), note.getLocation()
                                .getLatitude(), note.getLocation().getLongitude());
                        if (testHelper != null) {
                            testHelper.onMessage(note);
                        }
                    }

                    @Override
                    public void onError(Throwable t) {
                        log.warn("RouteChat Failed: {}", Status.fromThrowable(t));
                        if (testHelper != null) {
                            testHelper.onRpcError(t);
                        }
                        finishLatch.countDown();
                    }

                    @Override
                    public void onCompleted() {
                        log.info("Finished RouteChat");
                        finishLatch.countDown();
                    }
                });

        try {
            RouteNote[] requests =
                    {newNote("First message", 0, 0), newNote("Second message", 0, 10_000_000),
                            newNote("Third message", 10_000_000, 0), newNote("Fourth message", 10_000_000, 10_000_000)};

            for (RouteNote request : requests) {
                log.info("Sending message \"{}\" at {}, {}"
                        , request.getMessage()
                        , request.getLocation().getLatitude()
                        , request.getLocation().getLongitude());
                requestObserver.onNext(request);
            }
        } catch (RuntimeException e) {
            // Cancel RPC
            requestObserver.onError(e);
            throw e;
        }
        // Mark the end of requests
        requestObserver.onCompleted();

        // return the latch while receiving happens asynchronously
        return finishLatch;
    }

    /** Issues several different requests and then exits. */
    public static void main(String[] args) throws InterruptedException {
        String target = "localhost:8980";
        if (args.length > 0) {
            if ("--help".equals(args[0])) {
                System.err.println("Usage: [target]");
                System.err.println("");
                System.err.println("  target  The client to connect to. Defaults to " + target);
                System.exit(1);
            }
            target = args[0];
        }

        List<Feature> features;
        try {
            features = RouteGuideUtil.parseFeatures(RouteGuideUtil.getDefaultFeaturesFile());
        } catch (IOException ex) {
            ex.printStackTrace();
            return;
        }

        ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build();
        try {
            RouteGuideClient client = new RouteGuideClient(channel);
            // Looking for a valid feature
            client.getFeature(409146138, -746188906);

            // Feature missing.
            client.getFeature(0, 0);

            // Looking for features between 40, -75 and 42, -73.
            client.listFeatures(400000000, -750000000, 420000000, -730000000);

            // Record a few randomly selected points from the features file.
            client.recordRoute(features, 10);

            // Send and receive some notes.
            CountDownLatch finishLatch = client.routeChat();

            if (!finishLatch.await(1, TimeUnit.MINUTES)) {
                log.warn("routeChat can not finish within 1 minutes");
            }
        } finally {
            channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
        }
    }

    private RouteNote newNote(String message, int lat, int lon) {
        return RouteNote.newBuilder().setMessage(message)
                .setLocation(Point.newBuilder().setLatitude(lat).setLongitude(lon).build()).build();
    }

    /**
     * Only used for unit test, as we do not want to introduce randomness in unit test.
     */
    @VisibleForTesting
    void setRandom(Random random) {
        this.random = random;
    }

    /**
     * Only used for helping unit test.
     */
    @VisibleForTesting
    interface TestHelper {
        /**
         * Used for verify/inspect message received from client.
         */
        void onMessage(Message message);

        /**
         * Used for verify/inspect error received from client.
         */
        void onRpcError(Throwable exception);
    }

    @VisibleForTesting
    void setTestHelper(TestHelper testHelper) {
        this.testHelper = testHelper;
    }
}

服务端的代码

RouteGuideServiceImpl.java

package com.wy.routeguide.server;

import com.wy.routeguide.Feature;
import com.wy.routeguide.Point;
import com.wy.routeguide.Rectangle;
import com.wy.routeguide.RouteGuideGrpc.RouteGuideImplBase;
import com.wy.routeguide.RouteNote;
import com.wy.routeguide.RouteSummary;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;

import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static java.lang.Math.atan2;
import static java.lang.Math.cos;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.sin;
import static java.lang.Math.sqrt;
import static java.lang.Math.toRadians;
import static java.util.concurrent.TimeUnit.NANOSECONDS;

@Slf4j
public class RouteGuideServiceImpl extends RouteGuideImplBase {
    private final Collection<Feature> features;
    private final ConcurrentMap<Point, List<RouteNote>> routeNotes = new ConcurrentHashMap<>();

    {
        try{
            URL defFileUrl = RouteGuideUtil.getDefaultFeaturesFile();
            features = RouteGuideUtil.parseFeatures(defFileUrl);
        } catch (Exception e) {
            throw new RuntimeException("解析错误", e);
        }
    }

    @Override
    public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
        responseObserver.onNext(checkFeature(request));
        responseObserver.onCompleted();
    }

    private Feature checkFeature(Point location) {
        for (Feature feature : features) {
            if (feature.getLocation().getLatitude() == location.getLatitude()
                    && feature.getLocation().getLongitude() == location.getLongitude()) {
                return feature;
            }
        }

        // No feature was found, return an unnamed feature.
        return Feature.newBuilder().setName("").setLocation(location).build();
    }

    @Override
    public void listFeatures(Rectangle request, StreamObserver<Feature> responseObserver) {
        int left = min(request.getLo().getLongitude(), request.getHi().getLongitude());
        int right = max(request.getLo().getLongitude(), request.getHi().getLongitude());
        int top = max(request.getLo().getLatitude(), request.getHi().getLatitude());
        int bottom = min(request.getLo().getLatitude(), request.getHi().getLatitude());

        for (Feature feature : features) {
            if (!RouteGuideUtil.exists(feature)) {
                continue;
            }

            int lat = feature.getLocation().getLatitude();
            int lon = feature.getLocation().getLongitude();
            if (lon >= left && lon <= right && lat >= bottom && lat <= top) {
                responseObserver.onNext(feature);
            }
        }

        responseObserver.onCompleted();
    }

    @Override
    public StreamObserver<Point> recordRoute(StreamObserver<RouteSummary> responseObserver) {
        return new StreamObserver<Point>() {
            int pointCount;
            int featureCount;
            int distance;
            Point previous;
            final long startTime = System.nanoTime();

            @Override
            public void onNext(Point value) {
                pointCount++;
                if (RouteGuideUtil.exists(checkFeature(value))) {
                    featureCount++;
                }
                // For each point after the first, add the incremental distance from the previous point to
                // the total distance value.
                if (previous != null) {
                    distance += calcDistance(previous, value);
                }
                previous = value;
            }

            @Override
            public void onError(Throwable t) {
                log.error("recordRoute cancelled", t);
            }

            @Override
            public void onCompleted() {
                long seconds = NANOSECONDS.toSeconds(System.nanoTime() - startTime);
                responseObserver.onNext(RouteSummary.newBuilder()
                        .setPointCount(pointCount)
                        .setFeatureCount(featureCount)
                        .setDistance(distance)
                        .setElapsedTime((int) seconds)
                        .build());
                responseObserver.onCompleted();
            }
        };
    }

    private static int calcDistance(Point start, Point end) {
        int r = 6371000; // earth radius in meters
        double lat1 = toRadians(RouteGuideUtil.getLatitude(start));
        double lat2 = toRadians(RouteGuideUtil.getLatitude(end));
        double lon1 = toRadians(RouteGuideUtil.getLongitude(start));
        double lon2 = toRadians(RouteGuideUtil.getLongitude(end));
        double deltaLat = lat2 - lat1;
        double deltaLon = lon2 - lon1;

        double a = sin(deltaLat / 2) * sin(deltaLat / 2)
                + cos(lat1) * cos(lat2) * sin(deltaLon / 2) * sin(deltaLon / 2);
        double c = 2 * atan2(sqrt(a), sqrt(1 - a));

        return (int) (r * c);
    }

    @Override
    public StreamObserver<RouteNote> routeChat(StreamObserver<RouteNote> responseObserver) {
        return new StreamObserver<RouteNote>() {
            @Override
            public void onNext(RouteNote value) {
                List<RouteNote> notes = getOrCreateNotes(value.getLocation());

                // Respond with all previous notes at this location.
                for (RouteNote prevNote : notes.toArray(new RouteNote[0])) {
                    responseObserver.onNext(prevNote);
                }

                // Now add the new note to the list
                notes.add(value);
            }

            @Override
            public void onError(Throwable t) {
                log.error("routeChat cancelled", t);
            }

            @Override
            public void onCompleted() {
                responseObserver.onCompleted();
            }
        };
    }

    private List<RouteNote> getOrCreateNotes(Point location) {
        List<RouteNote> notes = Collections.synchronizedList(new ArrayList<RouteNote>());
        List<RouteNote> prevNotes = routeNotes.putIfAbsent(location, notes);
        return prevNotes != null ? prevNotes : notes;
    }
}

RouteGuideServer.java

package com.wy.routeguide.server;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class RouteGuideServer {
    private final int port;
    private final Server server;

    {
        port = 8980;
        ServerBuilder<?> serverBuilder = ServerBuilder.forPort(port);
        server = serverBuilder.addService(new RouteGuideServiceImpl()).build();
    }

    public void start() throws IOException {
        server.start();
        log.info("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 {
                    RouteGuideServer.this.stop();
                } catch (InterruptedException e) {
                    e.printStackTrace(System.err);
                }
                System.err.println("*** server shut down");
            }
        });
    }

    /** Stop serving requests and shutdown resources. */
    public 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 Exception {
        RouteGuideServer server = new RouteGuideServer();
        server.start();
        server.blockUntilShutdown();
    }
}

运行

依次启动类RouteGuideServer.javaRouteGuideClient.java,即可看到对应的结果输出了。

其他相关的代码

RouteGuideUtil.java

/*
 * Copyright 2015 The gRPC Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wy.routeguide.server;

import com.google.protobuf.util.JsonFormat;
import com.wy.routeguide.Feature;
import com.wy.routeguide.FeatureDatabase;
import com.wy.routeguide.Point;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.List;

/**
 * Common utilities for the RouteGuide demo.
 */
public class RouteGuideUtil {
  private static final double COORD_FACTOR = 1e7;

  /**
   * Gets the latitude for the given point.
   */
  public static double getLatitude(Point location) {
    return location.getLatitude() / COORD_FACTOR;
  }

  /**
   * Gets the longitude for the given point.
   */
  public static double getLongitude(Point location) {
    return location.getLongitude() / COORD_FACTOR;
  }

  /**
   * Gets the default features file from classpath.
   */
  public static URL getDefaultFeaturesFile() {
    return RouteGuideServer.class.getResource("route_guide_db.json");
  }

  /**
   * Parses the JSON input file containing the list of features.
   */
  public static List<Feature> parseFeatures(URL file) throws IOException {
    InputStream input = file.openStream();
    try {
      Reader reader = new InputStreamReader(input, Charset.forName("UTF-8"));
      try {
        FeatureDatabase.Builder database = FeatureDatabase.newBuilder();
        JsonFormat.parser().merge(reader, database);
        return database.getFeatureList();
      } finally {
        reader.close();
      }
    } finally {
      input.close();
    }
  }

  /**
   * Indicates whether the given feature exists (i.e. has a valid name).
   */
  public static boolean exists(Feature feature) {
    return feature != null && !feature.getName().isEmpty();
  }
}

pom.xml

<?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>org.example</groupId>
    <artifactId>grpc-demo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>

        <grpc.version>1.45.1</grpc.version>
        <protoc.version>3.20.0</protoc.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.grpc</groupId>
                <artifactId>grpc-bom</artifactId>
                <version>${grpc.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
        </dependency>
        <!-- necessary for Java 9+ -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>annotations-api</artifactId>
            <version>6.0.53</version>
            <scope>provided</scope>
        </dependency>


        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
            <version>${protoc.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.9</version> <!-- prevent downgrade via protobuf-java-util -->
        </dependency>

        <!-- logger -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.36</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.11</version>
        </dependency>

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
    </dependencies>

    <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.6.2</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>${project.artifactId}</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

route_guide_db.json

{
  "feature": [{
      "location": {
          "latitude": 407838351,
          "longitude": -746143763
      },
      "name": "Patriots Path, Mendham, NJ 07945, USA"
  }, {
      "location": {
          "latitude": 408122808,
          "longitude": -743999179
      },
      "name": "101 New Jersey 10, Whippany, NJ 07981, USA"
  }, {
      "location": {
          "latitude": 413628156,
          "longitude": -749015468
      },
      "name": "U.S. 6, Shohola, PA 18458, USA"
  }, {
      "location": {
          "latitude": 419999544,
          "longitude": -740371136
      },
      "name": "5 Conners Road, Kingston, NY 12401, USA"
  }, {
      "location": {
          "latitude": 414008389,
          "longitude": -743951297
      },
      "name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA"
  }, {
      "location": {
          "latitude": 419611318,
          "longitude": -746524769
      },
      "name": "287 Flugertown Road, Livingston Manor, NY 12758, USA"
  }, {
      "location": {
          "latitude": 406109563,
          "longitude": -742186778
      },
      "name": "4001 Tremley Point Road, Linden, NJ 07036, USA"
  }, {
      "location": {
          "latitude": 416802456,
          "longitude": -742370183
      },
      "name": "352 South Mountain Road, Wallkill, NY 12589, USA"
  }, {
      "location": {
          "latitude": 412950425,
          "longitude": -741077389
      },
      "name": "Bailey Turn Road, Harriman, NY 10926, USA"
  }, {
      "location": {
          "latitude": 412144655,
          "longitude": -743949739
      },
      "name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA"
  }, {
      "location": {
          "latitude": 415736605,
          "longitude": -742847522
      },
      "name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA"
  }, {
      "location": {
          "latitude": 413843930,
          "longitude": -740501726
      },
      "name": "162 Merrill Road, Highland Mills, NY 10930, USA"
  }, {
      "location": {
          "latitude": 410873075,
          "longitude": -744459023
      },
      "name": "Clinton Road, West Milford, NJ 07480, USA"
  }, {
      "location": {
          "latitude": 412346009,
          "longitude": -744026814
      },
      "name": "16 Old Brook Lane, Warwick, NY 10990, USA"
  }, {
      "location": {
          "latitude": 402948455,
          "longitude": -747903913
      },
      "name": "3 Drake Lane, Pennington, NJ 08534, USA"
  }, {
      "location": {
          "latitude": 406337092,
          "longitude": -740122226
      },
      "name": "6324 8th Avenue, Brooklyn, NY 11220, USA"
  }, {
      "location": {
          "latitude": 406421967,
          "longitude": -747727624
      },
      "name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA"
  }, {
      "location": {
          "latitude": 416318082,
          "longitude": -749677716
      },
      "name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA"
  }, {
      "location": {
          "latitude": 415301720,
          "longitude": -748416257
      },
      "name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA"
  }, {
      "location": {
          "latitude": 402647019,
          "longitude": -747071791
      },
      "name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA"
  }, {
      "location": {
          "latitude": 412567807,
          "longitude": -741058078
      },
      "name": "New York State Reference Route 987E, Southfields, NY 10975, USA"
  }, {
      "location": {
          "latitude": 416855156,
          "longitude": -744420597
      },
      "name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA"
  }, {
      "location": {
          "latitude": 404663628,
          "longitude": -744820157
      },
      "name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA"
  }, {
      "location": {
          "latitude": 407113723,
          "longitude": -749746483
      },
      "name": ""
  }, {
      "location": {
          "latitude": 402133926,
          "longitude": -743613249
      },
      "name": ""
  }, {
      "location": {
          "latitude": 400273442,
          "longitude": -741220915
      },
      "name": ""
  }, {
      "location": {
          "latitude": 411236786,
          "longitude": -744070769
      },
      "name": ""
  }, {
      "location": {
          "latitude": 411633782,
          "longitude": -746784970
      },
      "name": "211-225 Plains Road, Augusta, NJ 07822, USA"
  }, {
      "location": {
          "latitude": 415830701,
          "longitude": -742952812
      },
      "name": ""
  }, {
      "location": {
          "latitude": 413447164,
          "longitude": -748712898
      },
      "name": "165 Pedersen Ridge Road, Milford, PA 18337, USA"
  }, {
      "location": {
          "latitude": 405047245,
          "longitude": -749800722
      },
      "name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA"
  }, {
      "location": {
          "latitude": 418858923,
          "longitude": -746156790
      },
      "name": ""
  }, {
      "location": {
          "latitude": 417951888,
          "longitude": -748484944
      },
      "name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA"
  }, {
      "location": {
          "latitude": 407033786,
          "longitude": -743977337
      },
      "name": "26 East 3rd Street, New Providence, NJ 07974, USA"
  }, {
      "location": {
          "latitude": 417548014,
          "longitude": -740075041
      },
      "name": ""
  }, {
      "location": {
          "latitude": 410395868,
          "longitude": -744972325
      },
      "name": ""
  }, {
      "location": {
          "latitude": 404615353,
          "longitude": -745129803
      },
      "name": ""
  }, {
      "location": {
          "latitude": 406589790,
          "longitude": -743560121
      },
      "name": "611 Lawrence Avenue, Westfield, NJ 07090, USA"
  }, {
      "location": {
          "latitude": 414653148,
          "longitude": -740477477
      },
      "name": "18 Lannis Avenue, New Windsor, NY 12553, USA"
  }, {
      "location": {
          "latitude": 405957808,
          "longitude": -743255336
      },
      "name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA"
  }, {
      "location": {
          "latitude": 411733589,
          "longitude": -741648093
      },
      "name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA"
  }, {
      "location": {
          "latitude": 412676291,
          "longitude": -742606606
      },
      "name": "1270 Lakes Road, Monroe, NY 10950, USA"
  }, {
      "location": {
          "latitude": 409224445,
          "longitude": -748286738
      },
      "name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA"
  }, {
      "location": {
          "latitude": 406523420,
          "longitude": -742135517
      },
      "name": "652 Garden Street, Elizabeth, NJ 07202, USA"
  }, {
      "location": {
          "latitude": 401827388,
          "longitude": -740294537
      },
      "name": "349 Sea Spray Court, Neptune City, NJ 07753, USA"
  }, {
      "location": {
          "latitude": 410564152,
          "longitude": -743685054
      },
      "name": "13-17 Stanley Street, West Milford, NJ 07480, USA"
  }, {
      "location": {
          "latitude": 408472324,
          "longitude": -740726046
      },
      "name": "47 Industrial Avenue, Teterboro, NJ 07608, USA"
  }, {
      "location": {
          "latitude": 412452168,
          "longitude": -740214052
      },
      "name": "5 White Oak Lane, Stony Point, NY 10980, USA"
  }, {
      "location": {
          "latitude": 409146138,
          "longitude": -746188906
      },
      "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA"
  }, {
      "location": {
          "latitude": 404701380,
          "longitude": -744781745
      },
      "name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA"
  }, {
      "location": {
          "latitude": 409642566,
          "longitude": -746017679
      },
      "name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA"
  }, {
      "location": {
          "latitude": 408031728,
          "longitude": -748645385
      },
      "name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA"
  }, {
      "location": {
          "latitude": 413700272,
          "longitude": -742135189
      },
      "name": "367 Prospect Road, Chester, NY 10918, USA"
  }, {
      "location": {
          "latitude": 404310607,
          "longitude": -740282632
      },
      "name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA"
  }, {
      "location": {
          "latitude": 409319800,
          "longitude": -746201391
      },
      "name": "11 Ward Street, Mount Arlington, NJ 07856, USA"
  }, {
      "location": {
          "latitude": 406685311,
          "longitude": -742108603
      },
      "name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA"
  }, {
      "location": {
          "latitude": 419018117,
          "longitude": -749142781
      },
      "name": "43 Dreher Road, Roscoe, NY 12776, USA"
  }, {
      "location": {
          "latitude": 412856162,
          "longitude": -745148837
      },
      "name": "Swan Street, Pine Island, NY 10969, USA"
  }, {
      "location": {
          "latitude": 416560744,
          "longitude": -746721964
      },
      "name": "66 Pleasantview Avenue, Monticello, NY 12701, USA"
  }, {
      "location": {
          "latitude": 405314270,
          "longitude": -749836354
      },
      "name": ""
  }, {
      "location": {
          "latitude": 414219548,
          "longitude": -743327440
      },
      "name": ""
  }, {
      "location": {
          "latitude": 415534177,
          "longitude": -742900616
      },
      "name": "565 Winding Hills Road, Montgomery, NY 12549, USA"
  }, {
      "location": {
          "latitude": 406898530,
          "longitude": -749127080
      },
      "name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA"
  }, {
      "location": {
          "latitude": 407586880,
          "longitude": -741670168
      },
      "name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA"
  }, {
      "location": {
          "latitude": 400106455,
          "longitude": -742870190
      },
      "name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA"
  }, {
      "location": {
          "latitude": 400066188,
          "longitude": -746793294
      },
      "name": ""
  }, {
      "location": {
          "latitude": 418803880,
          "longitude": -744102673
      },
      "name": "40 Mountain Road, Napanoch, NY 12458, USA"
  }, {
      "location": {
          "latitude": 414204288,
          "longitude": -747895140
      },
      "name": ""
  }, {
      "location": {
          "latitude": 414777405,
          "longitude": -740615601
      },
      "name": ""
  }, {
      "location": {
          "latitude": 415464475,
          "longitude": -747175374
      },
      "name": "48 North Road, Forestburgh, NY 12777, USA"
  }, {
      "location": {
          "latitude": 404062378,
          "longitude": -746376177
      },
      "name": ""
  }, {
      "location": {
          "latitude": 405688272,
          "longitude": -749285130
      },
      "name": ""
  }, {
      "location": {
          "latitude": 400342070,
          "longitude": -748788996
      },
      "name": ""
  }, {
      "location": {
          "latitude": 401809022,
          "longitude": -744157964
      },
      "name": ""
  }, {
      "location": {
          "latitude": 404226644,
          "longitude": -740517141
      },
      "name": "9 Thompson Avenue, Leonardo, NJ 07737, USA"
  }, {
      "location": {
          "latitude": 410322033,
          "longitude": -747871659
      },
      "name": ""
  }, {
      "location": {
          "latitude": 407100674,
          "longitude": -747742727
      },
      "name": ""
  }, {
      "location": {
          "latitude": 418811433,
          "longitude": -741718005
      },
      "name": "213 Bush Road, Stone Ridge, NY 12484, USA"
  }, {
      "location": {
          "latitude": 415034302,
          "longitude": -743850945
      },
      "name": ""
  }, {
      "location": {
          "latitude": 411349992,
          "longitude": -743694161
      },
      "name": ""
  }, {
      "location": {
          "latitude": 404839914,
          "longitude": -744759616
      },
      "name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA"
  }, {
      "location": {
          "latitude": 414638017,
          "longitude": -745957854
      },
      "name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA"
  }, {
      "location": {
          "latitude": 412127800,
          "longitude": -740173578
      },
      "name": ""
  }, {
      "location": {
          "latitude": 401263460,
          "longitude": -747964303
      },
      "name": ""
  }, {
      "location": {
          "latitude": 412843391,
          "longitude": -749086026
      },
      "name": ""
  }, {
      "location": {
          "latitude": 418512773,
          "longitude": -743067823
      },
      "name": ""
  }, {
      "location": {
          "latitude": 404318328,
          "longitude": -740835638
      },
      "name": "42-102 Main Street, Belford, NJ 07718, USA"
  }, {
      "location": {
          "latitude": 419020746,
          "longitude": -741172328
      },
      "name": ""
  }, {
      "location": {
          "latitude": 404080723,
          "longitude": -746119569
      },
      "name": ""
  }, {
      "location": {
          "latitude": 401012643,
          "longitude": -744035134
      },
      "name": ""
  }, {
      "location": {
          "latitude": 404306372,
          "longitude": -741079661
      },
      "name": ""
  }, {
      "location": {
          "latitude": 403966326,
          "longitude": -748519297
      },
      "name": ""
  }, {
      "location": {
          "latitude": 405002031,
          "longitude": -748407866
      },
      "name": ""
  }, {
      "location": {
          "latitude": 409532885,
          "longitude": -742200683
      },
      "name": ""
  }, {
      "location": {
          "latitude": 416851321,
          "longitude": -742674555
      },
      "name": ""
  }, {
      "location": {
          "latitude": 406411633,
          "longitude": -741722051
      },
      "name": "3387 Richmond Terrace, Staten Island, NY 10303, USA"
  }, {
      "location": {
          "latitude": 413069058,
          "longitude": -744597778
      },
      "name": "261 Van Sickle Road, Goshen, NY 10924, USA"
  }, {
      "location": {
          "latitude": 418465462,
          "longitude": -746859398
      },
      "name": ""
  }, {
      "location": {
          "latitude": 411733222,
          "longitude": -744228360
      },
      "name": ""
  }, {
      "location": {
          "latitude": 410248224,
          "longitude": -747127767
      },
      "name": "3 Hasta Way, Newton, NJ 07860, USA"
  }]
}
java
gRPC

关于作者

justin
123456
获得点赞
文章被阅读