返回介绍

Day 13: Dropwizard - 非常棒的 Java REST 服务器栈

发布于 2025-01-31 20:47:30 字数 17791 浏览 0 评论 0 收藏 0

-

我已经是一个使用了 8 年 Java 的软件开发人员了,我写过的大多数应用程序是用的 Spring 框架或 Java EE。最近,我花了一些时间学习用 Python 进行 web 开发,其中印象非常深刻的是 Flask 框架 - 一个微型架构,这使得它很容易写 REST 后端。所以今天我决定找一个 Java 的 Python Flask 框架替代品,做一些研究后,我发现 Dropwizard 框架可以帮助达到 Flask 框架同样的生产力。在这篇博客中,我们将学习如何使用 Dropwizard 构建一个基于 REST 的 Java MongoDB 应用程序。

什么是 Dropwizard?

Dropwizard 是一个开源的 Java 框架,用于开发 OPS 友好、高性能的基于 REST 的后端。它是由 Yammer 开发的,来驱动基于 JVM 的后端。

Dropwizard 提供同类最佳的 Java 库到一个嵌入式应用程序包。它由以下部分组成:

  1. 嵌入式 Jetty:每一个应用程序被打包成一个 jar(而不是 war)文件,并开始自己的嵌入式 Jetty 容器。没有任何 war 文件和外部 servlet 容器。
  2. JAX-RS:Jersey(JAX-RS 的参考实现)是用来写基于 REST 的 Web 服务的。
  3. JSON:REST 服务用的是 JSON,Jackson 库用来做所有的 JSON 处理。
  4. 日志:使用 Logback 和 SLF4J 完成。
  5. Hibernate 验证:Dropwizard 使用 Hibernate 验证 API 进行声明性验证。
  6. 指标:Dropwizard 支持监控使用标准库,它在监控代码方面有无与伦比的洞察力。

除了上面提到的这几个,Dropwizard 还使用了一些其他的库,你可以在 这里找到完整的列表

为什么是 Dropwizard?

我决定学 Dropwizard 的原因有以下几点:

  1. 快速的项目引导:如果你已经在使用 Spring 和 Java EE,你就会明白开发人员在引导项目时的痛苦。使用 Dropwizard,你只需要在你的 pom.xml 文件中添加一个依赖就完成了。
  2. 应用指标:Dropwizard 自带应用程序指标的支持。它提供了类似 请求/响应时间 这种非常有用的信息,只要把 @ 定时注解来获取方法的执行时间。
  3. 生产力:每个 Dropwizard 应用程序有一个启动 Jetty 容器的主程序。这意味着,完全可以把应用程序作为一个主程序在 IDE 中运行和调试。所以就没有重新编译或部署 war 文件。

Github 库

今天的演示应用程序的代码在 GitHub 上有: day13-dropwizard-mongodb-demo-app

必备条件

  1. 基础的 Java 知识是必须的;
  2. 下载并安装 MongoDB 数据库
  3. 安装最新版本的 Java Development Kit (JDK), OpenJDK 7 或是 Oracle JDK 7 都可以,这篇文章中使用 JDK 7;
  4. Eclipse 官网 下载最新版本的 Eclipse 包,就目前而言 eclipse 最新版的代号是 Kepler;

Eclipse 的安装很容易,只需要解压下载下来的包即可。如果是在 Linux 或者 Mac 机器上,开个命令行窗口,输入如下命令:

$ tar -xzvf eclipse-jee-kepler-R-*.tar.gz 

Windows 下,你解压到哪里,那里就会有一个 eclipse 文件夹,这样就可以直接操作了,当然你也可以创建执行文件的快捷方式到桌面。

第 1 步:创建一个新的 Maven 项目

打开 Eclipse IDE,然后到项目工作区(project workspace)。要创建一个新的项目,转到 文件>新建> Maven 项目 (File > New > Maven Project) ,然后选择 Maven 原型 - 快速启动 (maven-archetype-quickstart) ,然后进入 Ground IdArtifact Id ,最后点击“完成”。

第 2 步:更新 pom.xml

现在更新 pom.xml 文件以包括 dropwizard 核心 maven 依赖。同时也将更新 Maven 项目使用 Java 1.7 版本,更新 pom.xml 文件后,更新 Maven 项目 (右键单击>Maven>更新项目)

<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.shekhar</groupId>
<artifactId>blog</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>blog</name>
<url>http://maven.apache.org</url>

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
  <dependency>
    <groupId>com.yammer.dropwizard</groupId>
    <artifactId>dropwizard-core</artifactId>
    <version>0.6.2</version>
  </dependency>

