Building an API With the Spark Java Framework – 用Spark Java框架建立一个API

最后修改: 2017年 2月 1日

1. Introduction

1.介绍

In this article, we will have a quick introduction to Spark framework. Spark framework is a rapid development web framework inspired by the Sinatra framework for Ruby and is built around Java 8 Lambda Expression philosophy, making it less verbose than most applications written in other Java frameworks.

在本文中,我们将对Spark框架进行简要介绍。Spark框架是一个快速开发的Web框架,其灵感来自于Ruby的Sinatra框架,并围绕着Java 8 Lambda Expression理念而构建,这使得它比大多数用其他Java框架编写的应用程序要少一些啰嗦。

It’s a good choice if you want to have a Node.js like experience when developing a web API or microservices in Java. With Spark, you can have a REST API ready to serve JSON in less than ten lines of code.

如果你想在用Java开发网络API或微服务时获得类似Node.js的体验,这是一个不错的选择。有了Spark,你可以在不到十行的代码中拥有一个准备好的REST API,为JSON服务。

We will have a quick start with a “Hello World” example, followed by a simple REST API.

我们将通过一个 “Hello World “的例子快速入门,然后是一个简单的REST API。

2. Maven Dependencies

2.Maven的依赖性

2.1. Spark Framework

2.1.火花框架

Include following Maven dependency in your pom.xml:

在你的pom.xml中包含以下Maven依赖项。

<dependency>
    <groupId>com.sparkjava</groupId>
    <artifactId>spark-core</artifactId>
    <version>2.5.4</version>
</dependency>

You can find the latest version of Spark on Maven Central.

您可以在Maven Central上找到Spark的最新版本。

2.2. Gson Library

2.2.Gson库

At various places in the example, we will be using Gson library for JSON operations. To include Gson in your project, include this dependency in your pom.xml:

在这个例子的不同地方,我们将使用Gson库进行JSON操作。要在你的项目中加入Gson,请在你的pom.xml中加入这个依赖。

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.0</version>
</dependency>

You can find the latest version of Gson on Maven Central.

您可以在Maven Central上找到Gson的最新版本。

3. Getting Started With Spark Framework

3.开始使用Spark框架

Let’s take a look at the basic building blocks of a Spark application and demonstrate a quick web service.

让我们来看看Spark应用程序的基本构件,并演示一个快速的网络服务。

3.1. Routes

3.1.路径

Web services in Spark Java are built upon routes and their handlers. Routes are essential elements in Spark. As per the documentation, each route is made up of three simple pieces – a verb, a path, and a callback.

Spark Java中的Web服务是建立在路由及其处理程序之上的。路由是Spark中的重要元素。根据文档,每个路由由三个简单的部分组成 – verbpathcallback

  1. The verb is a method corresponding to an HTTP method. Verb methods include: get, post, put, delete, head, trace, connect, and options
  2. The path (also called a route pattern) determines which URI(s) the route should listen to and provide a response for
  3. The callback is a handler function that is invoked for a given verb and path in order to generate and return a response to the corresponding HTTP request. A callback takes a request object and response object as arguments

Here we show the basic structure for a route that uses the get verb:

这里我们展示了一个使用get动词的路由的基本结构。

get("/your-route-path/", (request, response) -> {
    // your callback code
});

3.2. Hello World API

3.2.Hello World API</strong

Let’s create a simple web service that has two routes for GET requests and returns “Hello” messages in response. These routes use the get method, which is a static import from the class spark.Spark:

让我们创建一个简单的Web服务,它有两条GET请求的路由,并在响应中返回 “Hello “消息。这些路由使用get方法,它是从spark.Spark类中静态导入的。

import static spark.Spark.*;

public class HelloWorldService {
    public static void main(String[] args) {
 
        get("/hello", (req, res)->"Hello, world");
        
        get("/hello/:name", (req,res)->{
            return "Hello, "+ req.params(":name");
        });
    }
}

The first argument to the get method is the path for the route. The first route contains a static path representing only a single URI (“/hello”).

