本文共 15075 字,大约阅读时间需要 50 分钟。
转载:
本文将指导您完成一个简单的Dropwizard的Hello World项目。在此过程中,我们将解释各种底层库及其作用,以及Dropwizard中的重要概念,并建议一些组织技术来帮助您的项目能持续健康地增长。
Dropwizard跨越了作为lib库和框架之间的界限。其目标是为生产就绪的Web应用程序所需的所有内容提供高性能,高可靠的实现。并且这些功能被封装到可重用的库中,因此您的应用程序仍然很精简和专注,从而缩短了产品上线时间和维护负担。
因为您的Web应用程序不能没有HTTP,Dropwizard使用 ,一个令人难以置信的调优HTTP服务器直接嵌入到您的项目中。Dropwizard项目没有将应用程序交给复杂的应用程序服务器,而是有一个main
方法运行HTTP服务器。将您的应用程序作为一个简单的过程运行,消除了生产中Java的许多令人讨厌的方面(没有PermGen问题,没有应用程序服务器配置和维护,没有神秘的部署工具,没有类加载器问题,没有隐藏的应用程序日志,没有尝试调整单个垃圾收集器与多个应用程序工作负载一起工作)并允许您使用所有现有的Unix进程管理工具。
为了构建RESTful Web应用程序,我们发现在功能或性能方面没有任何东西胜过(参考实现)。它允许您编写干净,可测试的类,这些类可以优雅地将HTTP请求映射到简单的Java对象。它支持流输出,矩阵URI参数,条件GET
请求等等。
在数据格式方面,JSON已成为网络的通用语言,而则是JVM上的JSON之王。除了快速闪电之外,它还有一个复杂的对象映射器,允许您直接导出域模型。
有助于分析问题,为您提供无与伦比的洞悉您的生产环境中的代码的行为。
除了,和之外,Dropwizard还包括一些库,可以帮助您更快速高效地开发。
既然你已经了解到这了,那就让我们深入挖掘吧!
我们建议您将用于新的Dropwizard应用程序。如果你是一个大型的 / ,, ,,或粉丝,这很酷,但我们使用Maven,我们将在使用Maven时通过这个示例应用程序。如果您对Maven如何运作有任何疑问, 应该有你想要的东西。
你有三种选择:
使用创建项目:
mvn archetype:generate -DarchetypeGroupId=io.dropwizard.archetypes -DarchetypeArtifactId=java-simple -DarchetypeVersion=[REPLACE WITH A VALID DROPWIZARD VERSION]
看一下
请按照以下教程了解如何将其包含在现有项目中
首先,dropwizard.version
使用当前版本的Dropwizard(1.3.5)向POM 添加属性:
INSERT VERSION HERE
将dropwizard-core
库添加为依赖项:
io.dropwizard dropwizard-core ${dropwizard.version}
很好,这些足够了。我们现在已经建立了一个Maven项目,现在是时候开始编写真正的代码了。
每个Dropwizard应用程序都有自己的Configuration
类的子类,它指定特定的环境参数。这些参数在配置文件中指定,该文件被反序列化为应用程序配置类的实例。
我们将要构建的应用程序是一个高性能的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
实例的映射由完成。这意味着您的Configuration
类可以使用Jackson的所有。验证@NotEmpty
由Hibernate Validator处理,它具有 供您使用。
我们的YAML文件将如下所示,完整的:
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{ public static void main(String[] args) throws Exception { new HelloWorldApplication().run(args); } @Override public String getName() { return "hello-world"; } @Override public void initialize(Bootstrap 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。幸运的是,我们的应用程序需要符合行业标准,它规定了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
属性。这允许将其序列化为我们需要的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") Optionalname) { 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环境中:
@Overridepublic 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
:
@Overridepublic 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);}
现在我们快要准备好了!
我们建议把Dropwizard应用程序构建为“单” JAR文件-一个.jar
包含所有的运行应用程序所需的.class
文件。这允许您构建单个可部署工件,您可以将其从开发环境升级到测试环境再到生产环境,而无需担心已安装库中的差异。要开始构建我们的Hello World应用程序作为单JAR,我们需要配置一个名为maven-shade
的Maven插件。在文件的<build><plugins>
部分中pom.xml
,添加以下内容:
org.apache.maven.plugins maven-shade-plugin 2.3 true *:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA package shade com.example.helloworld.HelloWorldApplication
这将配置Maven在其package
阶段执行以下操作:
pom.xml
文件,该文件不包含在单JAR中的库的依赖项的内容。META-INF/services
的各种条目而不是覆盖它们。(如果没有那些,那么Dropwizard和Jersey都不会工作)com.example.helloworld.HelloWorldApplication
设为JAR MainClass
。这将允许您使用java -jar
运行JAR 。警告
如果您的应用程序具有必须签名的依赖项(例如,提供程序或其他可信库),则必须在 该库的
maven-shade-plugin
配置中添加,并将该JAR包含在类路径中。
警告
由于Dropwizard使用Java 功能来注册和加载扩展,因此maven-shade-plugin的选项将导致应用程序JAR不能正常工作。
Dropwizard也可以使用项目版本,如果它嵌入在JAR的清单中作为 Implementation-Version
。要使用Maven嵌入此信息,请将以下内容添加到文件的 <build><plugins>
部分pom.xml
:
org.apache.maven.plugins maven-jar-plugin 2.4 true
在尝试确定您在计算机上部署的应用程序版本时,这非常方便。
完成配置后,进入项目目录并运行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 commandsoptional arguments: -h, --help show this help message and exit -v, --version show the service version and exit
Dropwizard接受第一个命令行参数并将其分派给匹配的命令。在这种情况下,唯一可用的命令是server
,将您的应用程序作为HTTP服务器运行。该 server
命令需要一个配置文件,所以让我们继续为它提供 :
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-worldINFO [2011-12-03 00:38:32,931] org.eclipse.jetty.server.Server: jetty-7.x.y-SNAPSHOTINFO [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 STARTINGINFO [2011-12-03 00:38:33,238] org.eclipse.jetty.server.AbstractConnector: Started SocketConnector@0.0.0.0:8081 STARTING
您的Dropwizard应用程序现在正在侦听8080
应用程序请求和8081
管理请求的端口。如果按^C
,应用程序将正常关闭,首先关闭服务器套接字,然后等待处理正在进行的请求,然后关闭进程本身。
大功告成,试一下!
我们正在产生问候语,真棒。当然你的应用程序能做到的远远不止这些。使用Dropwizard的主要原因之一是它提供的开箱即用的操作工具,所有这些工具都可以 找到。
如果访问 ,则可以看到所有应用程序的指标都表示为JSON对象。
访问 可以让您快速获得在这个过程中运行的所有线程的线程转储。
提示
当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.jianshu.com/p/3bb308c9bbcb 来源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。