</dependencies>

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.1</version>
      <configuration>
        <source>1.7</source>
        <target>1.7</target>
      </configuration>
    </plugin>


  </plugins>
</build>

第 3 步:创建配置类

每个 Dropwizard 应用程序都有一个配置类,它指定特定的环境参数。文章后面会将如主机、端口和数据库名之类的 MongoDB 的配置参数添加给它。这个类扩展了 com.yammer.dropwizard.config.Configuration 类。

import com.yammer.dropwizard.config.Configuration;

public class BlogConfiguration extends Configuration{

}

第 4 步:创建服务类

该 Dropwizard 项目由一个服务类自举。这个类将各种提供基本功能的捆绑和命令集合在一块,它还启动嵌入式 Jetty 服务器并延伸 com.yammer.dropwizard.Service

import com.yammer.dropwizard.Service;
import com.yammer.dropwizard.config.Bootstrap;
import com.yammer.dropwizard.config.Environment;

public class BlogService extends Service<BlogConfiguration> {

  public static void main(String[] args) throws Exception {
    new BlogService().run(new String[] { "server" });
  }

  @Override
  public void initialize(Bootstrap<BlogConfiguration> bootstrap) {
    bootstrap.setName("blog");
  }

  @Override
  public void run(BlogConfiguration configuration, Environment environment) throws Exception {

  }

}

上面的这些服务类可以:

  1. 有一个作为服务入口点的 main 方法。在 main 方法里面,创建 BlogService 的实例,并调用 run 方法。我们将服务器命令作为参数传递,服务器命令将启动嵌入式 Jetty 服务器。
  2. 初始化方法在服务运行方法之前被调用。
  3. 接下来,服务运行时将调用它的 run 方法,文章后面会将 JAX-RS 源加到这个方法里。

第 5 步:写 IndexResource

写一个当 GET 请求指向“/” URL 时会被调用的源,创建一个新的 JAX-RS 源(此资源将列出所有的博客),如下:

import java.util.Arrays;
import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.yammer.metrics.annotation.Timed;

@Path("/")
public class IndexResource {

  @GET
  @Produces(value = MediaType.APPLICATION_JSON)
  @Timed
  public List<Blog> index() {
    return Arrays.asList(new Blog("Day 12: OpenCV--Face Detection for Java Developers",
        "https://www.openshift.com/blogs/day-12-opencv-face-detection-for-java-developers"));
  }
}

上面这段代码是一个标准的 JAX-RS 资源类。它添加 @ Path 注释和定义 index() 方法,这个 index() 会返回一个博客集合,这些博客将被转换为 JSON 文档。

上面提到 IndexResource 是用博客表示的。下面这段则表明该博客使用 Hibernate 验证器注解,以确保内容是有效的。例如,使用 @URL 注释,以确保只有合法的 URL 存储在 MongoDB 数据库。

import java.util.Date;
import java.util.UUID;

import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.URL;

public class Blog {

  private String id = UUID.randomUUID().toString();

  @NotBlank
  private String title;

  @URL
  @NotBlank
  private String url;

  private final Date publishedOn = new Date();

  public Blog() {
  }

  public Blog(String title, String url) {
    super();
    this.title = title;
    this.url = url;
  }

  public String getId() {
    return id;
  }

  public String getTitle() {
    return title;
  }

  public String getUrl() {
    return url;
  }

  public Date getPublishedOn() {
    return publishedOn;
  }
}

接下来,在服务类的 run 方法注册 IndexResource。用下面的方式更新 BlogService run 方法。

@Override
public void run(BlogConfiguration configuration, Environment environment) throws Exception {
   environment.addResource(new IndexResource());
}

现在,可以将 BlogService 类作为一个主程序来运行 (右键点击>运行方式> Java 应用程序) ,这将启动嵌入式 Jetty 容器,我们可以看到程序在 http://localhost:8080/ 里运行。

$ curl http://localhost:8080

[{"id":"9bb43d53-5436-4dac-abaa-ac530c833df1","title":"Day 12: OpenCV--Face Detection for Java Developers","url":"https://www.openshift.com/blogs/day-12-opencv-face-detection-for-java-developers","publishedOn":1384090975372}]

现在可以通过点击 “指标(Metrics)” 检查 IndexResource 的指标,该数据是可用的 JSON 格式。

"com.shekhar.blog.IndexResource" : {
  "index" : {
    "type" : "timer",
    "duration" : {
    "unit" : "milliseconds",
    "min" : 17.764,
    "max" : 17.764,
    "mean" : 17.764,
    "std_dev" : 0.0,
    "median" : 17.764,
    "p75" : 17.764,
    "p95" : 17.764,
    "p98" : 17.764,
    "p99" : 17.764,
    "p999" : 17.764
    },
    "rate" : {
    "unit" : "seconds",
    "count" : 1,
    "mean" : 7.246537731991882E-4,
    "m1" : 2.290184897291144E-12,
    "m5" : 3.551918562683463E-5,
    "m15" : 2.445031498756583E-4
    }
  }
  },

