[译] 第十三天:Dropwizard - 很棒的Java REST 服务栈

前言

8年软件开发生涯中我俨然一位Java开发者了。大多数我写的程序,都用Spring框架或者Java EE.最近我在学Python Web开发,其中印象很深的一个是Flask框架。Flask框架是个微框架,使得写REST后端很简单。我今天的30天挑战,决定找一款Java框架替代PythonFlask。一番搜索后,我发现Dropwizard框架可以达到如Flask同样的功效。这篇博客,我们来学习怎样用Dropwizard构建RESTful Java MongoDB程序。

什么是Dropwizard?

Dropwizard是一款开源Java框架,用于ops友好开发,高性能RESTful后端开发。它由Yammer开发并提供基于JVM后端的支持。 

Dropwizar延续最优Java库,嵌入到程序包,包括以下组件:

  1. 嵌入Jetty: 每个程序都打包成jar而不是war,启用自带的Jetty容器,没有WAR文件也没有外部servlet容器。
  2. JAX-RS:      Jersey(JAX-RS的参考实现)用来写RESTful Web服务,这样就没白费你已懂的JAX-RS知识。
  3. JSON: REST服务使用JSON, Jackson库用来做所有JSON处理。
  4. Logging: 用Logback和SLF4J完成。
  5. Hibernate      Validator: Dropwizard用Hbernate Vlidator API来验证声明。
  6. Metrics:      Dropwizard支持用Metrics库进行检测,提供观察代码对生产做了什么的绝佳视觉。 

为什么选择Dropwizard?

我学习Dropwizard的几点原因:

  1. 快速的项目引导:要是你用过Spring或者Java EE, 就会了解开发者要通过项目引导的痛苦,用Dropwizard,只需要添加一个依赖到pom.xml文件就好了。
  2. 项目Metrics:      Dropwizard支持项目metrics, 它提供很有用的信息如请求/相应时间等,我们只需给出@Timed注解就可获得方法执行时间。
  3. 生产力:每个Dropwizard应用有一个启用Jetty容器的主程序,意味着可以在IDE里直接像主程序一样运行和调试,没有必要再编译或者部署WAR文件。 

Github仓库

今天的demo放在github: day13-dropwizard-mongodb-demo-app

前提准备

  1. 必须会Java基础。
  2. 下载和安装MongoDB数据库
  3. 安装最新Java Development      Kit(JDK), 可以装OpenJDK7或者Oracle JDK 7. OpenShift支持OpenJDK 6和7, 这篇博客,我们用JDK 7.
  4. 官网下载最新的Eclipse,目前最新版本是Kepler.

         

安装Eclipse很简单,只需解压下载的安装包。在linux或者mac上,打开命令管理器输入以下命令。

$ tar -xzvf eclipse-jee-kepler-R-*.tar.gz

Windows上可以用7-zip或者其他解压工具解压,解压后,在你解压的路径会有一个eclipse的文件夹,可以给可执行文件创建快捷键。

第一步:新建Maven项目

打开Eclipse IDE导航到项目空间,新建项目,到File > New > Maven Project,选择maven-archetype-quichstart,输入Ground IdArtifact Id,最后点Finish.

第二步:更新pom.xml

现在更新pom.xml, 加入dropwizard-core maven依赖,再更新Maven项目用Java 1.7版本,更新pom.xml后更新Maven项目(右击>Maven>Update Project)

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>com.shekhar</groupId>
    <artifactId>blog</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
 
    <name>blog</name>
    <url>http://maven.apache.org</url>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>com.yammer.dropwizard</groupId>
            <artifactId>dropwizard-core</artifactId>
            <version>0.6.2</version>
        </dependency>
 
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
 
 
        </plugins>
    </build>
</project>
View Code

第三步:创建配置类

每个Dropwizard程序都有一个配置类,用来指定特定环境变量。后面我们会添加MongoDB配置参数如host, port, db name.这个类继承com.yammer.dropwizard.config.Configuration

import com.yammer.dropwizard.config.Configuration;
 
public class BlogConfiguration extends Configuration{
 
}
View Code

第四步:创建服务类

Dropwizard由一个服务类引导,这个类涵盖所有提供基础功能的绑定和命令,也启用内嵌的Jetty服务,继承com.yammer.dropwizard.Service.