get方法的第一个参数是路由的路径。第一个路由包含一个只代表一个URI的静态路径(“/hello”)。

The second route’s path (“/hello/:name”) contains a placeholder for the “name” parameter, as denoted by prefacing the parameter with a colon (“:”). This route will be invoked in response to GET requests to URIs such as “/hello/Joe” and “/hello/Mary”.

第二个路由的路径(“/hello/:name”)包含一个“name”参数的占位符,通过在参数前加一个冒号(”:”)来表示。这个路由将被调用,以响应对URI的GET请求,如“/hello/Joe”“/hello/Mary”

The second argument to the get method is a lambda expression giving a functional programming flavor to this framework.

get方法的第二个参数是lambda表达式,给这个框架带来了函数式编程的味道。

The lambda expression has request and response as arguments and helps return the response. We will put our controller logic in the lambda expression for the REST API routes, as we shall see later in this tutorial.

lambda表达式将请求和响应作为参数,并帮助返回响应。我们将把我们的控制器逻辑放在REST API路由的lambda表达式中,我们将在本教程的后面看到这一点。

3.3. Testing the Hello World API

3.3.测试Hello World API

After running the class HelloWorldService as a normal Java class, you will be able to access the service on its default port of 4567 using the routes defined with the get method above.

HelloWorldService类作为一个普通的Java类运行后,你将能够在其默认的4567端口上使用上述get方法定义的路由访问该服务。

Let’s look at the request and response for the first route:

让我们看一下第一条路线的请求和响应。

Request:

请求:

GET http://localhost:4567/hello

Response:

答复:

Hello, world

Let’s test the second route, passing the name parameter in its path:

让我们测试第二个路由,在其路径中传递name参数。

Request:

请求:

GET http://localhost:4567/hello/baeldung

Response:

答复:

Hello, baeldung

See how the placement of the text “baeldung” in the URI was used to match the route pattern “/hello/:name” – causing the second route’s callback handler function to be invoked.

请看URI中文本“baeldung”的位置如何被用来匹配路由模式“/hello/:name”–导致第二个路由的回调处理函数被调用。

4. Designing a RESTful Service

4.设计一个RESTful服务

In this section, we will design a simple REST web service for the following User entity:

在本节中,我们将为以下User实体设计一个简单的REST网络服务。

public class User {
    private String id;
    private String firstName;
    private String lastName;
    private String email;

    // constructors, getters and setters
}

4.1. Routes

4.1.路径

Let’s list the routes that make up our API:

让我们列出构成我们的API的路由。

  • GET /users — get list of all users
  • GET /users/:id — get user with given id
  • POST /users/:id — add a user
  • PUT /users/:id — edit a particular user
  • OPTIONS /users/:id — check whether a user exists with given id
  • DELETE /users/:id — delete a particular user

4.2. The User Service

4.2.用户服务

Below is the UserService interface declaring the CRUD operations for the User entity:

下面是UserService接口,声明了User实体的CRUD操作。

public interface UserService {
 
    public void addUser (User user);
    
    public Collection<User> getUsers ();
    public User getUser (String id);
    
    public User editUser (User user) 
      throws UserException;
    
    public void deleteUser (String id);
    
    public boolean userExist (String id);
}

For demonstration purposes, we provide a Map implementation of this UserService interface in the GitHub code to simulate persistence. You can supply your own implementation with the database and persistence layer of your choice.

出于演示目的,我们在GitHub代码中提供了该UserService接口的Map实现,以模拟持久化。你可以通过你选择的数据库和持久化层提供你自己的实现。

4.3. The JSON Response Structure

4.3.JSON响应结构

Below is the JSON structure of the responses used in our REST service:

下面是我们的REST服务中使用的响应的JSON结构。

{
    status: <STATUS>
    message: <TEXT-MESSAGE>
    data: <JSON-OBJECT>
}

The status field value can be either SUCCESS or ERROR. The data field will contain the JSON representation of the return data, such as a User or collection of Users.

