spark rest
我希望您今年Java来了! 今天,我们将研究一个清新,简单,美观且实用的框架,以Java编写REST应用程序。 它将非常简单,甚至根本不会看起来像Java。
我们将研究Spark Web框架。 不,它与Apache Spark不相关。 是的,很遗憾,他们使用相同的名字。
我认为理解该框架的最佳方法是构建一个简单的应用程序,因此我们将构建一个简单的服务来执行数学运算。
我们可以这样使用它:
请注意,该服务正在本地主机上的端口4567上运行,并且请求的资源为“ / 10 / add / 8”。
使用Gradle设置项目(
apply plugin: "java"
apply plugin: "idea"sourceCompatibility = 1.8repositories {mavenCentral()maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }maven { url "https://oss.sonatype.org/content/repositories/releases/" }
}dependencies {compile "com.javaslang:javaslang:2.0.0-RC1"compile "com.sparkjava:spark-core:2.3"compile "com.google.guava:guava:19.0-rc2"compile "org.projectlombok:lombok:1.16.6"testCompile group: 'junit', name: 'junit', version: '4.+'
}task launch(type:JavaExec) {main = "me.tomassetti.javaadvent.SparkService"classpath = sourceSets.main.runtimeClasspath
}
现在我们可以运行:
- 。 / gradlew想法来生成IntelliJ IDEA项目
- 。 / gradlew测试以运行测试
- 。 / gradlew组装以构建项目
- 。 / gradlew启动以启动我们的服务
现在,让我们认识Spark
您认为我们可以编写一个功能齐全的Web服务,用不到25行Java代码执行基本的数学运算吗? 没门? 好吧,再想想:
// imports omittedclass Calculator implements Route {private Map<String, Function2<Long, Long, Long>> functions = ImmutableMap.of("add", (a, b) -> a + b,"mul", (a, b) -> a * b,"div", (a, b) -> a / b,"sub", (a, b) -> a - b);@Overridepublic Object handle(Request request, Response response) throws Exception {long left = Long.parseLong(request.params(":left"));String operatorName = request.params(":operator");long right = Long.parseLong(request.params(":right"));return functions.get(operatorName).apply(left, right);}
}public class SparkService {public static void main(String[] args) {get("/:left/:operator/:right", new Calculator());}
}
在我们的主要方法中,我们只是说,当我们得到一个包含三个部分(用斜杠分隔)的请求时,我们应该使用计算器路线,这是我们唯一的路线。 Spark中的路由是接收请求,处理请求并产生响应的单元。
我们的计算器就是神奇的地方。 它在请求中查找参数“ left”,“ operatorName”和“ right”。 左右被解析为长值,而operatorName用于查找操作。 对于每个操作,我们都有一个函数(Function2 <Long,Long>),然后将其应用于我们的值(左和右)。 酷吧?
Function2是来自Javaslang项目的接口。
您现在可以启动该服务( ./gradlew启动,还记得吗?)并进行播放。
我上次检查Java时比较冗长,冗长,缓慢……好吧,它现在正在恢复。
好的,但是测试呢?
因此Java实际上可以非常简洁,作为软件工程师,我会庆祝一两分钟,但是不久之后我就开始感到不安……这些东西没有经过测试! 更糟糕的是,它看起来根本无法测试。 逻辑在我们的计算器类中,但是它接受一个Request并生成一个Response。 我不想实例化一个请求只是为了检查我的计算器是否按预期工作。 让我们重构一下:
class TestableCalculator implements Route {private Map<String, Function2<Long, Long, Long>> functions = ImmutableMap.of("add", (a, b) -> a + b,"mul", (a, b) -> a * b,"div", (a, b) -> a / b,"sub", (a, b) -> a - b);public long calculate(String operatorName, long left, long right) {return functions.get(operatorName).apply(left, right);}@Overridepublic Object handle(Request request, Response response) throws Exception {long left = Long.parseLong(request.params(":left"));String operatorName = request.params(":operator");long right = Long.parseLong(request.params(":right"));return calculate(operatorName, left, right);}
}
我们只是将管道(从请求中取出值)与逻辑分开,并将其放入自己的方法中: calculate 。 现在我们可以测试计算了。
public class TestableLogicCalculatorTest {@Testpublic void testLogic() {assertEquals(10, new TestableCalculator().calculate("add", 3, 7));assertEquals(-6, new TestableCalculator().calculate("sub", 7, 13));assertEquals(3, new TestableCalculator().calculate("mul", 3, 1));assertEquals(0, new TestableCalculator().calculate("div", 0, 7));}@Test(expected = ArithmeticException.class)public void testInvalidInputs() {assertEquals(0, new TestableCalculator().calculate("div", 0, 0));}}
我现在感觉好多了:我们的测试证明了这些东西是行得通的。 当然,如果我们尝试除以零,它将引发异常,但是事实就是这样。
但是,这对用户意味着什么?
这意味着:500。如果用户尝试使用一个不存在的操作,会发生什么?
如果值不是正确的数字怎么办?
好的,这似乎不是很专业。 让我们修复它。
错误处理,功能风格
要解决这两种情况,我们只需要使用Spark的一项功能即可:我们可以将特定的异常与特定的路线进行匹配。 我们的路由将产生有意义的HTTP状态代码和正确的消息。
public class SparkService {public static void main(String[] args) {exception(NumberFormatException.class, (e, req, res) -> res.status(404));exception(ArithmeticException.class, (e, req, res) -> {res.status(400);res.body("This does not seem like a good idea");});get("/:left/:operator/:right", new ReallyTestableCalculator());}
}
我们仍然要处理不存在的操作的情况,这是我们将在ReallyTestableCalculator中进行的操作 。
为此,我们将使用典型的函数模式:我们将返回Either 。 Either是可以具有left或right值的集合。 左侧通常表示有关错误的某种信息,例如错误代码或错误消息。 如果没有任何问题,Either都将包含一个正确的值,可能是各种各样的东西。 在本例中,如果无法执行操作,则将返回错误(我们定义的类),否则将以Long形式返回操作的结果。 因此,我们将返回Either <Error,Long>。
package me.tomassetti.javaadvent.calculators;import javaslang.Function2;
import javaslang.Tuple2;
import javaslang.collection.Map;
import javaslang.collection.HashMap;
import javaslang.control.Either;
import spark.Request;
import spark.Response;
import spark.Route;public class ReallyTestableCalculator implements Route {private static final int NOT_FOUND = 404;private Map<String, Function2<Long, Long, Long>> functions = HashMap.ofAll(new Tuple2<>("add", (a, b) -> a + b),new Tuple2<>("mul", (a, b) -> a * b),new Tuple2<>("div", (a, b) -> a / b),new Tuple2<>("sub", (a, b) -> a - b));public Either<Error, Long> calculate(String operatorName, long left, long right) {Either<Error, Long> unknownOp = Either.<Error, Long>left(new Error(NOT_FOUND, "Unknown math operation"));return functions.get(operatorName).map(f -> Either.<Error, Long>right(f.apply(left, right))).orElse(unknownOp);}@Overridepublic Object handle(Request request, Response response) throws Exception {long left = Long.parseLong(request.params(":left"));String operatorName = request.params(":operator");long right = Long.parseLong(request.params(":right"));Either<Error, Long> res = calculate(operatorName, left, right);if (res.isRight()) {return res.get();} else {response.status(res.left().get().getHttpCode());return null;}}
}
让我们测试一下:
package me.tomassetti.javaadvent;import javaslang.control.Either;
import me.tomassetti.javaadvent.calculators.ReallyTestableCalculator;
import org.junit.Test;import static org.junit.Assert.assertEquals;public class ReallyTestableLogicCalculatorTest {@Testpublic void testLogic() {assertEquals(Either.right(10L), new ReallyTestableCalculator().calculate("add", 3, 7));assertEquals(Either.right(-6L), new ReallyTestableCalculator().calculate("sub", 7, 13));assertEquals(Either.right(3L), new ReallyTestableCalculator().calculate("mul", 3, 1));assertEquals(Either.right(0L), new ReallyTestableCalculator().calculate("div", 0, 7));}@Test(expected = ArithmeticException.class)public void testInvalidOperation() {Either<me.tomassetti.javaadvent.calculators.Error, Long> res = new ReallyTestableCalculator().calculate("div", 0, 0);assertEquals(true, res.isLeft());assertEquals(400, res.left().get().getHttpCode());}@Testpublic void testUnknownOperation() {Either<me.tomassetti.javaadvent.calculators.Error, Long> res = new ReallyTestableCalculator().calculate("foo", 0, 0);assertEquals(true, res.isLeft());assertEquals(404, res.left().get().getHttpCode());}}
结果
我们提供了可以轻松测试的服务。 它执行数学运算。 它支持四个基本操作,但可以轻松扩展以支持更多操作。 处理错误并使用适当的HTTP代码:400(错误输入)和404(未知操作或值)。
结论
当我第一次看到Java 8时,我对新功能感到满意,但并不十分兴奋。 但是,几个月后,我看到了基于这些新功能的新框架,它们有可能真正改变我们用Java编程的方式。 Spark和Javaslang之类的东西正在发挥作用。 我认为现在Java可以保持简单而可靠,同时变得更加敏捷和高效。
- 您可以在Spark教程网站或我的博客tomassetti.me上找到更多教程。
翻译自: https://www.javacodegeeks.com/2015/12/introduction-spark-next-rest-framework-java.html
spark rest