import com.yammer.dropwizard.Service;
import com.yammer.dropwizard.config.Bootstrap;
import com.yammer.dropwizard.config.Environment;
 
public class BlogService extends Service<BlogConfiguration> {
 
    public static void main(String[] args) throws Exception {
        new BlogService().run(new String[] { "server" });
    }
 
    @Override
    public void initialize(Bootstrap<BlogConfiguration> bootstrap) {
        bootstrap.setName("blog");
    }
 
    @Override
    public void run(BlogConfiguration configuration, Environment environment) throws Exception {
 
    }
 
}
View Code

 

以上代码做了以下动作:

  1. 这个类有一个主方法,作为服务的入口,在这个主方法里,创建了一个BlogService实例来调用run方法,server命令用参数形式传递,它会启动内嵌的Jetty服务。
  2. Initalize 方法在执行service run方法前调用,设置服务名为blog.
  3. 接下来,有一个run方法会在service运行时调用,后面,我们会添加JAX-RS资源到这个方法。

第五步:写IndexResource

来写第一个当GET请求 '/' url时会被引用的资源,新建一个JAX-RS资源,它会列出所有博客。

import java.util.Arrays;
import java.util.List;
 
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
 
import com.yammer.metrics.annotation.Timed;
 
@Path("/")
public class IndexResource {
 
    @GET
    @Produces(value = MediaType.APPLICATION_JSON)
    @Timed
    public List<Blog> index() {
        return Arrays.asList(new Blog("Day 12: OpenCV--Face Detection for Java Developers",
                "https://www.openshift.com/blogs/day-12-opencv-face-detection-for-java-developers"));
    }
}
View Code

 

以上代码是一个标准的JAX-RS资源类,注释了@Path,定义了index()方法。Index()返回博客的集合,这些博客会被转换成JSON文档,@Timed注释确定了Dropwizard基础时间。 

 

以上IndexResource用一篇博客呈现,显示如下,博客的呈现用hibernate validator注释来确保内容有效性。例如,用@URL注释来确保只有有效的URL会存到MongoDB数据库。

import java.util.Date;
import java.util.UUID;
 
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.URL;
 
public class Blog {
 
    private String id = UUID.randomUUID().toString();
 
    @NotBlank
    private String title;
 
    @URL
    @NotBlank
    private String url;
 
    private final Date publishedOn = new Date();
 
    public Blog() {
    }
 
    public Blog(String title, String url) {
        super();
        this.title = title;
        this.url = url;
    }
 
    public String getId() {
        return id;
    }
 
    public String getTitle() {
        return title;
    }
 
    public String getUrl() {
        return url;
    }
 
    public Date getPublishedOn() {
        return publishedOn;
    }
}
View Code

 

然后,在servicerun方法里注册IndexResource, 用以下代码更新BlogServicerun方法。

@Override
public void run(BlogConfiguration configuration, Environment environment) throws Exception {
   environment.addResource(new IndexResource());
}
View Code

 

现在我们可以把BlogService作为主程序运行,右击>Run As>Java Application. 会启动内嵌Jetty容器然后看到程序运行在http://localhost:8080/.

$ curl http://localhost:8080
 
[{"id":"9bb43d53-5436-4dac-abaa-ac530c833df1","title":"Day 12: OpenCV--Face Detection for Java Developers","url":"https://www.openshift.com/blogs/day-12-opencv-face-detection-for-java-developers","publishedOn":1384090975372}]

 

管理界面在http://localhost:8081/ 

 

点击Metrics可以查看IndexResourceMetricx, 数据是JSON格式。

"com.shekhar.blog.IndexResource" : {
    "index" : {
      "type" : "timer",
      "duration" : {
        "unit" : "milliseconds",
        "min" : 17.764,
        "max" : 17.764,
        "mean" : 17.764,
        "std_dev" : 0.0,
        "median" : 17.764,
        "p75" : 17.764,
        "p95" : 17.764,
        "p98" : 17.764,
        "p99" : 17.764,
        "p999" : 17.764
      },
      "rate" : {
        "unit" : "seconds",
        "count" : 1,
        "mean" : 7.246537731991882E-4,
        "m1" : 2.290184897291144E-12,
        "m5" : 3.551918562683463E-5,
        "m15" : 2.445031498756583E-4
      }
    }
  },
View Code

第六步:配置MongoDB

pom.xml里添加Mongo-jackson-mapper依赖。