第 6 步:配置 MongoDB

pom.xml 里加入 mongo-jackson-mapper 的依赖。

<dependency>
  <groupId>net.vz.mongodb.jackson</groupId>
  <artifactId>mongo-jackson-mapper</artifactId>
  <version>1.4.2</version>
</dependency>

用 MongoDB 数据库的详细信息(如主机、端口和数据库名等)更新 BlogConfiguration 类。

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;

import org.codehaus.jackson.annotate.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

import com.yammer.dropwizard.config.Configuration;

public class BlogConfiguration extends Configuration {

  @JsonProperty
  @NotEmpty
  public String mongohost = "localhost";

  @JsonProperty
  @Min(1)
  @Max(65535)
  public int mongoport = 27017;

  @JsonProperty
  @NotEmpty
  public String mongodb = "mydb";
}

接下来,创建一个名为 MongoManaged 的新类,它将允许你在应用程序启动和停止时管理程序资源。这样就实现了 com.yammer.dropwizard.lifecycle.Managed

import com.mongodb.Mongo;
import com.yammer.dropwizard.lifecycle.Managed;

public class MongoManaged implements Managed {

  private Mongo mongo;

  public MongoManaged(Mongo mongo) {
    this.mongo = mongo;
  }

  @Override
  public void start() throws Exception {
  }

  @Override
  public void stop() throws Exception {
    mongo.close();
  }

}

在上面的代码中,关闭了 stop 方法中的 MongoDB 连接。

下一步,写一个 MongoHealthCheck 来检查 MongoDB 的连接与否。

import com.mongodb.Mongo;
import com.yammer.metrics.core.HealthCheck;

public class MongoHealthCheck extends HealthCheck {

  private Mongo mongo;

  protected MongoHealthCheck(Mongo mongo) {
    super("MongoDBHealthCheck");
    this.mongo = mongo;
  }

  @Override
  protected Result check() throws Exception {
    mongo.getDatabaseNames();
    return Result.healthy();
  }
}

现在,更新 BlogService 类,将 MongoDB 的配置包含进来。

package com.shekhar.blog;

import com.mongodb.Mongo;
import com.yammer.dropwizard.Service;
import com.yammer.dropwizard.config.Bootstrap;
import com.yammer.dropwizard.config.Environment;

public class BlogService extends Service<BlogConfiguration> {

  public static void main(String[] args) throws Exception {
    new BlogService().run(new String[] { "server" });
  }

  @Override
  public void initialize(Bootstrap<BlogConfiguration> bootstrap) {
    bootstrap.setName("blog");
  }

  @Override
  public void run(BlogConfiguration configuration, Environment environment) throws Exception {
    Mongo mongo = new Mongo(configuration.mongohost, configuration.mongoport);
    MongoManaged mongoManaged = new MongoManaged(mongo);
    environment.manage(mongoManaged);

    environment.addHealthCheck(new MongoHealthCheck(mongo));

    environment.addResource(new IndexResource());
  }
}

上面这段代码:

  1. 使用 BlogConfiguration 对象创建了一个新的 Mongo 实例。
  2. 一个新的 MongoManaged 实例被创建并添加到环境中。
  3. 健康检查被添加。

运行该应用程序作为主程序。你可以到本地的 http://localhost:8081/healthcheck 健康检查页面去检验 MongoDB 是否在运行,如果 MongoDB 没有运行,会看到一个异常堆栈跟踪。

! MongoDBHealthCheck: ERROR
!  can't call something : Shekhars-MacBook-Pro.local/192.168.1.101:27017/admin

com.mongodb.MongoException$Network: can't call something : Shekhars-MacBook-Pro.local/192.168.1.101:27017/admin
  at com.mongodb.DBTCPConnector.call(DBTCPConnector.java:227)
  at com.mongodb.DBApiLayer$MyCollection.__find(DBApiLayer.java:305)
  at com.mongodb.DB.command(DB.java:160)
  at com.mongodb.DB.command(DB.java:183)
  at com.mongodb.Mongo.getDatabaseNames(Mongo.java:327)
  at com.shekhar.blog.MongoHealthCheck.check(MongoHealthCheck.java:17)
  at com.yammer.metrics.core.HealthCheck.execute(HealthCheck.java:195)
  at 