status字段值可以是SUCCESSERRORdata字段将包含返回数据的JSON表示,例如一个UserUsers的集合。

When there is no data being returned, or if the status is ERROR, we will populate the message field to convey a reason for the error or lack of return data.

当没有数据被返回时,或者如果statusERROR,我们将填充message字段以传达错误或缺乏返回数据的原因。

Let’s represent the above JSON structure using a Java class:

让我们用一个Java类来表示上述JSON结构。

public class StandardResponse {
 
    private StatusResponse status;
    private String message;
    private JsonElement data;
    
    public StandardResponse(StatusResponse status) {
        // ...
    }
    public StandardResponse(StatusResponse status, String message) {
        // ...
    }
    public StandardResponse(StatusResponse status, JsonElement data) {
        // ...
    }
    
    // getters and setters
}

where StatusResponse is an enum defined as below:

其中StatusResponse是一个enum,定义如下。

public enum StatusResponse {
    SUCCESS ("Success"),
    ERROR ("Error");
 
    private String status;       
    // constructors, getters
}

5. Implementing RESTful Services

5.实现RESTful服务

Now let’s implement the routes and handlers for our REST API.

现在让我们为我们的REST API实现路由和处理程序。

5.1. Creating Controllers

5.1.创建控制器

The following Java class contains the routes for our API, including the verbs and paths and an outline of the handlers for each route:

下面的Java类包含了我们的API的路由,包括动词和路径以及每个路由的处理程序的概要。

public class SparkRestExample {
    public static void main(String[] args) {
        post("/users", (request, response) -> {
            //...
        });
        get("/users", (request, response) -> {
            //...
        });
        get("/users/:id", (request, response) -> {
            //...
        });
        put("/users/:id", (request, response) -> {
            //...
        });
        delete("/users/:id", (request, response) -> {
            //...
        });
        options("/users/:id", (request, response) -> {
            //...
        });
    }
}

We will show the full implementation of each route handler in the following subsections.

我们将在下面的小节中展示每个路由处理程序的完整实现。

5.2. Add User

5.2.添加用户

Below is the post method response handler which will add a User:

下面是post方法的响应处理器,它将添加一个User

post("/users", (request, response) -> {
    response.type("application/json");
    User user = new Gson().fromJson(request.body(), User.class);
    userService.addUser(user);

    return new Gson()
      .toJson(new StandardResponse(StatusResponse.SUCCESS));
});

Note: In this example, the JSON representation of the User object is passed as the raw body of a POST request.

注意:在这个例子中,User对象的JSON表示法被作为POST请求的原始主体传递。

Let’s test the route:

让我们测试一下这条路线。

Request:

请求:

POST http://localhost:4567/users
{
    "id": "1012", 
    "email": "your-email@your-domain.com", 
    "firstName": "Mac",
    "lastName": "Mason1"
}

Response:

答复:

{
    "status":"SUCCESS"
}

5.3. Get All Users

5.3.获取所有用户

Below is the get method response handler which returns all users from the UserService:

下面是get方法的响应处理程序,它从UserService返回所有用户。

get("/users", (request, response) -> {
    response.type("application/json");
    return new Gson().toJson(
      new StandardResponse(StatusResponse.SUCCESS,new Gson()
        .toJsonTree(userService.getUsers())));
});

Now let’s test the route:

现在让我们来测试一下这个路线。

Request:

请求:

GET http://localhost:4567/users

Response:

答复:

{
    "status":"SUCCESS",
    "data":[
        {
            "id":"1014",
            "firstName":"John",
            "lastName":"Miller",
            "email":"your-email@your-domain.com"
        },
        {
            "id":"1012",
            "firstName":"Mac",
            "lastName":"Mason1",
            "email":"your-email@your-domain.com"
        }
    ]
}

5.4. Get User by Id

5.4.通过标识获取用户

Below is the get method response handler which returns a User with the given id:

下面是get方法的响应处理程序,它返回一个具有给定idUser

