Java 使用入门

本教程适合初步接触云端应用构建的新手,例如工程师和 Web 开发者,他们希望在应用于 Google Cloud 的过程中了解重要的应用开发概念。

目标

如需其他关于如何使用特定语言来构建应用的教程,请参阅以下指南:

费用

本教程使用 Google Cloud 的以下收费组件:

本教程介绍如何将资源使用量保持在 Google Cloud 的始终免费层级的限额范围内。您可使用价格计算器根据您的预计使用情况来估算费用。Google Cloud 新用户可能有资格申请免费试用

完成本教程后,您可以删除所创建的资源以避免继续计费。如需了解详情,请参阅清理

准备工作

  1. 登录您的 Google 帐号。

    如果您还没有 Google 帐号,请注册新帐号

  2. 在 Cloud Console 的项目选择器页面上,选择或创建 Cloud 项目。

    转到项目选择器页面

  3. 确保您的 Google Cloud 项目已启用结算功能。 了解如何确认您的项目已启用结算功能

  4. 如需在原生模式下创建 Firestore 数据库,请完成以下步骤:
    1. 在 Cloud Console 中,转到 Firestore 查看器页面。
      转到 Firestore 查看器
    2. 选择 Firestore 模式屏幕中,点击选择原生模式
    3. 选择 Firestore 数据库的位置。 此位置设置是您的 Cloud 项目的默认 Google Cloud 资源位置。此位置将用于 Cloud 项目中需要位置设置的 Google Cloud 服务,具体地说,包括您的默认 Cloud Storage 存储分区和 App Engine 应用。
    4. 点击创建数据库
  5. 启用 App Engine Admin, Cloud Storage, Cloud Logging, and Error Reporting API。

    启用 API

  6. 在 Cloud Shell 中,打开应用的源代码。
    转到 Cloud Shell

    利用 Cloud Shell,您可以直接在浏览器中通过命令行访问 Google Cloud 资源。

  7. 如需将示例代码和更改下载到应用目录,请点击继续
  8. 在 Cloud Shell 中,配置 gcloud 工具以使用新的 Google Cloud 项目:

    # Configure gcloud for your project
    gcloud config set project PROJECT_ID
    

    PROJECT_ID 替换为您使用 Cloud Console 创建的 Google Cloud 项目的 ID。

    gcloud 命令行工具是您从命令行与 Google Cloud 资源进行交互的主要方式。在本教程中,您将使用 gcloud 工具来部署和监控应用。

运行应用

  1. 如果您已使用 Cloud Shell 且将其配置为使用 Java 11,请更新 shell 的 Java 替代组件 JAVA_HOMEPATH 环境变量,以指定 Java 8。
  2. 转到 1-cloud-run 目录并运行应用:
    GOOGLE_CLOUD_PROJECT=PROJECT_ID mvn -Plocal clean jetty:run-exploded
    
    PROJECT_ID 替换为您创建的 Google Cloud 项目的 ID。
  3. 在 Cloud Shell 中,点击网页预览 ,然后选择在端口 8080 上预览。此时系统会打开一个新窗口,您的应用正在其中运行。

将应用部署到 Cloud Run

Google Cloud 提供了多种运行代码的方案。在此示例中,您要使用 Cloud Run 将可扩缩应用部署到 Google Cloud。通过零服务器管理,Cloud Run 让您专注于编写代码。此外,Cloud Run 会自动扩缩以应对突发的流量高峰。

  1. 使用 Jib 构建映像:
    mvn package jib:build -Dimage gcr.io/PROJECT_ID/bookshelf

    PROJECT_ID 替换为您创建的 Google Cloud 项目的 ID。

  2. 然后部署映像:
    gcloud run deploy bookshelf --image gcr.io/PROJECT_ID/bookshelf \
    --platform managed --region us-central1 --allow-unauthenticated
    PROJECT_ID 替换为您创建的 Google Cloud 项目的 ID。

部署成功后会按如下格式向在 Cloud Run 中运行的应用输出一个端点:

https://bookshelf-abcdefghij-uc.a.run.app

现在可通过此网址查看您的应用,此网址在下文称为 YOUR_CODE_RUN_URL。只要在网络浏览器中输入此网址,即可查看应用。

Bookshelf 应用首页

使用 Firestore 保留数据

您不能在 App Engine 实例中存储信息,因为实例重启时信息会丢失,当新实例创建时该信息已不再存在。所以,您需要使用一个您的所有实例都在其中读写的数据库。

