跳转到内容

Controller 与请求处理

Feat Cloud 的控制器写起来和 Spring Boot 很像:一个类加 @Controller,方法加 @RequestMapping。但底层实现不同:路由映射在编译期生成,运行时不再反射扫描。

这一章集中讲 HTTP 请求进入 Controller 的完整过程:

  • 先建立 URL 到方法的映射
  • 再处理查询参数、路径参数和 JSON 请求体
  • 最后补充少数需要直接接触运行时上下文的能力

简笔手绘卡通风格,一个 Controller 小屋接收 HTTP 请求箭头,门口写着 GET 图标,里面伸出一条响应纸带,表现 URL 映射到方法,黑色手绘线稿,柔和绿色和蓝色,900x383

下面是一个最小但完整的控制器。它只做一件事:访问 http://localhost:8080/users/hello 时返回一段文本。

UserController.java
package com.example.controller;
import tech.smartboot.feat.cloud.annotation.Controller;
import tech.smartboot.feat.cloud.annotation.RequestMapping;
import tech.smartboot.feat.cloud.annotation.RequestMethod;
@Controller("users")
public class UserController {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello() {
return "hello controller";
}
}

启动应用后,控制台会输出路由注册信息:

路由注册日志
Feat Router:
|-> /users/hello ==> UserController@hello
http://0.0.0.0:8080/

验证:

curl 验证
curl http://localhost:8080/users/hello
响应结果
hello controller

@Controllervalue 属性定义该控制器下所有方法的 URL 前缀。

控制器路径方法路径完整 URL
users/hello/users/hello
api/v1/users/api/v1/users
(空)/hello/hello

基础路径为空时,方法路径就是完整 URL。

Controller 最常见的输入来源有三种:查询字符串、路径变量和 JSON 请求体。

从这里开始,示例代码默认写在前面的 UserController 里,只展示当前接口方法和必要的局部类型。

简笔手绘卡通风格,一条 HTTP 路径像手绘路线图,路线上有 query 参数、path 参数、GET POST PUT DELETE 小路牌,最终到达 Controller,画面轻松但专业,900x383,无复杂文字

使用 @Param 注解从 URL 查询字符串提取参数值。

UserController.java
@RequestMapping(value = "/search", method = RequestMethod.GET)
public String search(@Param("keyword") String keyword,
@Param("page") Integer page) {
int currentPage = page == null ? 1 : page;
return "搜索: " + keyword + ", 页码: " + currentPage;
}
curl 请求
curl "http://localhost:8080/users/search?keyword=feat&page=1"

使用 @PathParam 注解从 URL 路径提取变量值。

UserController.java
@RequestMapping(value = "/:userId", method = RequestMethod.GET)
public String getUser(@PathParam("userId") String userId) {
return "用户ID: " + userId;
}
curl 请求
curl http://localhost:8080/users/12345

Feat Cloud 支持两种路径参数写法:

  • 冒号形式:/:userId
  • 花括号形式:/{userId}

两种写法功能等价,团队内部统一即可。如果团队来自前端路由或 Feat Core 路由体系,:id 会更自然;如果团队长期使用 Spring MVC,{id} 往往更容易迁移。

创建和更新资源时,参数通常不会继续塞在 URL 里,而是放进 JSON 请求体。Controller 方法可以直接声明一个自定义 POJO 参数,不需要额外注解。

User.java
public class User {
private String username;
private String password;
private String role;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
}
UserController.java
@RequestMapping(value = "/", method = RequestMethod.POST)
public String create(User user) {
return "created user: " + user.getUsername();
}

请求时带上 Content-Type: application/json

curl 请求
curl -X POST http://localhost:8080/users/ \
-H 'Content-Type: application/json' \
-d '{"username":"Bob","password":"123456","role":"admin"}'

进入正式业务接口后,常见做法是用路径表达资源位置,用请求体承载要创建或更新的数据。

RequestMethod 覆盖常见 HTTP 方法:

RequestMethod.java
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}

为同一路径配置不同方法:

UserController.java
@RequestMapping(value = "/", method = RequestMethod.GET)
public String list() {
return "list all users";
}
@RequestMapping(value = "/", method = RequestMethod.POST)
public String create(User user) {
return "created user: " + user.getUsername();
}
@RequestMapping(value = "/:id", method = RequestMethod.GET)
public String get(@PathParam("id") String id) {
return "get user: " + id;
}
@RequestMapping(value = "/:id", method = RequestMethod.PUT)
public String update(@PathParam("id") String id, User user) {
return "updated user " + id + " to " + user.getUsername();
}
@RequestMapping(value = "/:id", method = RequestMethod.DELETE)
public String delete(@PathParam("id") String id) {
return "deleted user: " + id;
}

大部分接口只需要 @RequestMapping@Param@PathParam 和请求体绑定。遇到下面这些场景时,再使用更靠近运行时的能力。

这部分不是日常接口的起点,更像工具箱:只有当普通参数绑定不够用时,再取对应能力。

简笔手绘卡通风格,一个 Controller 工具箱打开,里面有 Context 放大镜、Async 小沙漏、Gzip 压缩夹,表现高级能力按需取用,线稿清楚,少量强调色,900x383

需要直接读取请求头、设置响应头或访问完整 URI 时,注入 Context

UserController.java
import tech.smartboot.feat.router.Context;
@RequestMapping(value = "/context", method = RequestMethod.GET)
public String context(Context context) {
String uri = context.Request.getRequestURI();
String method = context.Request.getMethod();
return "URI: " + uri + ", Method: " + method;
}

当方法内部需要调用远程服务或执行耗时操作时,可以返回 AsyncResponse,避免阻塞 Feat 的工作线程。

UserController.java
import tech.smartboot.feat.cloud.AsyncResponse;
import tech.smartboot.feat.cloud.RestResult;
import java.util.concurrent.CompletableFuture;
@RequestMapping(value = "/async", method = RequestMethod.GET)
public AsyncResponse async() {
AsyncResponse response = new AsyncResponse();
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
response.complete(RestResult.ok("异步响应完成"));
} catch (InterruptedException e) {
response.complete(RestResult.fail(e.getMessage()));
}
});
return response;
}

生产环境请把 CompletableFuture.runAsync 换成自定义线程池,避免占满 common pool。

@Controller 上开启 Gzip,可以压缩返回的大段文本或 JSON。默认阈值是 256 字节,低于该值的响应不会压缩。

ApiController.java
import tech.smartboot.feat.cloud.annotation.Controller;
@Controller(
value = "api",
gzip = true,
gzipThreshold = 256
)
public class ApiController {
@RequestMapping(value = "/large", method = RequestMethod.GET)
public String largeResponse() {
return "这是一段很长的文本内容,重复多次使其超过 256 字节。".repeat(10);
}
}

可以用带 Accept-Encoding: gzip 的请求验证压缩效果:

Gzip 请求
curl -H "Accept-Encoding: gzip" http://localhost:8080/api/large --compressed

传统框架在运行时才去扫描类路径、解析注解、生成代理。Feat Cloud 在编译期就做完了这些事。

当你运行 mvn compile 时,feat-cloud-starter 中的注解处理器会:

  1. 扫描 @Controller
  2. 解析 @RequestMapping 的路径和方法
  3. 生成 CloudService 实现类
  4. 把路由注册到 Router

带来的直接好处:调用开销接近直接方法调用、内存占用更低、启动更快,也更容易构建 Native Image。