最近项目用到了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的初步,还有许多其他功能,由于时间关系,不做详细介绍,如果
有时间我会再奉上一版深度版的。链接为:
希望这次的讲解对大家有帮助,感谢开源。