Google Cloud 提供了多种存储数据的方案。在此示例中,您将使用 Firestore 存储每本图书的数据。Firestore 是一种完全托管式的无服务器 NoSQL 文档数据库,可用于存储和查询数据。Firestore 能够根据您的应用需求自动扩缩,并在不使用时缩减到零个实例。现在添加第一本图书。

  1. 在网络浏览器中,输入以下网址:

    https://PROJECT_ID.REGION_ID.r.appspot.com

    替换以下内容:

  2. 如需为部署的应用创建图书,请点击添加图书

    向 Bookshelf 应用添加一本图书
  3. 书名字段中,输入 Moby Dick
  4. 作者字段中,输入 Herman Melville
  5. 点击保存。现在您的 Bookshelf 应用有了一个条目。

    Moby Dick Bookshelf 应用条目
  6. 在 Cloud Console 中,点击刷新 来刷新 Firestore 页面。现在数据显示在 Firestore 中。Bookshelf 应用将每本图书存储为具有唯一 ID 的 Firestore 文档,这些文档都存储在一个 Firestore 集合中。在本教程中,该集合称为 books。Firestore 文档示例。

Firestore 使用 Firestore 客户端库存储 books。下面是一个提取 Firestore 文档的示例:

public class FirestoreDao implements BookDao {
  private CollectionReference booksCollection;

  public FirestoreDao() {
    Firestore firestore = FirestoreOptions.getDefaultInstance().getService();
    booksCollection = firestore.collection("books");
  }

  private Book documentToBook(DocumentSnapshot document) {
    Map<String, Object> data = document.getData();
    if (data == null) {
      System.out.println("No data in document " + document.getId());
      return null;
    }

    return new Book.Builder()
        .author((String) data.get(Book.AUTHOR))
        .description((String) data.get(Book.DESCRIPTION))
        .publishedDate((String) data.get(Book.PUBLISHED_DATE))
        .imageUrl((String) data.get(Book.IMAGE_URL))
        .createdBy((String) data.get(Book.CREATED_BY))
        .createdById((String) data.get(Book.CREATED_BY_ID))
        .title((String) data.get(Book.TITLE))
        .id(document.getId())
        .build();
  }

  @Override
  public String createBook(Book book) {
    String id = UUID.randomUUID().toString();
    DocumentReference document = booksCollection.document(id);
    Map<String, Object> data = Maps.newHashMap();

    data.put(Book.AUTHOR, book.getAuthor());
    data.put(Book.DESCRIPTION, book.getDescription());
    data.put(Book.PUBLISHED_DATE, book.getPublishedDate());
    data.put(Book.TITLE, book.getTitle());
    data.put(Book.IMAGE_URL, book.getImageUrl());
    data.put(Book.CREATED_BY, book.getCreatedBy());
    data.put(Book.CREATED_BY_ID, book.getCreatedById());
    try {
      document.set(data).get();
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }

    return id;
  }