<dependency>
    <groupId>net.vz.mongodb.jackson</groupId>
    <artifactId>mongo-jackson-mapper</artifactId>
    <version>1.4.2</version>
</dependency>
View Code

 

更新BlogConfiguration类的MongoDB数据库信息,如host, port, database name.

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
 
import org.codehaus.jackson.annotate.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;
 
import com.yammer.dropwizard.config.Configuration;
 
public class BlogConfiguration extends Configuration {
 
    @JsonProperty
    @NotEmpty
    public String mongohost = "localhost";
 
    @JsonProperty
    @Min(1)
    @Max(65535)
    public int mongoport = 27017;
 
    @JsonProperty
    @NotEmpty
    public String mongodb = "mydb";
}
View Code

 

接下来新建一个MongoManaged类,让我们能控制程序启动和停止,它实现接口com.yammer.dropwizard.lifecycle.Managed.

import com.mongodb.Mongo;
import com.yammer.dropwizard.lifecycle.Managed;
 
public class MongoManaged implements Managed {
 
    private Mongo mongo;
 
    public MongoManaged(Mongo mongo) {
        this.mongo = mongo;
    }
 
    @Override
    public void start() throws Exception {
    }
 
    @Override
    public void stop() throws Exception {
        mongo.close();
    }
 

}
View Code

以上代码,我们在Stop方法里关掉MongoDB连接。

 

接下来我们创建一个MongoHealthCheck来检查MongoDB是连接还是断开的,Health checkDropwizard在生产环境做执行时测试检测服务的行为的功能。

import com.mongodb.Mongo;
import com.yammer.metrics.core.HealthCheck;
 
public class MongoHealthCheck extends HealthCheck {
 
    private Mongo mongo;
 
    protected MongoHealthCheck(Mongo mongo) {
        super("MongoDBHealthCheck");
        this.mongo = mongo;
    }
 
    @Override
    protected Result check() throws Exception {
        mongo.getDatabaseNames();
        return Result.healthy();
    }
 
}
View Code

 

现在更新BlogService, 加入MongoDB配置。

package com.shekhar.blog;
 
import com.mongodb.Mongo;
import com.yammer.dropwizard.Service;
import com.yammer.dropwizard.config.Bootstrap;
import com.yammer.dropwizard.config.Environment;
 
public class BlogService extends Service<BlogConfiguration> {
 
    public static void main(String[] args) throws Exception {
        new BlogService().run(new String[] { "server" });
    }
 
    @Override
    public void initialize(Bootstrap<BlogConfiguration> bootstrap) {
        bootstrap.setName("blog");
    }
 
    @Override
    public void run(BlogConfiguration configuration, Environment environment) throws Exception {
        Mongo mongo = new Mongo(configuration.mongohost, configuration.mongoport);
        MongoManaged mongoManaged = new MongoManaged(mongo);
        environment.manage(mongoManaged);
 
        environment.addHealthCheck(new MongoHealthCheck(mongo));
 
        environment.addResource(new IndexResource());
    }
 
}
View Code

以上代码:

  1. BlogConfiguration对象新建了一个Mongo实例。
  2. 新建了MongoManaged实例并添加到环境中。
  3. 添加了正常检测。

 

以主程序运行应用,可以通过http://localhost:8081/healthcheckHealthCheck页面检测MongoDB是否在运行,如果没有运行,可以看到异常信息。

! MongoDBHealthCheck: ERROR
!  can't call something : Shekhars-MacBook-Pro.local/192.168.1.101:27017/admin
 
com.mongodb.MongoException$Network: can't call something : Shekhars-MacBook-Pro.local/192.168.1.101:27017/admin
    at com.mongodb.DBTCPConnector.call(DBTCPConnector.java:227)
    at com.mongodb.DBApiLayer$MyCollection.__find(DBApiLayer.java:305)
    at com.mongodb.DB.command(DB.java:160)
    at com.mongodb.DB.command(DB.java:183)
    at com.mongodb.Mongo.getDatabaseNames(Mongo.java:327)
    at com.shekhar.blog.MongoHealthCheck.check(MongoHealthCheck.java:17)
    at com.yammer.metrics.core.HealthCheck.execute(HealthCheck.java:195)
    at 
