跳转到内容

返回值与错误响应

前面的章节已经解决了“请求如何进入方法”。这一章换到出口:Controller 方法返回什么,Feat Cloud 就如何把它写回客户端。

对业务接口来说,返回值不只是“能输出 JSON”。它还承担三个职责:让调用方知道请求是否成功,拿到业务数据,并在失败时得到可处理的错误信息。 简笔手绘卡通风格科技插画,蓝白科技配色,一个 Feat Cloud Controller 小工程师站在中间,左边 HTTP 请求箭头进入 Controller,右边分成三条输出通道:绿色 Success 数据包、蓝色 JSON 对象、红色 Error Message。每条通道分别标注 success、data、message。旁边有小型客户端机器人等待接收统一结果。整体像“请求入口→响应出口”的流水线,轻松幽默但专业,线条简洁,适合技术文档插图,16 横版。

先看最直接的两类返回值:文本和对象。

最短的接口可以直接返回 String。这种方式适合健康检查、简单文本响应或非常小的演示接口。

HealthController.java
import tech.smartboot.feat.cloud.annotation.Controller;
import tech.smartboot.feat.cloud.annotation.RequestMapping;
import tech.smartboot.feat.cloud.annotation.RequestMethod;
@Controller("health")
public class HealthController {
@RequestMapping(value = "/ping", method = RequestMethod.GET)
public String ping() {
return "pong";
}
}

验证:

curl 验证
curl http://localhost:8080/health/ping

返回:

响应结果
pong

如果返回的是自定义对象、集合或 Map,Feat Cloud 会把它序列化为 JSON。

UserController.java
import java.util.HashMap;
import java.util.Map;
@RequestMapping(value = "/info", method = RequestMethod.GET)
public Map<String, Object> info() {
Map<String, Object> data = new HashMap<>();
data.put("framework", "Feat Cloud");
data.put("type", "aot-web");
return data;
}

返回结果类似:

响应结果
{
"framework": "Feat Cloud",
"type": "aot-web"
}

这种写法适合很小的内部接口。只要接口会被前端、移动端或其他服务长期依赖,就建议使用统一响应对象。

当接口会被前端、移动端或其他服务长期依赖时,建议使用稳定响应结构。 简笔手绘卡通风格科技插画,一个名为“RestResult<T>”的大型透明集装箱漂浮在画面中央,内部整齐放置四个发光模块:success(绿色勾选图标)、code(数字面板)、message(对话气泡)、data(数据立方体)。前端网页、小程序手机、微服务机器人三位角色同时连接到这个统一容器,开心地读取相同格式的数据。整体表达“统一协议、统一响应结构”的概念,蓝色科技背景,线条清爽,带轻微拟人化元素,16 横版。

RestResult<T> 是 Feat Cloud 提供的通用响应封装。它把一次业务调用拆成四个字段:

字段含义
success业务是否成功
code业务响应码
message失败说明
data成功时返回的数据
UserController.java
import tech.smartboot.feat.cloud.RestResult;
import tech.smartboot.feat.cloud.annotation.Controller;
import tech.smartboot.feat.cloud.annotation.PathParam;
import tech.smartboot.feat.cloud.annotation.RequestMapping;
@Controller("users")
public class UserController {
@RequestMapping("/{username}")
public RestResult<User> getUser(@PathParam("username") String username) {
User user = findUser(username);
if (user == null) {
return RestResult.fail("User not found");
}
return RestResult.ok(user);
}
}

示例里的 UserfindUser(...) 可以替换成你自己的领域对象和查询逻辑。这里重点看响应结构。

响应结果
{
"success": true,
"code": 200,
"data": {
"username": "feat",
"role": "admin"
}
}

RestResult.fail(...) 表达的是业务失败,不等同于 HTTP 传输失败。也就是说,客户端仍然拿到一个结构稳定的 JSON 响应,可以按 successcode 做统一处理。

不是所有接口都必须包一层 RestResult

  • 健康检查、纯文本页面、简单调试接口,可以直接返回 String
  • 文件下载、图片、静态资源这类响应,更适合交给底层响应能力或静态资源处理
  • 对外稳定业务 API,建议使用 RestResult

关键不是形式统一,而是调用方能不能稳定理解你的响应。

简笔手绘卡通风格科技插画,左边是一位 Java 开发者只写了一行代码 return user;,神情轻松。中间是一个巨大的“Feat Compiler”自动工厂,内部齿轮运转,标注 AOT、Code Generation、Serializer。右边输出大量已经展开好的普通 Java 代码模块:writeBytes()、writeInt()、writeJsonValue()、gzip() 等零件,最终汇聚成 HTTP Response 数据流发送给客户端。画面表现“编译期提前展开序列化逻辑,而不是运行时反射”的核心思想。整体科技蓝配色,轻松幽默,工程感强,16 横版。 把它先理解成一件很具体的事:你写业务返回值,Feat Cloud 在编译期替你补上“怎么写到响应流里”这段样板代码

如果只说“编译期生成代码”,读者很容易觉得抽象。更直观的方式,是直接看它生成出来大概长什么样。

以一个返回 String 的接口为例,源码里你只写:

public String helloWorld() {
return "hello world";
}

但编译后,框架实际做的事情大致类似下面这样:

String rst = bean.helloWorld();
byte[] bytes = rst.getBytes("UTF-8");
if (bytes.length > 256) {
bytes = tech.smartboot.feat.core.common.FeatUtils.gzip(bytes);
ctx.Response.setHeader("Content-Encoding", "gzip");
}
ctx.Response.setContentLength(bytes.length);
ctx.Response.write(bytes);

换句话说,编译期序列化不是“神秘地帮你返回 JSON”,而是把“把返回值转成字节并写出去”这件事提前展开成了普通 Java 代码。

注解处理器会分析 Controller 方法的返回类型,并为常见类型生成直接写出逻辑。

类型序列化方式
String、基本类型、包装类型直接写输出流
DateTimestamp直接格式化后写输出流
自定义 POJO按 getter 生成访问代码
ListMap生成遍历代码
数组、Object、嵌套过深对象回退到 FastJSON2

如果返回的是 RestResult<List<Map<String, String>>> 这种更复杂的结构,生成出来的代码会更像“按字段逐个写出”:

RestResult<List<Map<String, String>>> rst = bean.hello1ss2();
os.write(b_success_true);
os.write(b_code);
writeInt(os, rst.getCode());
os.write(b_message);
writeJsonValue(os, rst.getMessage());
os.write(b_data);

这也是 Feat Cloud 性能模型的一部分:框架尽量在编译期确定响应结构,运行时少做反射和动态判断。对读者来说,真正能感受到的不是术语,而是“我写的一个返回值,最后会被展开成一段具体的写出逻辑”。