  @Override
  public Book readBook(String bookId) {
    try {
      DocumentSnapshot document = booksCollection.document(bookId).get().get();

      return documentToBook(document);
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public void updateBook(Book book) {
    DocumentReference document = booksCollection.document(book.getId());
    Map<String, Object> data = Maps.newHashMap();

    data.put(Book.AUTHOR, book.getAuthor());
    data.put(Book.DESCRIPTION, book.getDescription());
    data.put(Book.PUBLISHED_DATE, book.getPublishedDate());
    data.put(Book.TITLE, book.getTitle());
    data.put(Book.IMAGE_URL, book.getImageUrl());
    data.put(Book.CREATED_BY, book.getCreatedBy());
    data.put(Book.CREATED_BY_ID, book.getCreatedById());
    try {
      document.set(data).get();
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
  }

  @Override
  public void deleteBook(String bookId) {
    try {
      booksCollection.document(bookId).delete().get();
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
  }

  private List<Book> documentsToBooks(List<QueryDocumentSnapshot> documents) {
    List<Book> resultBooks = new ArrayList<>();
    for (QueryDocumentSnapshot snapshot : documents) {
      resultBooks.add(documentToBook(snapshot));
    }
    return resultBooks;
  }

  @Override
  public Result<Book> listBooks(String startTitle) {
    Query booksQuery = booksCollection.orderBy("title").limit(10);
    if (startTitle != null) {
      booksQuery = booksQuery.startAfter(startTitle);
    }
    try {
      QuerySnapshot snapshot = booksQuery.get().get();
      List<Book> results = documentsToBooks(snapshot.getDocuments());
      String newCursor = null;
      if (results.size() > 0) {
        newCursor = results.get(results.size() - 1).getTitle();
      }
      return new Result<>(results, newCursor);
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
    return new Result<>(Lists.newArrayList(), null);
  }

  @Override
  public Result<Book> listBooksByUser(String userId, String startTitle) {
    Query booksQuery =
        booksCollection.orderBy("title").whereEqualTo(Book.CREATED_BY_ID, userId).limit(10);
    if (startTitle != null) {
      booksQuery = booksQuery.startAfter(startTitle);
    }
    try {
      QuerySnapshot snapshot = booksQuery.get().get();
      List<Book> results = documentsToBooks(snapshot.getDocuments());
      String newCursor = null;
      if (results.size() > 0) {
        newCursor = results.get(results.size() - 1).getTitle();
      }
      return new Result<>(results, newCursor);
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
    return new Result<>(Lists.newArrayList(), null);
  }
}

如需详细了解如何使用 Firestore,请参阅向 Firestore 添加数据

在 Cloud Storage 中存储上传的文件

现在您已经添加了一本图书,接下来可添加图书封面图片。您不能在实例中存储文件,数据库也不适合图片文件。您可以使用 Cloud Storage。

Cloud Storage 是 Google Cloud 的主要 Blob 存储库。您可以使用 Cloud Storage 来托管要在 Google Cloud 中共享的应用资源。如需使用 Cloud Storage,您需要创建一个 Cloud Storage 存储分区,这是一个保存数据的基本容器。

  1. 在 Cloud Console 中,转到 Cloud Storage 浏览器页面。

    转到“Cloud Storage 浏览器”页面

  2. 点击创建存储分区
  3. 创建存储分区对话框中输入存储分区的名称:将 Google Cloud 项目 ID 附加到字符串 _bucket 前面,这样名称就显示为 YOUR_PROJECT_ID_bucket。此名称须遵守存储分区名称要求。其他所有字段可以保留默认值。
  4. 点击创建
  5. 创建存储分区后,点击修改图书,然后选择一张图片上传为图书的封面。例如,可以使用图中所示的这张属于公共领域的图片:
    Moby Dick 图书封面
  6. 点击保存。您将被重定向至首页,其中显示您的一个 Bookshelf 应用条目。
    Moby Dick Bookshelf 应用条目

Bookshelf 应用会使用 Cloud Storage 客户端库将上传的文件发送到 Cloud Storage。

public class CloudStorageHelper {

  private final Logger logger = Logger.getLogger(CloudStorageHelper.class.getName());
  private static Storage storage = null;

  static {
    storage = StorageOptions.getDefaultInstance().getService();
  }

  /**
   * Uploads a file to Google Cloud Storage to the bucket specified in the BUCKET_NAME environment
   * variable, appending a timestamp to end of the uploaded filename.
   */
  public String uploadFile(FileItemStream fileStream, final String bucketName)
      throws IOException, ServletException {
    checkFileExtension(fileStream.getName());

    System.out.println("FileStream name: " + fileStream.getName() + "\nBucket name: " + bucketName);

    DateTimeFormatter dtf = DateTimeFormat.forPattern("-YYYY-MM-dd-HHmmssSSS");
    DateTime dt = DateTime.now(DateTimeZone.UTC);
    String dtString = dt.toString(dtf);
    final String fileName = fileStream.getName() + dtString;

    // the inputstream is closed by default, so we don't need to close it here
    @SuppressWarnings("deprecation")
    BlobInfo blobInfo =
        storage.create(
            BlobInfo.newBuilder(bucketName, fileName)
                // Modify access list to allow all users with link to read file
                .setAcl(new ArrayList<>(Arrays.asList(Acl.of(User.ofAllUsers(), Role.READER))))
                .build(),
            fileStream.openStream());
    logger.log(
        Level.INFO, "Uploaded file {0} as {1}", new Object[] {fileStream.getName(), fileName});
    // return the public download link
    return blobInfo.getMediaLink();
  }

  /** Checks that the file extension is supported. */
  private void checkFileExtension(String fileName) throws ServletException {
    if (fileName != null && !fileName.isEmpty() && fileName.contains(".")) {
      String[] allowedExt = {".jpg", ".jpeg", ".png", ".gif"};
      for (String ext : allowedExt) {
        if (fileName.endsWith(ext)) {
          return;
        }
      }
      throw new ServletException("file must be an image");
    }
  }
}

如需详细了解如何使用 Cloud Storage,请参阅方法指南列表

使用 Google Cloud 的运维套件监控您的应用

您已经部署了应用,创建并修改了 books 集合。如需为用户监控这些事件,可以使用 Application Performance Management。

使用 Cloud Logging 监控日志

  1. 在 Google Cloud 中,转到日志查看器

    转到日志查看器

    您可以在其中实时监控应用。如果您的应用出现问题,这是应该首先查看的位置之一。

    Stackdriver 日志查看器
  2. 资源下拉列表中,选择 Cloud Run Revision, bookshelf

使用 Error Reporting 监测错误

  1. 在 Cloud Console 中,转到 Error Reporting 页面。
    转到“Error Reporting”页面
    Error Reporting 可突出显示应用中的错误和异常,您还可以设置相关的提醒。
  2. 在浏览器中,转到应用中的 /errors 网址。
    YOUR_CODE_RUN_URL/errors

    这会生成一个新的测试异常并发送至 Google Cloud 的运维套件。

  3. 在 Cloud Console 中,返回到 Error Reporting 页面,很快就可以看到这个新错误。点击自动重新加载,这样就无需手动刷新页面。

    Error Reporting 中的错误消息。

清理

为避免因本教程中使用的资源而导致您的 Google Cloud Platform 帐号产生费用,请执行以下操作:

删除项目

  1. 在 Cloud Console 中,转到管理资源页面。

    转到“管理资源”页面

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

更进一步