希望今年您对Java的热情很高! 今天,我们将研究一个清新,简单,美观且实用的框架,以Java编写REST应用程序。 它将非常简单,甚至根本不会看起来像Java。
我们将研究Spark Web框架。 不,它与Apache Spark不相关。 是的,很遗憾,他们使用相同的名字。
我认为理解该框架的最佳方法是构建一个简单的应用程序,因此我们将构建一个简单的服务来执行数学运算。
我们可以这样使用它:
请注意,该服务正在本地主机上的端口4567上运行,并且请求的资源为“ / 10 / add / 8”。
使用Gradle设置项目(
apply plugin: "java"
apply plugin: "idea"
sourceCompatibility = 1.8
repositories {
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 omitted
class 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);
@Override
public 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);
}
@Override
public 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 {
@Test
public 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);
}
@Override
public 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 {
@Test
public 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());
}
@Test
public 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