get("/users/:id", (request, response) -> {
    response.type("application/json");
    return new Gson().toJson(
      new StandardResponse(StatusResponse.SUCCESS,new Gson()
        .toJsonTree(userService.getUser(request.params(":id")))));
});

Now let’s test the route:

现在让我们来测试一下这个路线。

Request:

请求:

GET http://localhost:4567/users/1012

Response:

答复:

{
    "status":"SUCCESS",
    "data":{
        "id":"1012",
        "firstName":"Mac",
        "lastName":"Mason1",
        "email":"your-email@your-domain.com"
    }
}

5.5. Edit a User

5.5.编辑一个用户

Below is the put method response handler, which edits the user having the id supplied in the route pattern:

下面是put method 响应处理程序,它编辑具有路由模式中提供的id的用户。

put("/users/:id", (request, response) -> {
    response.type("application/json");
    User toEdit = new Gson().fromJson(request.body(), User.class);
    User editedUser = userService.editUser(toEdit);
            
    if (editedUser != null) {
        return new Gson().toJson(
          new StandardResponse(StatusResponse.SUCCESS,new Gson()
            .toJsonTree(editedUser)));
    } else {
        return new Gson().toJson(
          new StandardResponse(StatusResponse.ERROR,new Gson()
            .toJson("User not found or error in edit")));
    }
});

Note: In this example, the data are passed in the raw body of a POST request as a JSON object whose property names match the fields of the User object to be edited.

注意:在这个例子中,数据以JSON对象的形式在POST请求的原始体中传递,其属性名称与要编辑的User对象的字段相匹配。

Let’s test the route:

让我们测试一下这条路线。

Request:

请求:

PUT http://localhost:4567/users/1012
{
    "lastName": "Mason"
}

Response:

答复:

{
    "status":"SUCCESS",
    "data":{
        "id":"1012",
        "firstName":"Mac",
        "lastName":"Mason",
        "email":"your-email@your-domain.com"
    }
}

5.6. Delete a User

5.6.删除一个用户

Below is the delete method response handler, which will delete the User with the given id:

下面是delete方法的响应处理程序,它将删除具有给定idUser

delete("/users/:id", (request, response) -> {
    response.type("application/json");
    userService.deleteUser(request.params(":id"));
    return new Gson().toJson(
      new StandardResponse(StatusResponse.SUCCESS, "user deleted"));
});

Now, let’s test the route:

现在,让我们测试一下这条路线。

Request:

请求:

DELETE http://localhost:4567/users/1012

Response:

答复:

{
    "status":"SUCCESS",
    "message":"user deleted"
}

5.7. Check if User Exists

5.7.检查用户是否存在

The options method is a good choice for conditional checking. Below is the options method response handler which will check whether a User with the given id exists:

options方法是条件检查的一个好选择。下面是options方法的响应处理程序,它将检查是否存在一个给定idUser

options("/users/:id", (request, response) -> {
    response.type("application/json");
    return new Gson().toJson(
      new StandardResponse(StatusResponse.SUCCESS, 
        (userService.userExist(
          request.params(":id"))) ? "User exists" : "User does not exists" ));
});

Now let’s test the route:

现在让我们来测试一下这个路线。

Request:

请求:

OPTIONS http://localhost:4567/users/1012

Response:

答复:

{
    "status":"SUCCESS",
    "message":"User exists"
}

6. Conclusion

6.结论

In this article, we had a quick introduction to the Spark framework for rapid web development.

在这篇文章中,我们对用于快速网络开发的Spark框架进行了快速介绍。

This framework is mainly promoted for generating microservices in Java. Node.js developers with Java knowledge who want to leverage libraries built on JVM libraries should feel at home using this framework.

这个框架主要是为在Java中生成微服务而推广的。具有Java知识的Node.js开发人员如果想利用建立在JVM库上的库,应该可以放心地使用这个框架。

And as always, you can find all the sources for this tutorial in the Github project.

和往常一样,你可以在Github项目中找到本教程的所有来源。