目录
生产就绪,开箱即用(Production-ready, out of the box.)
准备开始
入门将指导完成创建一个简单的Dropwizard项目:Hello World,在这过程中,我们将解释各种各样的底层库和作用以及Dropwizard的重要概念,并且建议一些组织技巧来帮助你的项目成长(或者你也可以直接跳到有趣的部分。)
概述
Dropwizard介于(straddles)库和框架之间,它的目标是为准备生产的web应用程序提供高性能、可靠的实现。因为这个功能被提取到一个可重用的库中,所以您的应用程序仍然保持精简和集中,从而减少了投放市场的时间和维护负担。
Jetty HTTP
没有HTTP就不能成为一个web应用程序,Dropwizard提供一个Jetty HTTP库,将一个经过调优的HTTP服务直接嵌入到你的项目中,Dropwizard项目有一个main方法来启动HTTP服务器,而不是将应用程序交给一个复杂的服务器。运行你的应用程序将会是一个简单的进程,它消除了很多在构建java的规程中令人讨厌的地方(没有永久代的问题,没有应用服务器配置和维护的问题,没有难懂的部署问题,没有class加载问题,没有隐藏的应用日志的问题,没有试图优化一个垃圾收集器与多个应用程序工作负载的问题),允许你使用现有的unix进程管理工具。
Jersey REST
对于构建RESTful风格的web应用程序,我们发现在特性或性能方面没有什么能比得上Jersey (JAX-RS参考实现),它允许您编写干净、可测试的类,这些类优雅地将HTTP请求映射到简单的Java对象。它支持流输出、矩阵URI参数、条件GET请求等等。
Jackson JSON
在数据格式方面,JSON已经成为web的通用语言,Jackson是JVM上JSON的王者。除了非常快,它还有一个复杂的对象映射器,允许您直接导出实体模型。
Metrics metrics
Metrics库完成了所有工作,为您的代码在生产环境中提供了无与伦比的检测能力。
And Friends
除了Jetty、Jersey和Jackson之外,Dropwizard还提供了一些库,可以帮助您更快地发布产品,减少遗憾。
Guava:除了高度优化的不可变数据结构之外,还提供了越来越多的类来加速Java开发。
Logback和slf4j:高性能和灵活的日志记录
Hibernate Validator:JSR349参考实现,提供一个简单的声明的框架,为了校验用户输入信息和生成有用的和友好的错误信息
Apache HttpClient和Jersey Client库允许与其他web服务进行低级和高级交互。
JDBI:一个最简单的方式用java去使用关系数据库
Liquibase:在整个开发和发布周期中,Liquibase是检查数据库模式的好方法,它应用高级数据库重构,而不是一次性的DDL脚本。
Freemarker和Mustache是面向更多用户应用程序的简单模板系统。
Joda Time是一个非常完整、合理的库,用于处理日期和时间。
既然你已经了解了情况,我们就开始挖吧!
Maven构建
我们建议你使用maven来构建Dropwizard项目
首先在你的pom.xml中添加一个dropwizard.version,代表Dropwizard的当前版本是1.3.12
<properties> <dropwizard.version>1.3.12</dropwizard.version> </properties>
添加一个依赖库dropwizard-core
<dependencies> <dependency> <groupId>io.dropwizard</groupId> <artifactId>dropwizard-core</artifactId> <version>${dropwizard.version}</version> </dependency> </dependencies>
好了,xml的配置就讲到这里了,现在我们可以构建一个Maven项目,现在是时候开始编写真正的代码了。
创建一个Configuration类
每个Dropwizard应用程序都有自己的Configuration类子类,该类指定特定于环境的参数。这些参数在YAML配置文件中指定,YAML配置文件将被反序列化成应用程序的配置类并进行验证。
我们将要构建的应用程序是一个高性能的Hello World服务,我们的一个需求是,我们需要能够根据不同的环境改变Hello的方式,我们首先需要指定至少两件事:一个用于saying hello的模板和一个默认名称,以防用户没有指定他们的名称。
import com.example.helloworld.core.Template;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;
import io.dropwizard.db.DataSourceFactory;
import javax.validation.constraints.NotEmpty;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.Collections;
import java.util.Map;public class HelloWorldConfiguration extends Configuration {
@NotEmpty
private String template;@NotEmpty
private String defaultName = "Stranger";@Valid
@NotNull
private DataSourceFactory database = new DataSourceFactory();@NotNull
private Map<String, Map<String, String>> viewRendererConfiguration = Collections.emptyMap();@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 defaultName) {
this.defaultName = defaultName;
}public Template buildTemplate() {
return new Template(template, defaultName);
}@JsonProperty("database")
public DataSourceFactory getDataSourceFactory() {
return database;
}@JsonProperty("database")
public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
this.database = dataSourceFactory;
}@JsonProperty("viewRendererConfiguration")
public Map<String, Map<String, String>> getViewRendererConfiguration() {
return viewRendererConfiguration;
}@JsonProperty("viewRendererConfiguration")
public void setViewRendererConfiguration(Map<String, Map<String, String>> viewRendererConfiguration) {
this.viewRendererConfiguration = viewRendererConfiguration;
}
}
这里面有很多东西,让我们先打开一点
当这个类从YAML文件反序列化时,它将从YAML对象中拉出两个根级字段:template(用于Hello World的模板)和defaultName(要使用的默认名称)。模板和defaultName都用@NotEmpty注释,因此,如果YAML配置文件中有空白值,或者完全没有模板,那么将抛出一个信息异常,您的应用程序将无法启动。
template和defaultName的getter和setter方法都使用@JsonProperty进行注释,这使得Jackson既可以反序列化YAML文件中的属性,也可以序列化它。
从YAML到应用程序配置实例的映射由Jackson完成。这意味着您的配置类可以使用Jackson的所有对象映射注释。@NotEmpty的验证由Hibernate验证器处理,Hibernate验证器有许多内置的约束供您使用。
yaml文件
template: Hello, %s! defaultName: Stranger
Dropwizard有更多的配置参数,但它们都有正常的默认值,所以您可以保持您的配置文件小而集中。
创建一个Application类
结合项目的Configuration子类,它的应用程序子类构成Dropwizard应用程序的核心。应用程序类将提供基本功能的各种bundles和commands组合在一起。
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参数化的。初始化方法用于在运行应用程序之前配置应用程序所需的各个方面,如包、配置源提供者等。此外,我们还添加了一个静态main方法,它将成为应用程序的入口点。现在,我们还没有实现任何功能,所以我们的run方法有点无聊。让我们解决这个问题!
创建一个代表性的类
为了对这个表示进行建模,我们将创建这个类
public class Saying {
private long id;@Length(max = 3)
private String content;public Saying() {
}public Saying(long id, String content) {
this.id = id;
this.content = content;
}@JsonProperty
public long getId() {
return id;
}@JsonProperty
public String getContent() {
return content;
}
}
这里的JSON序列化是由Jackson完成的,它支持的远不止像这样简单的JavaBean对象。除了复杂的注释集之外,您甚至可以编写自定义序列化器和反序列化器。
创建一个Resource类
Resource是Dropwizard应用程序的重要组成部分。每个资源类都与一个URI模板相关联。对于我们的应用程序,我们需要一个资源。
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); } }
两个注解:@Path和@Produces。
@Path(“/hello-world”)告诉Jersey这个资源可以在URI /hello-world访问。
@Produces(MediaType.APPLICATION_JSON)让Jersey的内容协商代码知道这个资源生成的表示形式是application/json。
AtomicLong为我们提供了一种廉价的、线程安全的方法来生成惟一(ish) id。
资源类由多个线程并发使用。一般来说,我们建议资源是无状态/不可变的,但务必记住上下文。
@QueryParam(“name”)注释告诉Jersey将name参数从查询字符串映射到方法中的name参数。
@ timing注解,Dropwizard会自动记录它调用的持续时间和速率,作为一个度量计时器。
sayHello返回后,Jersey将获取say实例并寻找一个提供者类,该类可以将say实例编写为application/json。Dropwizard内置了这样一个提供程序,允许将Java对象作为JSON对象来生成和消费。提供者写出JSON,客户机收到一个包含应用程序/ JSON内容类型的200 OK响应。
注册Resource类
在实际工作之前,我们需要返回HelloWorldApplication并添加这个新的资源类。在其运行方法中,我们可以从HelloWorldConfiguration实例读取模板和默认名称,创建一个新的HelloWorldResource实例,然后将其添加到应用程序的Jersey环境中。
@Override public void run(HelloWorldConfiguration configuration, Environment environment) { final HelloWorldResource resource = new HelloWorldResource( configuration.getTemplate(), configuration.getDefaultName() ); environment.jersey().register(resource); }
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(); } }
注册健康检查
@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
要开始将Hello World应用程序构建为一个JAR,我们需要配置一个Maven插件Maven -shade
<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在它的包阶段做一些事情:
1.生成一个pom.xml文件,该文件不包含JAR中包含内容的库的依赖关系。
2.从已签名jar中排除所有数字签名。如果不这样做,那么Java将认为签名无效,并且不会加载或运行JAR文件。
3.整理jar中的各种META-INF/services条目,而不是覆盖它们。(Dropwizard和Jersey都离不开这些。)
4.设置com.example.helloworld。HelloWorldApplication作为JAR的主类。这将允许您使用java -jar运行JAR。
版本控制JAR
Dropwizard也可以使用项目版本,如果它作为实现版本嵌入到JAR的清单中
<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>
运行Application
server命令需要一个配置文件,所以让我们继续给它之前保存的YAML文件:
java -jar target/hello-world-0.0.1-SNAPSHOT.jar server hello-world.yml
您的Dropwizard应用程序现在监听端口8080的应用程序请求和8081的管理请求。如果按下^C,应用程序将优雅地关闭,首先关闭服务器套接字,然后等待正在处理的请求,然后关闭进程本身。
好,祝贺你。您已经为生产准备好了一个Hello World应用程序(除了缺少测试之外),该应用程序每秒可以处理30,000到50,000个请求。希望您已经了解了Dropwizard如何将Jetty、Jersey、Jackson和其他稳定、成熟的库结合起来,为开发RESTful web应用程序提供一个出色的平台。
Dropwizard包含的内容比这里介绍的多得多(命令、包、servlet、高级配置、验证、HTTP客户机、数据库客户机、视图等等),所有这些都在用户手册中介绍。
dropwizard-core
dropwizard-core模块为您提供了大多数应用程序所需的一切。
它包括:
- Jetty:一个高性能的HTTP服务器
- Jersey:一个功能全面的restful风格的web框架
- Jackson:对JVM而言,一个最好的JSON库
- Metrics:一个优秀的应用监控库
- Guava:Google的一个优秀工具库
- Logback:Log4j的继承者,Log4j是Java中使用最广泛的日志记录框架
- Hibernate Validator:Java Bean验证标准的参考实现。
开发项目
如果您计划为其他开发人员开发一个客户端来访问您的服务,我们建议您将您的项目分为三个Maven模块:project-api、project-client和project-application。
- project-api:包含你的实体类
- project-client:使用这些类,去为您的应用程序实现一个完整的client
- project-application:提供实际的应用程序实现,包括资源
如果你不计划为其他开发人员开发一个客户端,那么可以将project-api和project-application组合到一个项目中。
应用
毫无疑问(unsurprisingly),Dropwizard应用程序的主要入口点是application类,每个应用程序都有一个名称,该名称主要用于呈现命令行界面。在应用程序的构造函数中,可以向应用程序添加包和命令。
配置
Dropwizard提供了许多内置的配置参数,每个应用程序子类都有一个类型参数:它的匹配配置子类的类型参数。这些通常位于应用程序主包的根目录中。例如,您的用户应用程序将有两个类:UserApplicationConfiguration继承了Configuration和UserApplication继承了application <UserApplicationConfiguration>。
当应用程序运行配置好的命令(如server命令)时,Dropwizard解析提供的YAML配置文件,并通过将YAML字段名映射到对象字段名来构建应用程序配置类的实例。
- 如果您的配置文件没有以.yml或.yaml结尾,Dropwizard将尝试将其解析为JSON文件。
为了保持配置文件和类的可管理性,我们建议将相关的配置参数分组到独立的配置类中。例如,如果您的应用程序需要一组配置参数才能连接到消息队列,我们建议您创建一个新的MessageQueueFactory类:
public class MessageQueueFactory { @NotEmpty private String host; @Min(1) @Max(65535) private int port = 5672; @JsonProperty public String getHost() { return host; } @JsonProperty public void setHost(String host) { this.host = host; } @JsonProperty public int getPort() { return port; } @JsonProperty public void setPort(int port) { this.port = port; } public MessageQueueClient build(Environment environment) { MessageQueueClient client = new MessageQueueClient(getHost(), getPort()); environment.lifecycle().manage(new Managed() { @Override public void start() { } @Override public void stop() { client.close(); } }); return client; } }
在本例中,我们的工厂将自动将MessageQueueClient连接绑定到应用程序环境的生命周期
然后,主配置子类可以将其包含为成员字段
public class ExampleConfiguration extends Configuration { @Valid @NotNull private MessageQueueFactory messageQueue = new MessageQueueFactory(); @JsonProperty("messageQueue") public MessageQueueFactory getMessageQueueFactory() { return messageQueue; } @JsonProperty("messageQueue") public void setMessageQueueFactory(MessageQueueFactory factory) { this.messageQueue = factory; } }
然后你的应用子类可以使用你的工厂直接为消息队列构造一个客户端:
public void run(ExampleConfiguration configuration, Environment environment) { MessageQueueClient messageQueue = configuration.getMessageQueueFactory().build(environment); }
然后,在您的应用程序的YAML文件中,您可以使用嵌套的messageQueue字段:
messageQueue: host: mq.example.com port: 5673
@NotNull、@NotEmpty、@Min、@Max和@Valid注释是Dropwizard验证功能的一部分。如果您的YAML配置文件是messageQueue。主机字段丢失(或为空字符串),Dropwizard将拒绝启动,并将输出描述问题的错误消息。
一旦您的应用程序解析了YAML文件并构造了它的配置实例,Dropwizard就会调用您的应用程序子类来初始化您的应用程序环境。
环境变量
dropwizard-configuration模块同样提供了用环境变量来替换配置类中的值,通过使用SubstitutingSourceProvider
和EnvironmentVariableSubstitutor。
public class MyApplication extends Application<MyConfiguration> { // [...] @Override public void initialize(Bootstrap<MyConfiguration> bootstrap) { // Enable variable substitution with environment variables bootstrap.setConfigurationSourceProvider( new SubstitutingSourceProvider(bootstrap.getConfigurationSourceProvider(), new EnvironmentVariableSubstitutor(false) ) ); } // [...] }
应该替换的配置设置需要在配置文件中显式地编写,并遵循Apache Commons Lang库中strsubstitute tor的替换规则。
mySetting: ${DW_MY_SETTING} defaultSetting: ${DW_DEFAULT_SETTING:-default value}
SSL
Dropwizard内置SSL支持。您将需要提供自己的java密钥存储库
server: applicationConnectors: - type: https port: 8443 keyStorePath: example.keystore keyStorePassword: example validateCerts: false
引导(Bootstrapping)
Dropwizard应用程序在提供命令行界面、解析配置文件或作为服务器运行之前,必须先经过引导阶段。此阶段对应于应用程序子类的initialize方法。您可以添加包、命令或注册Jackson模块,以允许您将自定义类型包含在配置类中。
private final HibernateBundle<HelloWorldConfiguration> hibernate = new HibernateBundle<HelloWorldConfiguration>(
RoleInfoHiber.class) {
@Override
public PooledDataSourceFactory getDataSourceFactory(HelloWorldConfiguration configuration) {
return configuration.getDatabase();
}
};
@Override
public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
bootstrap.addBundle(hibernate);
}
环境
Dropwizard环境由应用程序提供的所有资源、servlet、过滤器、健康检查、Jersey提供程序、托管对象、任务和Jersey属性组成。
每个应用程序子类实现一个run方法。这是你应该创建新的资源实例等,并将它们添加到给定的环境类:
@Override public void run(ExampleConfiguration config, Environment environment) { // encapsulate complicated setup logic in factories final Thingy thingy = config.getThingyFactory().build(); environment.jersey().register(new ThingyResource(thingy)); environment.healthChecks().register("thingy", new ThingyHealthCheck(thingy)); }
保持run方法干净是很重要的,因此,如果创建某个对象的实例很复杂,比如上面的Thingy类,请将该逻辑提取到工厂中。
健康检查
健康检查是一个运行时测试,您可以使用它来验证应用程序在其生产环境中的行为。例如,你可能想要确保你的数据库客户端连接到数据库:
public class DatabaseHealthCheck extends HealthCheck { private final Database database; public DatabaseHealthCheck(Database database) { this.database = database; } @Override protected Result check() throws Exception { if (database.isConnected()) { return Result.healthy(); } else { return Result.unhealthy("Cannot connect to " + database.getUrl()); } } }
然后,您可以将此健康检查添加到应用程序的环境中:
environment.healthChecks().register("database", new DatabaseHealthCheck(database));
通过向管理端口上的healthcheck发送GET请求,您可以运行这些测试并查看结果:
$ curl http://dw.example.com:8081/healthcheck {"deadlocks":{"healthy":true},"database":{"healthy":true}}
如果所有健康检查报告成功,则返回一个200 OK。如果出现任何失败,将返回一个500个内部服务器错误,并带有错误消息和异常堆栈跟踪(如果抛出异常)。
所有Dropwizard应用程序都默认安装了死锁健康检查,该检查使用Java 1.6内置的线程死锁检测来确定是否有线程死锁