HttpRequestFunction:直接注册 Service 方法
函数路由让一个具有明确输入、输出的 Service 方法直接成为 HTTP 入口。对于只负责读取 JSON、调用 Service、输出响应的接口,可以省略单独的 Handler 类,同时保留模型、校验、业务服务和路由配置的分层。
三个类型的职责
| 类型 | 职责 |
|---|---|
IHttpRequestFunction<R, T> | 函数接口,T 为输入类型,R 为返回类型,方法签名为 R handle(T input),允许抛出异常 |
HttpRequestFunctionRouter | 保存路径、函数和 TioTypeReference<T>,按路径查找函数 |
HttpRequestFunctionHandler | 框架内部执行器,读取正文、绑定参数、调用函数并转换响应 |
源码中的接口名是 IHttpRequestFunction。业务不用创建 HttpRequestFunctionHandler;框架启动时创建执行器,在函数路由命中后调用它。
Service 方法只要符合单参数、有返回值的签名,就可以通过 service::method 转换为函数接口。Service 本身不必实现 IHttpRequestFunction,方法名也不必叫 handle。
完整示例:直接注册 Service
下面使用 Java record 定义请求与响应模型,各段代码保存为对应的独立文件。Service 通过 Aop.get 获取,配置类通过启动参数交给框架执行。
请求和响应模型
package example.model;
public record GreetingRequest(String name) {
}
package example.model;
public record GreetingResponse(String message) {
}
独立参数校验工具
package example.validation;
import example.model.GreetingRequest;
import nexus.io.tio.utils.validator.ParameterValidator;
public final class GreetingValidator {
private GreetingValidator() {
}
public static String name(GreetingRequest input) {
ParameterValidator.require(input != null, "Request body is required");
return ParameterValidator.text(input.name(), "name", 100);
}
}
Service
package example.service;
import example.model.GreetingRequest;
import example.model.GreetingResponse;
import example.validation.GreetingValidator;
import nexus.io.model.body.RespBodyVo;
public class GreetingService {
public RespBodyVo greet(GreetingRequest input) {
String name = GreetingValidator.name(input);
GreetingResponse data = new GreetingResponse("你好," + name);
return RespBodyVo.ok(data);
}
}
此例的 Service 是直接面向接口的应用服务,因此返回 RespBodyVo。框架会把该返回值转换成 JSON 响应。普通实体返回值也能序列化,但不会自动套上 RespBodyVo。如果服务需要供 HTTP、定时任务和消息消费共同调用,可以让核心业务方法返回响应实体,在路由适配函数中添加 RespBodyVo.ok(data)。
全局参数异常处理
package example.exception;
import nexus.io.model.body.RespBodyVo;
import nexus.io.model.exception.BusinessException;
import nexus.io.tio.boot.exception.TioBootExceptionHandler;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.core.ChannelContext;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.model.exception.ParameterValidationException;
import nexus.io.tio.websocket.common.WebSocketRequest;
public class ApiExceptionHandler implements TioBootExceptionHandler {
@Override
public Object handler(HttpRequest request, Throwable error) {
if (error instanceof ParameterValidationException) {
TioRequestContext.getResponse().setStatus(400);
return RespBodyVo.fail(400, error.getMessage());
}
if (error instanceof BusinessException business) {
TioRequestContext.getResponse().setStatus(business.getStatus());
return RespBodyVo.fail(business.getStatus(), business.getMessage());
}
TioRequestContext.getResponse().setStatus(500);
return RespBodyVo.fail(500, "Service unavailable");
}
@Override
public Object wsTextHandler(WebSocketRequest ws, String text, ChannelContext channel,
HttpRequest request, Throwable error) {
return RespBodyVo.fail(500, "Service unavailable");
}
@Override
public Object wsBytesHandler(WebSocketRequest ws, byte[] bytes, ChannelContext channel,
HttpRequest request, Throwable error) {
return RespBodyVo.fail(500, "Service unavailable");
}
}
注册 Service 方法
package example.config;
import example.exception.ApiExceptionHandler;
import example.model.GreetingRequest;
import example.service.GreetingService;
import nexus.io.context.BootConfiguration;
import nexus.io.jfinal.aop.Aop;
import nexus.io.model.type.TioTypeReference;
import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.tio.http.server.router.HttpRequestFunctionRouter;
public class GreetingConfig implements BootConfiguration {
@Override
public void config() {
TioBootServer server = TioBootServer.me();
server.setExceptionHandler(new ApiExceptionHandler());
HttpRequestFunctionRouter router = server.getRequestFunctionRouter();
GreetingService service = Aop.get(GreetingService.class);
router.add("/api/function/greeting", service::greet,
new TioTypeReference<GreetingRequest>() {
});
}
}
new TioTypeReference<GreetingRequest>() { } 的匿名子类保留输入类型,框架据此解析正文。普通对象和 List<GreetingRequest> 等泛型容器都可以用这种方式声明。
函数路由的注册 API 是 add,按路径匹配,不限制 GET、POST 等方法。需要显式限制 HTTP 方法时,使用后面的 HttpRequestRouter.post 方案。不要在两套路由中注册相同路径,普通 Handler 路由在函数路由前匹配。
启动应用
package example;
import example.config.GreetingConfig;
import nexus.io.tio.boot.TioApplication;
public class GreetingApplication {
public static void main(String[] args) {
TioApplication.run(GreetingApplication.class, new GreetingConfig(), args);
}
}
向 /api/function/greeting 发送请求,使用 Content-Type: application/json:
{
"name": "小明"
}
响应的 data 字段包含:
{
"message": "你好,小明"
}
使用 HttpRequestRouter.post,同样可以省略 Handler 类
对于要求明确 HTTP 方法、统一绑定 JSON/表单/查询参数的业务接口,可以把很短的 HTTP 适配代码写在路由配置中。仍然按一个接口注册一个独立函数,Service 保持独立类。下面的配置可以替换上面的 GreetingConfig,复用同一个模型、校验工具和 Service。
package example.config;
import example.exception.ApiExceptionHandler;
import example.model.GreetingRequest;
import example.service.GreetingService;
import nexus.io.context.BootConfiguration;
import nexus.io.jfinal.aop.Aop;
import nexus.io.model.body.RespBodyVo;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.tio.http.common.utils.ParameterValidationUtils;
import nexus.io.tio.http.server.router.HttpRequestRouter;
public class GreetingPostConfig implements BootConfiguration {
@Override
public void config() {
TioBootServer server = TioBootServer.me();
server.setExceptionHandler(new ApiExceptionHandler());
HttpRequestRouter router = server.getRequestRouter();
GreetingService service = Aop.get(GreetingService.class);
router.post("/api/greeting", request -> {
GreetingRequest input = ParameterValidationUtils.body(request, GreetingRequest.class);
RespBodyVo body = service.greet(input);
return TioRequestContext.getResponse().respond(body);
});
}
}
启动时将配置改为 new GreetingPostConfig()。这段代码走普通请求路由,不经过 HttpRequestFunctionHandler。它保留了方法约束和统一参数绑定,也省去了只有转发代码的 Handler 文件。适配逻辑变多时,再将函数提取为独立 Handler,便于阅读和测试。
输入绑定与返回值
函数路由按声明的输入类型读取正文,不进行 Content-Type 分支或查询参数合并:
| 输入类型 | 读取方式 |
|---|---|
String | 正文不能为空或仅含空格、制表符、换行等 trim 空白;校验后原样返回,不去除引号、不裁剪内容 |
byte[] | 正文必须有至少一个字节,原样返回二进制数据 |
Integer、Long、Byte、Short | 对应包装类型的数值转换,格式错误或溢出按参数异常处理 |
Float、Double | 数值转换,要求有限数值 |
Boolean | 正文为 true 或 false,忽略大小写 |
Character | 正文必须恰好是一个 Java char |
| 实体、集合等复杂类型 | 使用配置的 JSON provider,根据 TioTypeReference 解析 JSON 正文 |
函数执行前统一检查正文存在且字节长度大于零。JSON 解析结果必须非 null,因此空白 JSON 正文和 JSON 字面量 null 都会抛出 ParameterValidationException,不会调用 Service。字符串正文中的文本 null 是普通字符串,不按 JSON null 处理;二进制正文只检查字节长度,不检查文本空白。
{} 和 [] 可以通过基础正文校验。复杂对象的字段必填、长度,以及集合的最小元素数量仍由独立参数校验工具检查,自动绑定不代替这些约束。需要无正文的接口时,可使用 HttpRequestRouter.get 注册普通请求函数。需要表单或查询参数合并时,使用上面的 ParameterValidationUtils.body 方案;动态字段可使用 Kv,确定字段使用实体。
返回 RespBodyVo 或普通实体时,框架生成 JSON;返回 HttpResponse 时沿用该响应;字符串和二进制使用对应响应处理逻辑。返回 null 时保留当前响应。接口需要 Long、时间或嵌套结构规范化时,可在构造 RespBodyVo 前调用 ResponseValueNormalizer.normalize(data),参见请求校验与响应转换。
校验、认证和异常
函数执行前仍经过框架请求拦截器。Token 校验放在拦截器,Service 校验业务权限、资源归属和状态。直接注册 service::method 时,函数只接收声明的输入模型;签名是多个参数的业务方法需要适配函数。
框架将正文转换失败作为 ParameterValidationException 交给全局异常处理器。Service 抛出的参数异常、业务异常及其他运行时异常保留原类型;受检异常包装为运行时异常并保留 cause。应用可用统一异常策略输出 RespBodyVo,无需在每个 Service 方法中重复 try/catch。
Aop 获取的 Service 可能被多个请求共享。把请求参数保留在局部变量中,不要将当前请求、token 或当前用户写入 Service 的可变实例字段。
CORS 注解与方法引用
实现 IHttpRequestFunction 的具体类,可以在类或 handle 方法上使用 @EnableCORS。框架支持继承来的公共 handle 方法;方法上的配置优先于类配置。
service::greet 和 lambda 是函数对象,目标 Service 类或 greet 方法上的注解不会自动复制到该对象。此类接口的跨域策略可以在统一 CORS 配置或拦截器中设置。需要按具体函数类配置注解时,再使用显式的 IHttpRequestFunction 实现。相关配置见跨域。
选择建议
| 接口需求 | 适合的写法 |
|---|---|
| JSON 正文输入、单参数 Service、统一响应 | 函数路由注册 service::method |
| 明确 GET/POST、统一读取表单/查询参数 | HttpRequestRouter.get/post 加短适配函数 |
| 较多 HTTP 处理、文件流、多个服务编排 | 独立 Handler,内部通过 Aop 调用 Service |
省略 Handler 文件后,HTTP 适配职责仍由框架或路由函数承担。让 Service 保留业务逻辑、模型保留确定字段、校验和认证各自集中处理,就能减少重复代码并保持清晰的分层。