Caused by: java.io.IOException: couldn't connect to [Shekhars-MacBook-Pro.local/192.168.1.101:27017] bc:java.net.ConnectException: Connection refused
    at com.mongodb.DBPort._open(DBPort.java:228)
    at com.mongodb.DBPort.go(DBPort.java:112)
    at com.mongodb.DBPort.call(DBPort.java:79)
    at com.mongodb.DBTCPConnector.call(DBTCPConnector.java:218)
    ... 33 more
 
* deadlocks: OK

 

启动MongoDB可以看到如下

* MongoDBHealthCheck: OK
* deadlocks: OK

第七步:创建BlogResource

现在来写BlogResource类,用于响应创建博客的入口。

import java.util.ArrayList;
import java.util.List;
 
import javax.validation.Valid;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
 
import net.vz.mongodb.jackson.DBCursor;
import net.vz.mongodb.jackson.JacksonDBCollection;
 
import com.yammer.metrics.annotation.Timed;
 
@Path("/blogs")
@Produces(value = MediaType.APPLICATION_JSON)
@Consumes(value = MediaType.APPLICATION_JSON)
public class BlogResource {
 
    private JacksonDBCollection<Blog, String> collection;
 
    public BlogResource(JacksonDBCollection<Blog, String> blogs) {
        this.collection = blogs;
    }
 
    @POST
    @Timed
    public Response publishNewBlog(@Valid Blog blog) {
        collection.insert(blog);
        return Response.noContent().build();
    }
}
View Code

 

Java程序类型运行BlogService类,要测试BlogResource, 设置一个curl请求。

$ curl -i -X POST -H "Content-Type: application/json" -d '{"title":"Day 12: OpenCV--Face Detection for Java Developers","url":"https://www.openshift.com/blogs/day-12-opencv-face-detection-for-java-developers"}' http://localhost:8080/blogs
 
 
HTTP/1.1 204 No Content
Date: Sun, 10 Nov 2013 14:08:03 GMT
Content-Type: application/json

第八步:更新IndexResource

现在更新IndexResource index()方法,从MongoDB获取所有博客文档。

import java.util.ArrayList;
import java.util.List;
 
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
 
import net.vz.mongodb.jackson.DBCursor;
import net.vz.mongodb.jackson.JacksonDBCollection;
 
import com.yammer.metrics.annotation.Timed;
 
@Path("/")
public class IndexResource {
 
    private JacksonDBCollection<Blog, String> collection;
 
    public IndexResource(JacksonDBCollection<Blog, String> blogs) {
        this.collection = blogs;
    }
 
    @GET
    @Produces(value = MediaType.APPLICATION_JSON)
    @Timed
    public List<Blog> index() {
        DBCursor<Blog> dbCursor = collection.find();
        List<Blog> blogs = new ArrayList<>();
        while (dbCursor.hasNext()) {
            Blog blog = dbCursor.next();
            blogs.add(blog);
        }
        return blogs;
    }
 
}
View Code

 

更新BlogService run方法,传递博客集合到IndexResource.

@Override
    public void run(BlogConfiguration configuration, Environment environment) throws Exception {
        Mongo mongo = new Mongo(configuration.mongohost, configuration.mongoport);
        MongoManaged mongoManaged = new MongoManaged(mongo);
        environment.manage(mongoManaged);
 
        environment.addHealthCheck(new MongoHealthCheck(mongo));
 
        DB db = mongo.getDB(configuration.mongodb);
        JacksonDBCollection<Blog, String> blogs = JacksonDBCollection.wrap(db.getCollection("blogs"), Blog.class, String.class);
 
        environment.addResource(new IndexResource(blogs));
 
        environment.addResource(new BlogResource(blogs));
    }
View Code

 

Java程序形式运行BlogService, 要测试BlogResource, 设置curl请求。

$ curl http://localhost:8080
 
[{"id":"527f9806300462bbd300687e","title":"Day 12: OpenCV--Face Detection for Java Developers","url":"https://www.openshift.com/blogs/day-12-opencv-face-detection-for-java-developers","publishedOn":1384093702592}]

第九步:发布到云

这有一个博客讲我们怎样发布Dropwizard程序到OpenShift, 参考博客 

 

这是今天的内容,继续给反馈吧。 

 

原文:https://www.openshift.com/blogs/day-13-dropwizard-the-awesome-java-rest-server-stack

posted on 2013-12-26 18:22  百花宫  阅读(4615)  评论(0编辑  收藏  举报