1. 概述
Java 生态中不乏优秀的 Web 框架,比如我们耳熟能详的 Spring、Play 和 Grails。但要说完全遵循不可变性和纯面向对象设计的,几乎没有。
本文带你了解一个另类框架 —— Takes。我们将用它搭建一个简单的 Web 应用,涵盖路由、请求/响应处理、异常处理以及单元测试等核心功能。
踩坑提示:Takes 的设计理念非常“函数式”和“纯 OOP”,如果你习惯了 Spring 的注解和配置,刚上手可能会觉得“反人类”,但理解其哲学后,代码会变得异常清晰。
2. Takes 框架核心理念
Takes 是一个基于 Java 8 的不可变 Web 框架,它的设计非常“洁癖”:
✅ 完全避免使用 null
✅ 禁用 public static
方法
✅ 不支持可变类(mutable classes)
✅ 禁止类型强制转换(casting)和反射(reflection)
正因为如此,Takes 才敢宣称自己是一个“真正面向对象”的框架。它不依赖任何 XML 或 YAML 配置文件,开箱即用,内置了 JSON/XML 响应、模板渲染等常用功能。
简单粗暴地说:一切皆对象,一切皆不可变。
3. 环境搭建
首先,在 pom.xml
中引入 Takes 依赖:
<dependency>
<groupId>org.takes</groupId>
<artifactId>takes</artifactId>
<version>1.19</version>
</dependency>
接着,创建一个实现 Take
接口的类:
public class TakesHelloWorld implements Take {
@Override
public Response act(Request req) {
return new RsText("Hello, world!");
}
}
Take
接口是框架的核心抽象,每个 Take
都是一个请求处理器,通过 act
方法接收请求并返回响应。
这里我们使用 RsText
类,将纯文本 "Hello, world!" 作为响应内容。
然后,创建启动类 TakesApp
:
public class TakesApp {
public static void main(String... args) {
new FtBasic(new TakesHelloWorld()).start(Exit.NEVER);
}
}
FtBasic
是 Front
接口的一个基础实现,负责启动内嵌的 Web 服务,并将请求转发给指定的 Take
。
⚠️ Takes 使用 ServerSocket
自研了一套无状态 Web 服务,默认监听 80 端口。我们可以在代码中指定端口:
new FtBasic(new TakesHelloWorld(), 6060).start(Exit.NEVER);
或者通过命令行参数传入:
--port=6060
最后,用 Maven 编译打包:
mvn clean package
就可以在 IDE 中直接运行 TakesApp
了。
4. 运行方式
除了在 IDE 中运行,也可以作为独立服务启动。
4.1. 命令行运行
先编译:
javac -cp "takes.jar:." com.baeldung.takes.*
再运行:
java -cp "takes.jar:." com.baeldung.takes.TakesApp --port=6060
4.2. 使用 Maven 插件
推荐使用 exec-maven-plugin
,方便集成:
<profiles>
<profile>
<id>reload</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>start-server</id>
<phase>pre-integration-test</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.baeldung.takes.TakesApp</mainClass>
<cleanupDaemonThreads>false</cleanupDaemonThreads>
<arguments>
<argument>--port=${port}</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
启动命令:
mvn clean integration-test -Preload -Dport=6060
这样就能在 CI/CD 环境中轻松集成。
5. 路由配置
Takes 使用 TkFork
类实现请求路由,通过匹配规则将请求分发给不同的 Take
。
示例:为根路径 /
和 /contact
添加路由:
public static void main(String... args) {
new FtBasic(
new TkFork(
new FkRegex("/", new TakesHelloWorld()),
new FkRegex("/contact", new TakesContact())
), 6060
).start(Exit.NEVER);
}
其中 FkRegex
使用正则表达式匹配请求路径,逻辑清晰,扩展性强。
6. 请求处理
Takes 在 org.takes.rq
包中提供了丰富的装饰器类来处理 HTTP 请求。
常用方式如下:
获取请求方法:
String requestMethod = new RqMethod.Base(req).method();
获取请求头:
Iterable<String> requestHeaders = new RqHeaders.Base(req).head();
获取请求体:
String body = new RqPrint(req).printBody();
获取表单参数:
String username = new RqFormSmart(req).single("username");
这些装饰器都遵循“装饰器模式”,可以链式调用,组合使用,非常灵活。
7. 响应处理
响应处理在 org.takes.rs
包中,同样采用装饰器模式。
核心装饰器包括:
设置状态码:
Response resp = new RsWithStatus(200);
验证:
assertEquals("[HTTP/1.1 200 OK], ", resp.head().toString());
设置 Content-Type:
Response resp = new RsWithType(new RsEmpty(), "text/html");
设置响应体:
new RsWithBody("Contact us at https://www.baeldung.com")
组合使用示例:构建一个完整的 HTML 响应:
public class TakesContact implements Take {
@Override
public Response act(Request req) throws IOException {
return new RsWithStatus(
new RsWithType(
new RsWithBody("Contact us at https://www.baeldung.com"),
"text/html"), 200);
}
}
- 返回 JSON:
@Override public Response act(Request req) { JsonStructure json = Json.createObjectBuilder() .add("id", rs.getInt("id")) .add("user", rs.getString("user")) .build(); return new RsJson(json); }
8. 异常处理
Takes 通过 Fallback
接口处理异常场景,并提供多种实现。
处理 404:
new FtBasic( new TkFallback( new TkFork( new FkRegex("/", new TakesHelloWorld()), // ... ), new FbStatus(404, new RsText("Page Not Found")) ), 6060 ).start(Exit.NEVER);
组合多个 fallback(如 404 和 405):
new TkFallback( new TkFork( /* routes */ ), new FbChain( new FbStatus(404, new RsText("Page Not Found")), new FbStatus(405, new RsText("Method Not Allowed")) ) ), 6060 ).start(Exit.NEVER);
自定义异常处理逻辑:
new FbChain( new FbStatus(404, new RsText("Page Not Found")), new FbStatus(405, new RsText("Method Not Allowed")), new Fallback() { @Override public Opt<Response> route(RqFallback req) { return new Opt.Single<>(new RsText(req.throwable().getMessage())); } } )
这种设计将错误处理也“对象化”,保持了整体设计的一致性。
9. 模板集成
Takes 支持与 Apache Velocity 集成,实现动态页面渲染。
先引入依赖:
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
然后使用 RsVelocity
渲染模板:
内联模板:
public class TakesIndex implements Take { @Override public Response act(Request req) throws IOException { return new RsHtml( new RsVelocity("${username}", new RsVelocity.Pair("username", "Baeldung")) ); } }
使用外部模板文件:
new RsVelocity( this.getClass().getResource("/templates/index.vm"), new RsVelocity.Pair("username", username) )
最终用 RsHtml
包装返回 HTML 响应。
10. 单元测试
Takes 提供 RqFake
类用于创建伪造请求,方便单元测试。
示例:测试 TakesContact
:
String resp = new RsPrint(new TakesContact().act(new RqFake())).printBody();
assertEquals("Contact us at https://www.baeldung.com", resp);
RsPrint
用于提取响应体内容,测试轻量高效。
11. 集成测试
使用 FtRemote
可启动一个临时服务器,在随机端口运行 Take
,适合集成测试。
示例:
new FtRemote(new TakesContact()).exec(
new FtRemote.Script() {
@Override
public void exec(URI home) throws IOException {
HttpClient client = HttpClientBuilder.create().build();
HttpResponse response = client.execute(new HttpGet(home));
int statusCode = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
String result = EntityUtils.toString(entity);
assertEquals(200, statusCode);
assertEquals("Contact us at https://www.baeldung.com", result);
}
});
这里使用 Apache HttpClient 发起真实请求,验证端到端行为。
12. 总结
本文带你快速上手 Takes 框架,从环境搭建到路由、请求/响应处理、异常处理,再到单元和集成测试,覆盖了核心开发流程。
Takes 的设计哲学非常独特:不可变 + 纯对象 + 无配置。虽然学习曲线较陡,但一旦适应,代码的可读性和可测试性会大幅提升。
所有示例代码已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/web-modules/java-takes