Caused by: java.io.IOException: couldn't connect to [Shekhars-MacBook-Pro.local/192.168.1.101:27017] bc:java.net.ConnectException: Connection refused
  at com.mongodb.DBPort._open(DBPort.java:228)
  at com.mongodb.DBPort.go(DBPort.java:112)
  at com.mongodb.DBPort.call(DBPort.java:79)
  at com.mongodb.DBTCPConnector.call(DBTCPConnector.java:218)
  ... 33 more

* deadlocks: OK

现在启动 MongoDB,可以看到:

* MongoDBHealthCheck: OK
* deadlocks: OK

第 7 步:创建 BlogResource

现在写 BlogResource 类,它负责创建博客条目。

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

import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import net.vz.mongodb.jackson.DBCursor;
import net.vz.mongodb.jackson.JacksonDBCollection;

import com.yammer.metrics.annotation.Timed;

@Path("/blogs")
@Produces(value = MediaType.APPLICATION_JSON)
@Consumes(value = MediaType.APPLICATION_JSON)
public class BlogResource {

  private JacksonDBCollection<Blog, String> collection;

  public BlogResource(JacksonDBCollection<Blog, String> blogs) {
    this.collection = blogs;
  }

  @POST
  @Timed
  public Response publishNewBlog(@Valid Blog blog) {
    collection.insert(blog);
    return Response.noContent().build();
  }
}

下一步,更新 BlogService run 方法,将 BlogResource 也加进来。

 @Override
  public void run(BlogConfiguration configuration, Environment environment) throws Exception {
    Mongo mongo = new Mongo(configuration.mongohost, configuration.mongoport);
    MongoManaged mongoManaged = new MongoManaged(mongo);
    environment.manage(mongoManaged);

    environment.addHealthCheck(new MongoHealthCheck(mongo));

    DB db = mongo.getDB(configuration.mongodb);
    JacksonDBCollection<Blog, String> blogs = JacksonDBCollection.wrap(db.getCollection("blogs"), Blog.class, String.class);

    environment.addResource(new IndexResource());

    environment.addResource(new BlogResource(blogs));
  }

将 BlogService 类作为一个 Java 应用程序运行。为了测试 BlogResource,做一个 curl 请求:

$ curl -i -X POST -H "Content-Type: application/json" -d '{"title":"Day 12: OpenCV--Face Detection for Java Developers","url":"https://www.openshift.com/blogs/day-12-opencv-face-detection-for-java-developers"}' http://localhost:8080/blogs


HTTP/1.1 204 No Content
Date: Sun, 10 Nov 2013 14:08:03 GMT
Content-Type: application/json

第 8 步:更新 IndexResource

现在,更新 IndexResource index() 方法来从 MongoDB 获取所有的博客文件。

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

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import net.vz.mongodb.jackson.DBCursor;
import net.vz.mongodb.jackson.JacksonDBCollection;

import com.yammer.metrics.annotation.Timed;

@Path("/")
public class IndexResource {

  private JacksonDBCollection<Blog, String> collection;

  public IndexResource(JacksonDBCollection<Blog, String> blogs) {
    this.collection = blogs;
  }

  @GET
  @Produces(value = MediaType.APPLICATION_JSON)
  @Timed
  public List<Blog> index() {
    DBCursor<Blog> dbCursor = collection.find();
    List<Blog> blogs = new ArrayList<>();
    while (dbCursor.hasNext()) {
      Blog blog = dbCursor.next();
      blogs.add(blog);
    }
    return blogs;
  }

}

更新 BlogService run 方法将博客集合传递给 IndexResource。

@Override
  public void run(BlogConfiguration configuration, Environment environment) throws Exception {
    Mongo mongo = new Mongo(configuration.mongohost, configuration.mongoport);
    MongoManaged mongoManaged = new MongoManaged(mongo);
    environment.manage(mongoManaged);

    environment.addHealthCheck(new MongoHealthCheck(mongo));

    DB db = mongo.getDB(configuration.mongodb);
    JacksonDBCollection<Blog, String> blogs = JacksonDBCollection.wrap(db.getCollection("blogs"), Blog.class, String.class);

    environment.addResource(new IndexResource(blogs));

    environment.addResource(new BlogResource(blogs));
  }

将 BlogService 类作为一个 Java 应用程序运行。为了测试 BlogResource,做一个 curl 请求:

$ curl http://localhost:8080

[{"id":"527f9806300462bbd300687e","title":"Day 12: OpenCV--Face Detection for Java Developers","url":"https://www.openshift.com/blogs/day-12-opencv-face-detection-for-java-developers","publishedOn":1384093702592}]

第 9 步:部署到云端

这里有一篇文章,教你如何在 OpenShift 部署 Dropwizard 应用,点击 这里



今天就这些,欢迎反馈。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文