入门
本文将指导您完成一个简单的Dropwizard的Hello World项目。在此过程中,我们将解释各种底层库及其作用,以及Dropwizard中的重要概念,并建议一些组织技术来帮助您的项目能持续健康地增长。
概览
Dropwizard跨越了作为lib库和框架之间的界限。其目标是为生产就绪的Web应用程序所需的所有内容提供高性能,高可靠的实现。并且这些功能被封装到可重用的库中,因此您的应用程序仍然很精简和专注,从而缩短了产品上线时间和维护负担。
用于HTTP的Jetty
因为您的Web应用程序不能没有HTTP,Dropwizard使用Jetty ,一个令人难以置信的调优HTTP服务器直接嵌入到您的项目中。Dropwizard项目没有将应用程序交给复杂的应用程序服务器,而是有一个main
方法运行HTTP服务器。将您的应用程序作为一个简单的过程运行,消除了生产中Java的许多令人讨厌的方面(没有PermGen问题,没有应用程序服务器配置和维护,没有神秘的部署工具,没有类加载器问题,没有隐藏的应用程序日志,没有尝试调整单个垃圾收集器与多个应用程序工作负载一起工作)并允许您使用所有现有的Unix进程管理工具。
用于REST的Jersey
为了构建RESTful Web应用程序,我们发现在功能或性能方面没有任何东西胜过Jersey(JAX-RS参考实现)。它允许您编写干净,可测试的类,这些类可以优雅地将HTTP请求映射到简单的Java对象。它支持流输出,矩阵URI参数,条件GET
请求等等。
用于JSON的Jackson
在数据格式方面,JSON已成为网络的通用语言,而Jackson则是JVM上的JSON之王。除了快速闪电之外,它还有一个复杂的对象映射器,允许您直接导出域模型。
用于监控的Metrics
指标有助于分析问题,为您提供无与伦比的洞悉您的生产环境中的代码的行为。
其他集成的工具
除了Jetty,Jersey和Jackson之外,Dropwizard还包括一些库,可以帮助您更快速高效地开发。
- Guava除了高度优化的不可变数据结构外,还提供了越来越多的类来加速Java的开发。
- Logback和slf4j用于高性能和灵活的日志记录。
- Hibernate Validator是JSR 349参考实现,它提供了一个简单的声明性框架,用于验证用户输入并生成有用且易于i18n的错误消息。
- 在Apache的HttpClient的和Jersey客户端库允许都与其他Web服务低收入和高层次的互动。
- JDBI是使用Java的关系数据库最直接的方法。
- Liquibase是在整个开发和发布周期中检查数据库模式的好方法,应用高级数据库重构而不是一次性DDL脚本。
- Freemarker和Mustache是简单的模板系统,适用于面向用户的更多应用程序。
- Joda Time是一个非常完整,理智的库,用于处理日期和时间。
既然你已经了解到这了,那就让我们深入挖掘吧!
使用Maven进行设置
我们建议您将Maven用于新的Dropwizard应用程序。如果你是一个大型的Ant / Ivy,Buildr, Gradle,SBT,Leiningen或Gant粉丝,这很酷,但我们使用Maven,我们将在使用Maven时通过这个示例应用程序。如果您对Maven如何运作有任何疑问, Maven:The Complete Reference应该有你想要的东西。
你有三种选择:
-
使用dropwizard-archetype创建项目:
mvn archetype:generate -DarchetypeGroupId=io.dropwizard.archetypes -DarchetypeArtifactId=java-simple -DarchetypeVersion=[REPLACE WITH A VALID DROPWIZARD VERSION]
请按照以下教程了解如何将其包含在现有项目中
教程
首先,dropwizard.version
使用当前版本的Dropwizard(1.3.5)向POM 添加属性:
<properties>
<dropwizard.version>INSERT VERSION HERE</dropwizard.version>
</properties>
将dropwizard-core
库添加为依赖项:
<dependencies>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
<version>${dropwizard.version}</version>
</dependency>
</dependencies>
很好,这些足够了。我们现在已经建立了一个Maven项目,现在是时候开始编写真正的代码了。
创建配置类
每个Dropwizard应用程序都有自己的Configuration
类的子类,它指定特定的环境参数。这些参数在YAML配置文件中指定,该文件被反序列化为应用程序配置类的实例。
我们将要构建的应用程序是一个高性能的Hello World服务。我们需要至少指定两件事:一个用于说出问候的模板和一个默认名称,以防用户未指定其名称。
package com.example.helloworld;
import io.dropwizard.Configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;
public class HelloWorldConfiguration extends Configuration {
@NotEmpty
private String template;
@NotEmpty
private String defaultName = "Stranger";
@JsonProperty
public String getTemplate() {
return template;
}
@JsonProperty
public void setTemplate(String template) {
this.template = template;
}
@JsonProperty
public String getDefaultName() {
return defaultName;
}
@JsonProperty
public void setDefaultName(String name) {
this.defaultName = name;
}
}
这里有很多知识点,所以让我们解释一下吧。
当从YAML文件反序列化该类时,它将从YAML对象中提取两个根级别字段:template
,我们的Hello World说明的模板,以及defaultName
要使用的默认名称。template
和defaultName
都标注了@NotEmpty
,所以如果YAML配置文件有任何空值或缺少template
完全的信息会抛出异常,并且应用程序将无法启动。
template
和 defaultName
的getter和setter都有注释 @JsonProperty
,这允许Jackson既可以从YAML文件反序列化属性,也可以序列化它。
注意
从YAML到您的应用程序
Configuration
实例的映射由Jackson完成。这意味着您的Configuration
类可以使用Jackson的所有对象映射注释。验证@NotEmpty
由Hibernate Validator处理,它具有 广泛的内置约束供您使用。
我们的YAML文件将如下所示,完整的示例yml在这里:
template: Hello, %s!
defaultName: Stranger
Dropwizard有许多比这更多的配置参数,但他们都有健全的默认值,这样可以保持你的配置文件小,重点突出。
因此,将YAML文件保存在您计划运行的jar的目录中(见下文)hello-world.yml
,因为我们很快就会启动并运行,我们将需要它。接下来,我们正在创建我们的应用程序类!
创建应用程序类
结合项目的Configuration
子类,Application
子类构成了Dropwizard应用程序的核心。所述Application
类启动各bundle和command命令,其提供基本功能在一起(稍后会详细介绍)。但是现在,我们 HelloWorldApplication
看起来像这样:
package com.example.helloworld;
import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import com.example.helloworld.resources.HelloWorldResource;
import com.example.helloworld.health.TemplateHealthCheck;
public class HelloWorldApplication extends Application<HelloWorldConfiguration> {
public static void main(String[] args) throws Exception {
new HelloWorldApplication().run(args);
}
@Override
public String getName() {
return "hello-world";
}
@Override
public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
// nothing to do yet
}
@Override
public void run(HelloWorldConfiguration configuration,
Environment environment) {
// nothing to do yet
}
}
如您所见,HelloWorldApplication
使用应用程序的配置类型HelloWorldConfiguration
进行参数化。initialize
方法用于在运行应用程序之前配置所需应用程序的各个方面,如bundle,配置源提供程序等。此外,我们还添加了一个static
main
方法,它将成为我们应用程序的入口点。现在,我们没有实现任何功能,所以我们的run
方法有点无聊。让我们解决这个问题!
创建表示类
在我们进入Hello World应用程序的细节之前,我们需要停下来思考一下我们的API。幸运的是,我们的应用程序需要符合行业标准RFC 1149,它规定了Hello World的以下JSON表示:
{
"id": 1,
"content": "Hi!"
}
该id
字段是该问候的唯一标识符,并且content
是该问候的文本表示。(值得庆幸的是,这是一个相当直接的行业标准。)
要对此表示进行建模,我们将创建一个表示类:
package com.example.helloworld.api;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.Length;
public class Saying {
private long id;
@Length(max = 3)
private String content;
public Saying() {
// Jackson deserialization
}
public Saying(long id, String content) {
this.id = id;
this.content = content;
}
@JsonProperty
public long getId() {
return id;
}
@JsonProperty
public String getContent() {
return content;
}
}
这是一个非常简单的POJO,但有一些值得注意的事情。
首先,它是不可改变的。这使得Saying
实例在多线程环境以及单线程环境中非常容易推理。其次,它使用JavaBeans标准id
和content
属性。这允许Jackson将其序列化为我们需要的JSON。Jackson对象映射代码将id
使用返回值填充JSON对象的字段#getId()
,同样使用content
和#getContent()
。最后,bean利用验证来确保内容大小不超过3。
注意
这里的JSON序列化由Jackson完成,它支持的不仅仅是像这样的简单JavaBean对象。除了复杂的注释集,您甚至可以编写自定义序列化程序和反序列化程序。
既然我们已经有了表示类,接下来学习下资源。
创建资源类
Jersey资源是Dropwizard应用程序的核心。每个资源类都与URI模板相关联。对于我们的应用程序,我们需要从URI /hello-world
返回一个Saying
实例,因此我们的资源类如下所示:
package com.example.helloworld.resources;
import com.example.helloworld.api.Saying;
import com.codahale.metrics.annotation.Timed;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.concurrent.atomic.AtomicLong;
import java.util.Optional;
@Path("/hello-world")
@Produces(MediaType.APPLICATION_JSON)
public class HelloWorldResource {
private final String template;
private final String defaultName;
private final AtomicLong counter;
public HelloWorldResource(String template, String defaultName) {
this.template = template;
this.defaultName = defaultName;
this.counter = new AtomicLong();
}
@GET
@Timed
public Saying sayHello(@QueryParam("name") Optional<String> name) {
final String value = String.format(template, name.orElse(defaultName));
return new Saying(counter.incrementAndGet(), value);
}
}
HelloWorldResource
有两个注释:@Path
和@Produces
。@Path("/hello-world")
告诉Jersey,这个资源可以在URI上访问/hello-world
,并且 @Produces(MediaType.APPLICATION_JSON)
让Jersey的内容协商代码知道这个资源产生的表示形式是application/json
。
HelloWorldResource
采用两个参数进行构造:template
它用于产生问候语,当用户拒绝告诉我们他们的名字时使用defaultName
。AtomicLong
为我们提供了一种廉价,线程安全的方法来生成唯一(ish)ID。
警告
资源类由多个线程同时使用。一般来说,我们建议资源是无状态/不可变的,但重要的是要记住上下文。
#sayHello(Optional<String>)
是这个类的核心,这是一个相当简单的方法。@QueryParam("name")
注解告诉Jersey从请求参数字符串中把name
参数赋值给方法中的name
参数。如果客户端发送请求 /hello-world?name=Dougie
,sayHello
将被调用Optional.of("Dougie")
; 如果name
查询字符串中没有参数,sayHello
则将调用Optional.absent()
。(支持Guava的Optional
是Dropwizard为Jersey的现有功能添加的一些额外的功能)
注意
如果客户端发送请求
/hello-world?name=
,sayHello
将被调用Optional.of("")
。这可能看起来很奇怪,但这遵循标准(应用程序可能具有不同的行为,具体取决于参数是否为空而不存在)。如果你希望/hello-world?name=
返回“Hello,Stranger!”,可以用NonEmptyStringParam
替换Optional<String>
参数。有关资源参数的更多信息,请参阅 文档
在sayHello
方法内部,我们递增计数器,使用格式化模板String.format(String,Object...)
,并返回一个Saying
新实例。
因为sayHello
带有注释@Timed
,Dropwizard会自动将其调用的持续时间和速率记录为Metrics Timer。
一旦sayHello
返回,Jersey就会获取Saying
实例并查找可以将Saying
实例写为application/json
的转换类。Dropwizard内置了一个这样的转换类,允许使用Java对象生成JSON对象,JSON对象生成Java对象。该转换类生成JSON,客户端会收到200 OK
的内容类型为application/json
的响应。
注册资源
不过,在这一切生效之前,我们需要回到HelloWorldApplication
并注册这个新的资源类。在run
方法中,我们可以从HelloWorldConfiguration
实例中读取模板和默认名称 ,创建一个新HelloWorldResource
实例,然后将其添加到应用程序的Jersey环境中:
@Override
public void run(HelloWorldConfiguration configuration,
Environment environment) {
final HelloWorldResource resource = new HelloWorldResource(
configuration.getTemplate(),
configuration.getDefaultName()
);
environment.jersey().register(resource);
}
当我们的应用程序启动时,我们使用配置文件中的参数创建一个新的资源类实例,并将其交给它Environment
,它就像应用程序可以执行的所有操作的注册表。
注意
Dropwizard应用程序可以包含许多资源类,每个资源类都对应于自己的URI。只需添加另一个
@Path
注释资源类,并使用新类的实例调用register
方法即可。
在我们走得太远之前,我们应该为我们的应用添加健康检查。
创建健康检查
运行状况检查为您提供了一种向应用程序添加小测试的方法,以便您验证应用程序在生产中是否正常运行。我们强烈建议您的所有应用程序至少具有一组最小的运行状况检查。
注意
事实上,我们强烈推荐这一点,如果你忽略了为你的项目添加健康检查,Dropwizard会唠叨你。
由于格式化字符串在应用程序运行时不太可能失败(与数据库连接池不同),因此我们必须在这里想一些创意。我们将添加一个运行状况检查,以确保我们可以格式化提供的模板:
package com.example.helloworld.health;
import com.codahale.metrics.health.HealthCheck;
public class TemplateHealthCheck extends HealthCheck {
private final String template;
public TemplateHealthCheck(String template) {
this.template = template;
}
@Override
protected Result check() throws Exception {
final String saying = String.format(template, "TEST");
if (!saying.contains("TEST")) {
return Result.unhealthy("template doesn't include a name");
}
return Result.healthy();
}
}
TemplateHealthCheck
检查两件事:提供的模板确实是格式良好的格式字符串,模板确实能生成具有给定名称的输出。
如果字符串不是格式良好的格式字符串(例如,有人意外地将Hello,%s%
放入 配置文件中),那么String.format(String, Object...)
将抛出一个IllegalFormatException
,并且运行状况检查将隐式失败。如果呈现的问候语不包括测试字符串,则运行状况检查将通过返回不健康的Result
而显式失败。
添加健康检查
与Dropwizard中的大多数内容一样,我们使用适当的参数创建一个新实例并将其添加到Environment
:
@Override
public void run(HelloWorldConfiguration configuration,
Environment environment) {
final HelloWorldResource resource = new HelloWorldResource(
configuration.getTemplate(),
configuration.getDefaultName()
);
final TemplateHealthCheck healthCheck =
new TemplateHealthCheck(configuration.getTemplate());
environment.healthChecks().register("template", healthCheck);
environment.jersey().register(resource);
}
现在我们快要准备好了!
建立工程的JAR包
我们建议把Dropwizard应用程序构建为“单” JAR文件-一个.jar
包含所有的运行应用程序所需的.class
文件。这允许您构建单个可部署工件,您可以将其从开发环境升级到测试环境再到生产环境,而无需担心已安装库中的差异。要开始构建我们的Hello World应用程序作为单JAR,我们需要配置一个名为maven-shade
的Maven插件。在文件的<build><plugins>
部分中pom.xml
,添加以下内容:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.helloworld.HelloWorldApplication</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
这将配置Maven在其package
阶段执行以下操作:
- 生成一个
pom.xml
文件,该文件不包含在单JAR中的库的依赖项的内容。 - 从已签名的JAR中排除所有数字签名。如果不这样做,则Java认为签名无效,并且不会加载或运行您的JAR文件。
- 整理JAR中
META-INF/services
的各种条目而不是覆盖它们。(如果没有那些,那么Dropwizard和Jersey都不会工作) - 把
com.example.helloworld.HelloWorldApplication
设为JARMainClass
。这将允许您使用java -jar
运行JAR 。
警告
如果您的应用程序具有必须签名的依赖项(例如,JCA / JCE提供程序或其他可信库),则必须在 该库的
maven-shade-plugin
配置中添加排除项,并将该JAR包含在类路径中。
警告
由于Dropwizard使用Java ServiceLoader功能来注册和加载扩展,因此maven-shade-plugin的minimizeJar选项将导致应用程序JAR不能正常工作。
版本化您的JAR
Dropwizard也可以使用项目版本,如果它嵌入在JAR的清单中作为 Implementation-Version
。要使用Maven嵌入此信息,请将以下内容添加到文件的 <build><plugins>
部分pom.xml
:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
在尝试确定您在计算机上部署的应用程序版本时,这非常方便。
完成配置后,进入项目目录并运行mvn package
(或从IDE 运行package
目标)。你应该看到这样的东西:
[INFO] Including org.eclipse.jetty:jetty-util:jar:7.6.0.RC0 in the shaded jar.
[INFO] Including com.google.guava:guava:jar:10.0.1 in the shaded jar.
[INFO] Including com.google.code.findbugs:jsr305:jar:1.3.9 in the shaded jar.
[INFO] Including org.hibernate:hibernate-validator:jar:4.2.0.Final in the shaded jar.
[INFO] Including javax.validation:validation-api:jar:1.0.0.GA in the shaded jar.
[INFO] Including org.yaml:snakeyaml:jar:1.9 in the shaded jar.
[INFO] Replacing original artifact with shaded artifact.
[INFO] Replacing /Users/yourname/Projects/hello-world/target/hello-world-0.0.1-SNAPSHOT.jar with /Users/yourname/Projects/hello-world/target/hello-world-0.0.1-SNAPSHOT-shaded.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 8.415s
[INFO] Finished at: Fri Dec 02 16:26:42 PST 2011
[INFO] Final Memory: 11M/81M
[INFO] ------------------------------------------------------------------------
恭喜!你已经建立了你的第一个Dropwizard项目!现在是时候运行了!
运行您的应用程序
现在您已经构建了一个JAR文件,现在是时候运行它了。
在项目目录中,运行以下命令:
java -jar target/hello-world-0.0.1-SNAPSHOT.jar
您应该看到如下内容:
usage: java -jar hello-world-0.0.1-SNAPSHOT.jar
[-h] [-v] {server} ...
positional arguments:
{server} available commands
optional arguments:
-h, --help show this help message and exit
-v, --version show the service version and exit
Dropwizard接受第一个命令行参数并将其分派给匹配的命令。在这种情况下,唯一可用的命令是server
,将您的应用程序作为HTTP服务器运行。该 server
命令需要一个配置文件,所以让我们继续为它提供 我们之前保存的YAML文件:
java -jar target/hello-world-0.0.1-SNAPSHOT.jar server hello-world.yml
您应该看到如下内容:
INFO [2011-12-03 00:38:32,927] io.dropwizard.cli.ServerCommand: Starting hello-world
INFO [2011-12-03 00:38:32,931] org.eclipse.jetty.server.Server: jetty-7.x.y-SNAPSHOT
INFO [2011-12-03 00:38:32,936] org.eclipse.jetty.server.handler.ContextHandler: started o.e.j.s.ServletContextHandler{/,null}
INFO [2011-12-03 00:38:32,999] com.sun.jersey.server.impl.application.WebApplicationImpl: Initiating Jersey application, version 'Jersey: 1.10 11/02/2011 03:53 PM'
INFO [2011-12-03 00:38:33,041] io.dropwizard.setup.Environment:
GET /hello-world (com.example.helloworld.resources.HelloWorldResource)
INFO [2011-12-03 00:38:33,215] org.eclipse.jetty.server.handler.ContextHandler: started o.e.j.s.ServletContextHandler{/,null}
INFO [2011-12-03 00:38:33,235] org.eclipse.jetty.server.AbstractConnector: Started BlockingChannelConnector@0.0.0.0:8080 STARTING
INFO [2011-12-03 00:38:33,238] org.eclipse.jetty.server.AbstractConnector: Started SocketConnector@0.0.0.0:8081 STARTING
您的Dropwizard应用程序现在正在侦听8080
应用程序请求和8081
管理请求的端口。如果按^C
,应用程序将正常关闭,首先关闭服务器套接字,然后等待处理正在进行的请求,然后关闭进程本身。
大功告成,试一下! http://localhost:8080/hello-world
我们正在产生问候语,真棒。当然你的应用程序能做到的远远不止这些。使用Dropwizard的主要原因之一是它提供的开箱即用的操作工具,所有这些工具都可以在管理端口上 http://localhost:8081/找到。
如果访问指标资源 http://localhost:8081/metrics,则可以看到所有应用程序的指标都表示为JSON对象。
访问线程的资源 http://localhost:8081/threads可以让您快速获得在这个过程中运行的所有线程的线程转储。
提示
当Jetty工作线程处理传入的HTTP请求时,线程名称将设置为请求的方法和URI。在调试性能不佳的请求时,这非常有用。
* deadlocks: OK
* template: OK
template
这是你的TemplateHealthCheck
的结果,毫不奇怪地通过了。 deadlocks
是一个内置的运行状况检查,它查找死锁的JVM线程并打印出列表(如果找到)。
下一步
嗯,恭喜。您已经准备好用于生产的Hello World应用程序(除了缺少测试),它能够每秒执行30,000-50,000个请求。希望您已经了解了Dropwizard如何将Jetty,Jersey,Jackson和其他稳定,成熟的库结合起来,为开发RESTful Web应用程序提供了一个非凡的平台。
Dropwizard还有很多内容(命令commands,绑定bundles,servlet,高级配置,验证validation,HTTP客户端,数据库客户端,视图等),所有这些都在用户手册中介绍。
官网 https://www.dropwizard.io/1.3.5/docs/getting-started.html