使用Spark构建简单的RESTful API

免责声明 :这篇文章是关于名为Spark的Java微型Web框架的,而不是关于数据处理引擎Apache Spark的 。

在此博客文章中,我们将看到如何使用Spark构建简单的Web服务。 如免责声明中所述,Spark是受Ruby框架Sinatra启发的Java微型Web框架。 Spark的目的是简化操作,仅提供最少的功能集。 但是,它提供了用几行Java代码构建Web应用程序所需的一切。


入门

假设我们有一个带有一些属性的简单域类和一个提供一些基本CRUD功能的服务:

public class User {private String id;private String name;private String email;// getter/setter
}
public class UserService {// returns a list of all userspublic List<User> getAllUsers() { .. }// returns a single user by idpublic User getUser(String id) { .. }// creates a new userpublic User createUser(String name, String email) { .. }// updates an existing userpublic User updateUser(String id, String name, String email) { .. }
}

现在,我们希望将UserService的功能公开为RESTful API(为简单起见,我们将跳过REST的超媒体部分)。 为了访问,创建和更新用户对象,我们要使用以下URL模式:

得到 /用户 获取所有用户的列表
得到 / users / <id> 获取特定用户
开机自检 /用户 创建一个新用户
/ users / <id> 更新用户

返回的数据应为JSON格式。

要开始使用Spark,我们需要以下Maven依赖项:

<dependency><groupId>com.sparkjava</groupId><artifactId>spark-core</artifactId><version>2.0.0</version>
</dependency>
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.7</version>
</dependency>

Spark使用SLF4J进行日志记录,因此我们需要SLF4J活页夹才能查看日志和错误消息。 在此示例中,我们为此目的使用slf4j-simple依赖项。 但是,您也可以使用Log4j或您喜欢的任何其他绑定程序。 在类路径中使用slf4j-simple足以在控制台中查看日志输出。

我们还将使用GSON生成JSON输出,并使用JUnit编写简单的集成测试。 您可以在完整的pom.xml中找到这些依赖项。

返回所有用户

现在该创建一个负责处理传入请求的类了。 我们首先实现GET / users请求,该请求应返回所有用户的列表。

