最近项目用到了Dropwizard框架,个人感觉还不错,那么这里就从他们官网入手,然后加上自己的实现步骤让大家初步了解这个框架。
官网对DW(Dropwizard)的定义是跨越了一个库和框架之间的界限。他的目标是提供一个生产就绪的web应用程序所需的一切性能可靠的实现。那么这句话可能有些绕,我个人理解就是他能免去我们部署web应用的很多步骤。由于这个功能被提取到可以重复使用的库中,我们的应用程序保持很大程度的精简和集中,这样可以减少我们程序的上线时间和维护负担。
Jetty for HTTP
由于Web应用不可能缺少HTTP,DW使用Jetty Http库将一个非常棒的HTTP服务器嵌入到我们的项目中。DW不是将你的程序提交到复杂的服务器上,DW上有个main方法来启动我们的服务器,DW是将我们的应用作为一个简单的线程来跑,消去了Java生产环境中一些非常复杂令人讨厌的过程,并且允许我们使用所有现有的Unix进程管理工具。
Jersey for REST
为了定义Restful的web应用,我们发现在性能和特性方面没有什么能比得过Jersey。它允许你编写干净的,可以测试的类,这个类可以优雅的将http请求映射成为简单的Java对象。它支持流输出,矩阵URL参数,条件GET请求,还有更多。
Jackson for JSON
在数据格式方面,JSON已经成为了网络的通用语,Jackson在jvm中就是Json的龙头老大。除了像闪电一样快速,他有一个复杂的对象映射,允许你直接导出你的域模型。
Metrics for metrics
Metrics库对事物进行舍入,在你的生产环境中,为你提供独一无二的洞察力。(也就是说这个是用来监控)那么到了这里,我们关于DW的总体印象应该已经差不多了,下面我结合官网实际操作。
我使用maven和idea进行开发,项目名字为:dw_demo。关于如何创建maven项目不解释,创建完项目后如图所示:
然后打开我们的pom.xml文件,加入dw的依赖(以下并非完全pom文件,仅展现部分):
<properties>
<dropwizard.version>0.9.2</dropwizard.version>
</properties>
<dependencies>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
<version>${dropwizard.version}</version>
</dependency>
</dependencies>
Creating A Configuration Class
内容如下:
package com.config; import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;
import org.hibernate.validator.constraints.NotEmpty; /**
* Created by moon on 2017/1/13.
*/
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 用来说helloworld的模板。defaultName 默认的名字。template和defaultName都用@NotEmpty被注释,所以在YAML配置文件中如果有空值或者忘了其中一者,异常将会被抛出,我们的应用将不会被启动。
defaultName和template的get 和set 方法都被@JsonProperty标注,这不止允许jackson从YAML配置文件反序列化,同样允许它序列化。
然后我们创建一个YAML的配置文件:
里面的内容如下:
template: Hello, %s!
defaultName: Stranger
大家可以看到,与我们的配置类中的变量一一对应,相信很多人看到这里就明白了。
Creating An Application Class
结合我们项目中的Configuration子类,我们的Application的子类形成了我们DW的应用的核心。Application的子类把不同的提供各式各样功能的包和命令拉取到了一起。
现在,我们开始建立我们的Application子类:
其内容如下:
package com.app; import com.config.HelloWorldConfiguration;
import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment; /**
* Created by moon on 2017/1/13.
*/
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使用应用程序的configuration进行参数化。(因为用了我们的HelloWorldConfiuration,而它是Configuration的子类)。
initialize方法用于配置应用在正式启动之前所需:包,配置源等。同时我们需要加入一个main方法,这是我们应用的入口。到目前为止,我们并没有实现任何的功能,所以我们的run方法有点无趣,让我们开始丰富它。
Creating A Representation Class
在我们开始继续我们的程序之前,我们需要停下来思考一下我们程序的API。幸运的是,我们的应用需要符合行业标准。RFC 1149(别问我这是什么,连这个都不知道,我tm也不知道)。他指定了helloworld saying的如下json表达形式:
{
"id": 1,
"content": "Hi!"
}
id字段是语法的唯一标识符。content是说的具体内容。
为了建模这个表示,我们需要创建一个表示类 :
该类的内容如下:
package com.api; import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.Length; /**
* Created by moon on 2017/1/13.
*/
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在多线程环境和单线程环境非常容易被推理。其次,它使用java的JavaBean来保存id和content属性。这允许jackson把他序列化为我们需要的JSON。jackson对象的映射代码将会使用getId()返回的对象来填充JSON对象的id字段,content同理。最后,bean利用验证来确保内容不大于3。
Creating A Resource Class
Jersey资源是DW应用程序的肉和土豆(这种比喻我也是醉了)。每个资源类都与URL相关联(这个很重要,后面有说)。对于我们的应用程序来说,我们需要一个resources来通过url:/helloworld来返回新的Saying实例对象。
现在我们开始建立的Resource:
类的内容如下:
package com.resource; import com.api.Saying;
import com.codahale.metrics.annotation.Timed;
import com.google.common.base.Optional; 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; /**
* Created by moon on 2017/1/13.
*/
@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.or(defaultName));
return new Saying(counter.incrementAndGet(), value);
} }
HelloWorldResource有两个声明:@Path和@Produces。@Path("/hello-world")告诉Jersey这个resource可以通过 "/hello-world"URL被访问。
@Produces(MediaType.APPLICATION_JSON)让Jersey的内容协商代码知道这个资源产生的是application/json.
HelloWorldResource构造器接收两个参数,创建saying的template和当用户没有指明名字时的默认名称。AtomicLong为我们提供一种线程安全,简易的方式去生成(ish)ID。
sayHello方法是这个类的肉,也是一个非常简单的方法。@QueryParam("name")告诉Jersey把在查询参数中的name映射到方法中的name中。如果一个客户发送请求到:/hello-world?name=Dougie,sayHello 方法将会伴随Optional.of("Dougie")被调用。如果查询参数中没有name,sayHello将会伴随着Optional.absent()被调用。
在sayHello方法里面,我们增加计数器的值,使用String.format来格式化模板,返回一个新的Saying实例。因为sayHello被@Timed注释,DW将会自动调用他的持续时间和速率记录为度量定时器。
一旦sayHello返回,Jersey将会采用Saying的实例,并寻找一个提供程序类来将Saying实例写为:application/json。
Registering A Resource
在这些正式工作之前,我们需要到HelloWorldApplication中,并将新的resouce加入其中,在run方法中我们可以读取到HelloWorldConfiguration的template和defaultName实例,创建一个新的HelloWorldResource实例,并将其加入到新的Jersey环境中。
我们HelloWorldApplication中新的run方法如下:
@Override
public void run(HelloWorldConfiguration configuration,
Environment environment) {
final HelloWorldResource resource = new HelloWorldResource(
configuration.getTemplate(),
configuration.getDefaultName()
);
environment.jersey().register(resource);
}
当我们的应用启动的时候,我们使用配置文件中的参数创建一个新的资源类实例,并传递给environment.
在pom文件中加入:
<build>
<plugins>
<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.app.HelloWorldApplication</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin> </plugins>
</build>
然后打包:
打包成功后,在我们的target目录下面会出现我们所需的包:
然后我们开始运行:
这里面官方为我们提供两个参数,我们需要启动服务,所以后面加入server参数,重新启动如下:
这说明我们的项目已经启动了,那么让我们访问一下url看是否正确:
返回结果正常,没毛病。
以上仅仅是DW的初步,还有许多其他功能,由于时间关系,不做详细介绍,如果
有时间我会再奉上一版深度版的。链接为:
希望这次的讲解对大家有帮助,感谢开源。