import static spark.Spark.*;public class UserController {public UserController(final UserService userService) {get("/users", new Route() {@Overridepublic Object handle(Request request, Response response) {// process requestreturn userService.getAllUsers();}});// more routes}
}

注意第一行中的spark.Spark。*的静态导入。 这使我们可以访问各种静态方法,包括get(),post(),put()等。 在构造函数中,get()方法用于注册一个Route,该Route侦听/ users上的GET请求。 路由负责处理请求。 每当发出GET / users请求时,都会调用handle()方法。 在handle()内部,我们返回一个应发送给客户端的对象(在本例中为所有用户的列表)。

Spark从Java 8 Lambda表达式中受益匪浅。 Route是一个功能接口(仅包含一种方法),因此我们可以使用Java 8 Lambda表达式来实现它。 使用Lambda表达式,上面的Route定义如下所示:

get("/users", (req, res) -> userService.getAllUsers());

要启动该应用程序,我们必须创建一个简单的main()方法。 在main()内部,我们创建服务的实例,并将其传递给我们新创建的UserController:

public class Main {public static void main(String[] args) {new UserController(new UserService());}
}

如果现在运行main(),Spark将启动一个侦听端口4567的嵌入式Jetty服务器。我们可以通过启动GET http:// localhost:4567 / users请求来测试我们的第一个路由。

如果服务返回包含两个用户对象的列表,则响应主体可能如下所示:

[com.mscharhag.sparkdemo.User@449c23fd, com.mscharhag.sparkdemo.User@437b26fe]

显然,这不是我们想要的回应。

Spark使用名为ResponseTransformer的接口将路由返回的对象转换为实际的HTTP响应。
ReponseTransformer看起来像这样:

public interface ResponseTransformer {String render(Object model) throws Exception;
}

ResponseTransformer具有一个方法,该方法接受一个对象并返回此对象的String表示形式。 ResponseTransformer的默认实现只是在传递的对象上调用toString()(它创建如上所示的输出)。

由于我们要返回JSON,因此我们必须创建一个ResponseTransformer,将传递的对象转换为JSON。 为此,我们使用带有两个静态方法的小型JsonUtil类:

public class JsonUtil {public static String toJson(Object object) {return new Gson().toJson(object);}public static ResponseTransformer json() {return JsonUtil::toJson;}
}

toJson()是使用GSON将对象转换为JSON的通用方法。 第二种方法利用Java 8方法引用来返回ResponseTransformer实例。 ResponseTransformer还是一个功能接口,因此可以通过提供适当的方法实现(toJson())来满足它。 因此,每当调用json()时,我们都会获得一个新的ResponseTransformer,它利用了我们的toJson()方法。

在我们的UserController中,我们可以将ResponseTransformer作为第三个参数传递给Spark的get()方法:

import static com.mscharhag.sparkdemo.JsonUtil.*;public class UserController {public UserController(final UserService userService) {get("/users", (req, res) -> userService.getAllUsers(), json());...}
}

再次注意第一行中JsonUtil。*的静态导入。 这使我们可以选择仅通过调用json()来创建新的ResponseTransformer。

现在,我们的响应如下所示:

[{"id": "1866d959-4a52-4409-afc8-4f09896f38b2","name": "john","email": "john@foobar.com"
},{"id": "90d965ad-5bdf-455d-9808-c38b72a5181a","name": "anna","email": "anna@foobar.com"
}]

我们还有一个小问题。 返回的响应带有错误的Content-Type 。 为了解决这个问题,我们可以注册一个设置JSON Content-Type的Filter:

after((req, res) -> {res.type("application/json");
});

过滤器还是一个功能接口,因此可以通过一个简短的Lambda表达式实现。 在我们的Route处理完请求后,过滤器会将每个响应的Content-Type更改为application / json。 我们还可以使用before()代替after()来注册过滤器。 然后,在路由处理请求之前,将调用过滤器。

GET / users请求现在应该可以工作了!

返回特定用户

要返回特定用户,我们只需在UserController中创建一条新路由:

get("/users/:id", (req, res) -> {String id = req.params(":id");User user = userService.getUser(id);if (user != null) {return user;}res.status(400);return new ResponseError("No user with id '%s' found", id);
}, json());

使用req.params(“:id”),我们可以从URL获取:id路径参数。 我们将此参数传递给我们的服务以获取相应的用户对象。 如果未找到具有传递ID的用户,则假定服务返回null。 在这种情况下,我们将HTTP状态代码更改为400(错误请求)并返回一个错误对象。

ResponseError是一个小的帮助程序类,我们用于将错误消息和异常转换为JSON。 看起来像这样:

public class ResponseError {private String message;public ResponseError(String message, String... args) {this.message = String.format(message, args);}public ResponseError(Exception e) {this.message = e.getMessage();}public String getMessage() {return this.message;}
}

现在,我们可以使用以下请求查询单个用户:

GET / users / 5f45a4ff-35a7-47e8-b731-4339c84962be

如果存在具有此ID的用户,我们将收到如下所示的响应:

{"id": "5f45a4ff-35a7-47e8-b731-4339c84962be","name": "john","email": "john@foobar.com"
}

如果我们使用无效的用户ID,将创建ResponseError对象并将其转换为JSON。 在这种情况下,响应如下所示:

{"message": "No user with id 'foo' found"
}

创建和更新用户

创建和更新用户非常容易。 就像返回所有用户的列表一样,这是通过单个服务调用完成的:

post("/users", (req, res) -> userService.createUser(req.queryParams("name"),req.queryParams("email")
), json());put("/users/:id", (req, res) -> userService.updateUser(req.params(":id"),req.queryParams("name"),req.queryParams("email")
), json());

要为HTTP POST或PUT请求注册路由,我们只需使用Spark的静态post()和put()方法。 在Route内部,我们可以使用req.queryParams()访问HTTP POST参数。

为了简单起见(并显示另一个Spark功能),我们不在路由内进行任何验证。 相反,我们假定如果传入无效值,则服务将引发IllegalArgumentException。

Spark为我们提供了注册ExceptionHandlers的选项。 如果在处理路由时引发Exception,则将调用ExceptionHandler。 ExceptionHandler是我们可以使用Java 8 Lambda表达式实现的另一个单一方法接口:

exception(IllegalArgumentException.class, (e, req, res) -> {res.status(400);res.body(toJson(new ResponseError(e)));
});

在这里,我们创建一个ExceptionHandler,如果抛出IllegalArgumentException则调用它。 捕获的Exception对象作为第一个参数传递。 我们将响应代码设置为400,并在响应正文中添加一条错误消息。

如果当email参数为空时服务抛出IllegalArgumentException,我们可能会收到如下响应:

{"message": "Parameter 'email' cannot be empty"
}

控制器的完整资源可以在这里找到。

测试中

由于Spark的简单性质,因此为示例应用程序编写集成测试非常容易。

让我们从基本的JUnit测试设置开始:

public class UserControllerIntegrationTest {@BeforeClasspublic static void beforeClass() {Main.main(null);}@AfterClasspublic static void afterClass() {Spark.stop();}...
}

在beforeClass()中,我们通过简单地运行main()方法来启动应用程序。 所有测试完成后,我们调用Spark.stop()。 这将停止运行我们的应用程序的嵌入式服务器。

之后,我们可以在测试方法中发送HTTP请求,并验证我们的应用程序返回了正确的响应。 一个发送创建新用户请求的简单测试如下所示:

@Test
public void aNewUserShouldBeCreated() {TestResponse res = request("POST", "/users?name=john&email=john@foobar.com");Map<String, String> json = res.json();assertEquals(200, res.status);assertEquals("john", json.get("name"));assertEquals("john@foobar.com", json.get("email"));assertNotNull(json.get("id"));
}

request()和TestResponse是两个小型的自制测试实用程序。 request()将HTTP请求发送到传递的URL,并返回TestResponse实例。 TestResponse只是一些HTTP响应数据的小包装。 request()和TestResponse的源包含在GitHub上的完整测试类中 。

结论

与其他Web框架相比,Spark仅提供了少量功能。 但是,它是如此简单,您可以在几分钟之内构建小型Web应用程序(即使您以前从未使用过Spark)。 如果您想研究Spark,则应该清楚地使用Java 8,它减少了您必须编写的代码量。

  • 您可以在GitHub上找到示例项目的完整源代码。

翻译自: https://www.javacodegeeks.com/2014/06/building-a-simple-restful-api-with-spark.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/363121.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cf1206解题报告

目录 cf1206解题报告ABCDE&#xff0c;Fcf1206解题报告 A 模拟 #include <bits/stdc.h> using namespace std; int n,m,a[1010],b[12910]; int dsr[1111]; int main() {scanf("%d",&n);for(int i1;i<n;i) scanf("%d",&a[i]),dsr[a[i]]1;s…

canvas时钟

<!DOCTYPE html><html><head lang"en"><meta charset"UTF-8"><title></title></head><body><canvas id"canvas" width"500" height"500">您的浏览器不支持&#xff…

第七周

这个作业属于哪个课程C语言程序设计 &#xff08;第三版&#xff09;这个作业要求在哪里2019春季第七周作业我的课程目标学习指针的运用这个作业在哪个具体方面帮助我实现目标这个作业让我知道了指针实用性参考文献无一、2019春第七周作业&#xff08;基础题&#xff09; 7-2 自…

很久没来了,嘿嘿 问候一下大家,O(∩_∩)O哈哈~

很久没来了&#xff0c;嘿嘿。发现上次发帖正好是一个月以前哦&#xff0c;嘿嘿。最近职位火热招聘&#xff0c;有意向的快联系哦&#xff0c;嘿嘿1. BI Technical PM BI Technical PM Job Location: Redmond Oversea education or working background. 2 …

Java EE 8 –为更多设备提供更多应用程序

如果我不喜欢夏天的一件事&#xff0c;那就是事实是没有太多要分享或谈论的新闻。 谁决定将Java Day Tokyo置于这一年的无聊时间里&#xff0c;谁干得不错&#xff0c;就给我一个机会撰写有关新的和即将到来的Java EE 8规范的博客文章&#xff0c;其中包含了更多的思想和建议。…

全局预处理与执行,作用域与作用域链

一.全局预处理 <!DOCTYPE html><html><head lang"en"><meta charset"UTF-8"><title></title></head><body><p>全局预处理&#xff1a;首先会创建一个词法环境(Lexical Environment),然后扫面全局里…

2090. 「ZJOI2016」旅行者 分治,最短路

2090. 「ZJOI2016」旅行者 链接 loj 思路 \((l,mid)(mid1,r)\).考虑跨过mid的贡献。 假设选的中间那条线的点为gzy,贡献为\(dis(x,gzy)dis(gzy,y)\) 那就计算n遍最短路,一次分治为\(n^2mlog{nm}\) 设Sn*m.矩阵的长度是不定的&#xff0c;每次取最长的边进行分治是最好的&#x…

Xshell连接Linux慢问题解决办法

由于各种原因&#xff0c;经常更换网络环境&#xff0c;然后发现&#xff0c;每次更换网络环境后&#xff0c;xshell连接虚拟机的rhel或者CentOS都几乎是龟速.... 今天专门查了一下解决方案&#xff1a; 原来是ssh的服务端在连接时会自动检测dns环境是否一致导致的&#xff0c;…

Gradle入门:依赖管理

即使不是没有可能&#xff0c;创建没有任何外部依赖关系的现实应用程序也是一项挑战。 这就是为什么依赖性管理是每个软件项目中至关重要的部分的原因。 这篇博客文章描述了我们如何使用Gradle管理项目的依赖关系。 我们将学习配置已使用的存储库和所需的依赖项。 我们还将通过…

牛客NOIP暑期七天营-提高组1

牛客NOIP暑期七天营-提高组1 链接 A 边权可为0就排序建一条链子。 但是边权不为0 除了第一个有0的不行。 x连向上一个比他小的数。 期间判断有无解。 #include <bits/stdc.h> #define ll long long using namespace std; const int _2e57; int read() {int x0,f1;char sg…

BZOJ.4009.[HNOI2015]接水果(整体二分 扫描线)

LOJBZOJ洛谷 又是一个三OJ rank1&#xff01;w \(Description\) &#xff08;还是感觉&#xff0c;为啥非要出那种题目背景啊-直接说不好么&#xff09; 给定一棵树和一个路径集合&#xff08;每条路径有一个权值&#xff09;。\(Q\)次询问&#xff0c;每次询问给定一条路径&am…

HTML5常用标签及特殊字符表

*http://html5doctor.com/nav*http://html5doctor.com/article*http://html5doctor.com/section*http://html5doctor.com/asidehttp://html5doctor.com/divhttp://html5doctor.com/figurehttp://html5doctor.com/outlinehttp://html5doctor.com/semantics p 和 span 的理解 p标…

【转载】使用Imaging组件加载GIF动画

Mobil手机加载GIF动态图像的方法有两种&#xff0c;一个就是使用GIF89a标准算法&#xff0c;另一个就是使用SDK自带的Imaging组件&#xff0c;这两种方法是很典型的手机图像处理技术的实践。使用Imaging组件加载GIF比使用标准算法处理高效的多&#xff0c;特别是在处理真彩GIF动…

在Java中获取素数的无限列表

一个常见的问题是确定数字的素因式分解。 蛮力方法是审判部门&#xff08; 维基百科 &#xff0c; 可汗学院 &#xff09;&#xff0c;但是如果必须考虑多个数字&#xff0c;这需要大量的浪费工作。 一种广泛使用的解决方案是Eratosthenes筛&#xff08; 维基百科 &#xff0c…

CF888G XOR-MST trie,贪心

CF888G XOR-MST 链接 CF888G 思路 trie上贪心&#xff0c;先左右两边连边&#xff0c;再用一条边的代价连起左右两颗树。因为内部的边一定比跨两棵树的边权笑&#xff0c;显然是对的。 代码自己瞎yy的。启发式合并 代码 #include <bits/stdc.h> #define ll long long usi…

【处理手记】Configuration system failed to initialize异常的另类原因

有个c#程序在某台电脑上&#xff0c;执行某个操作时&#xff0c;总是会报如图错误&#xff1a; 度娘一番&#xff0c;发现市面上常见的原因是配置文件中的特定节点的位置不对&#xff0c;或者配置文件损坏等等&#xff0c;而这个程序根本没有使用内置的配置文件方案&#xff0c…

学习《Building Applications with FME Objects》 之四 从数据集读取要素

FMEOReader可以访问任何支持格式的数据。 FMEOReader返回两类要素&#xff1a;schema&#xff08;模式&#xff09;要素和数据要素&#xff0c;模式要素用于描述数据集模型。每种支持的格式都有一个模式&#xff0c;一个模式要素是一类要素的数据模型&#xff0c;模式要素描述属…

使用Zapier将应用程序与Neo4j集成

最近&#xff0c;我被带往Zapier &#xff0c;以便在系统之间完成一些轻量级的集成&#xff0c;以快速地进行概念验证。 最初是持怀疑态度的&#xff0c;我发现它确实可以节省时间&#xff0c;并将您从未集成过的系统所有部分捆绑在一起。 而且&#xff0c;这是人们集成他们使…

手机闪屏

表示&#xff0c;本人买了一个华为手机&#xff0c;采用三四个月&#xff0c;就开始闪屏了&#xff0c;手机一划屏就闪&#xff0c;开始的时候表示不能理解&#xff0c;不是手机一般是1年才换吗&#xff1f;突然遇到我也是纠结。 于是乎&#xff0c;浏览了些百度上面的解决方式…

工程能力之C4模型

概述 刚在InfoQ上看到一篇介绍C4Model的文章,觉得这个模型设计的很赞,很有指导意义,做个简单的记录. Why,为什么需要架构图? ThoughtWorks中国 文章中有几句话我觉得很有道理,这里直接摘抄. “纸上的不是架构&#xff0c;每个人脑子里的才是” ; “那些精妙的方案之所以落不了…